From c8ea31386b955d170e35ef8ab307a93f656f5624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Sj=C3=B6blom?= Date: Sun, 26 Oct 2025 21:45:36 +0100 Subject: [PATCH] Adding locale support for the remove subcommand --- README.md | 13 +++++- src/main.cpp | 5 +- src/mpq.cpp | 9 ++-- src/mpq.h | 2 +- test/conftest.py | 2 +- test/test_remove.py | 111 ++++++++++++++++++++++++++++++++++++++------ 6 files changed, 120 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 4e8aa69..216b112 100644 --- a/README.md +++ b/README.md @@ -185,9 +185,20 @@ $ mpqcli add allianz.txt --locale deDE Remove a file from an existing MPQ archive. ``` -mpqcli remove fth.txt wow-patch.mpq +$ mpqcli remove fth.txt wow-patch.mpq +[-] Removing file for locale 0: fth.txt ``` +### Remove a file from an MPQ archive with a given locale + +Use the `--locale` argument to specify the locale of the file to be removed. + +``` +$ mpqcli remove alianza.txt wow-patch.mpq --locale esES +[-] Removing file for locale 1034: alianza.txt +``` + + ### List all files in an MPQ archive Pretty simple, list files in an MPQ archive. Useful to "pipe" to other tools, such as `grep` (see below for examples). diff --git a/src/main.cpp b/src/main.cpp index cf67d4b..8ef3a86 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -114,6 +114,8 @@ int main(int argc, char **argv) { remove->add_option("target", baseTarget, "Target MPQ archive") ->required() ->check(CLI::ExistingFile); + remove->add_option("--locale", baseLocale, "Locale of file to remove") + ->check(LocaleValid); // Subcommand: List CLI::App *list = app.add_subcommand("list", "List files from the MPQ archive"); @@ -264,7 +266,8 @@ int main(int argc, char **argv) { return 1; } - RemoveFile(hArchive, baseFile); + LCID locale = LangToLocale(baseLocale); + RemoveFile(hArchive, baseFile, locale); CloseMpqArchive(hArchive); } diff --git a/src/mpq.cpp b/src/mpq.cpp index f1d577a..90c7b45 100644 --- a/src/mpq.cpp +++ b/src/mpq.cpp @@ -230,16 +230,17 @@ int AddFile(HANDLE hArchive, const fs::path& localFile, const std::string& archi return 0; } -int RemoveFile(HANDLE hArchive, const std::string& archiveFilePath) { - std::cout << "[-] Removing file: " << archiveFilePath << std::endl; +int RemoveFile(HANDLE hArchive, const std::string& archiveFilePath, LCID locale) { + SFileSetLocale(locale); + std::cout << "[-] Removing file for locale " << locale <<": " << archiveFilePath << std::endl; if (!SFileHasFile(hArchive, archiveFilePath.c_str())) { - std::cerr << "[!] Failed: File doesn't exist: " << archiveFilePath << std::endl; + std::cerr << "[!] Failed: File doesn't exist for locale " << locale << ": " << archiveFilePath << std::endl; return -1; } if (!SFileRemoveFile(hArchive, archiveFilePath.c_str(), 0)) { - std::cerr << "[!] Failed: File cannot be removed: " << archiveFilePath << std::endl; + std::cerr << "[!] Failed: File cannot be removed for locale " << locale << ": " << archiveFilePath << std::endl; return -1; } diff --git a/src/mpq.h b/src/mpq.h index 3acd9a7..411d2af 100644 --- a/src/mpq.h +++ b/src/mpq.h @@ -16,7 +16,7 @@ int ExtractFile(HANDLE hArchive, const std::string& output, const std::string& f HANDLE CreateMpqArchive(std::string outputArchiveName, int32_t fileCount, int32_t mpqVersion); int AddFiles(HANDLE hArchive, const std::string& inputPath, LCID locale); int AddFile(HANDLE hArchive, const fs::path& localFile, const std::string& archiveFilePath, LCID locale); -int RemoveFile(HANDLE hArchive, const std::string& archiveFilePath); +int RemoveFile(HANDLE hArchive, const std::string& archiveFilePath, LCID locale); int ListFiles(HANDLE hHandle, const std::string &listfileName, bool listAll, bool listDetailed, std::vector& propertiesToPrint); char* ReadFile(HANDLE hArchive, const char *szFileName, unsigned int *fileSize, LCID preferredLocale); void PrintMpqInfo(HANDLE hArchive, const std::string& infoProperty); diff --git a/test/conftest.py b/test/conftest.py index f4d996a..e68dc47 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -61,7 +61,7 @@ def generate_test_files(): yield created_files -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def generate_locales_mpq_test_files(binary_path): script_dir = Path(__file__).parent diff --git a/test/test_remove.py b/test/test_remove.py index fbb9554..e801677 100644 --- a/test/test_remove.py +++ b/test/test_remove.py @@ -2,20 +2,20 @@ from pathlib import Path -def test_remove_target_mpq_does_not_exist(binary_path, generate_test_files): +def test_remove_target_mpq_does_not_exist(binary_path, generate_locales_mpq_test_files): """ Test MPQ file removal with a non-existent target. This test checks: - If the application exits correctly when the target does not exist. """ - _ = generate_test_files + _ = generate_locales_mpq_test_files script_dir = Path(__file__).parent - target_dir = script_dir / "does" / "not" / "exist" - target_file = script_dir / "data" / "files" / "cats.txt" + test_file = "cats.txt" + target_file = script_dir / "does" / "not" / "exist.mpq" result = subprocess.run( - [str(binary_path), "remove", str(target_file), str(target_dir)], + [str(binary_path), "remove", str(test_file), str(target_file)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True @@ -24,39 +24,51 @@ def test_remove_target_mpq_does_not_exist(binary_path, generate_test_files): assert result.returncode == 105, f"mpqcli failed with error: {result.stderr}" -def test_remove_target_file_does_not_exist(binary_path, generate_test_files): +def test_remove_target_file_does_not_exist(binary_path, generate_locales_mpq_test_files): """ Test MPQ file removal with a non-existent file to remove. This test checks: - If the application exits correctly when the target file to remove does not exist. """ - _ = generate_test_files + _ = generate_locales_mpq_test_files script_dir = Path(__file__).parent - target_dir = script_dir / "does" / "not" / "exist" - target_file = script_dir / "data" / "files" / "cats.txt" + test_file = "does-not-exist.txt" + target_file = script_dir / "data" / "mpq_with_many_locales.mpq" result = subprocess.run( - [str(binary_path), "remove", str(target_file), str(target_dir)], + [str(binary_path), "remove", str(test_file), str(target_file)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) - assert result.returncode == 105, f"mpqcli failed with error: {result.stderr}" + output_lines = set(result.stdout.splitlines()) + expected_stdout_output = { + "[-] Removing file for locale 0: does-not-exist.txt", + } + assert output_lines == expected_stdout_output, f"Unexpected output: {output_lines}" + + output_lines = set(result.stderr.splitlines()) + expected_stderr_output = { + "[!] Failed: File doesn't exist for locale 0: does-not-exist.txt", + } + assert output_lines == expected_stderr_output, f"Unexpected output: {output_lines}" + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" -def test_remove_file_from_mpq_archive(binary_path, generate_test_files): + +def test_remove_file_from_mpq_archive(binary_path, generate_locales_mpq_test_files): """ Test MPQ file removal. This test checks: - If the application correctly handles removing a file from an MPQ archive. """ - _ = generate_test_files + _ = generate_locales_mpq_test_files script_dir = Path(__file__).parent - target_file = script_dir / "data" / "files.mpq" test_file = "cats.txt" + target_file = script_dir / "data" / "mpq_with_many_locales.mpq" result = subprocess.run( [str(binary_path), "remove", str(test_file), str(target_file)], @@ -65,4 +77,75 @@ def test_remove_file_from_mpq_archive(binary_path, generate_test_files): text=True ) + output_lines = set(result.stdout.splitlines()) + expected_output = { + "[-] Removing file for locale 0: cats.txt", + } + assert output_lines == expected_output, f"Unexpected output: {output_lines}" + + output_lines = set(result.stderr.splitlines()) + expected_stderr_output = set() + assert output_lines == expected_stderr_output, f"Unexpected output: {output_lines}" + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + + +def test_remove_file_with_locale_from_mpq_archive(binary_path, generate_locales_mpq_test_files): + """ + Test MPQ file removal with locale. + + This test checks: + - If the application correctly handles removing a file with the given locale from an MPQ archive. + """ + _ = generate_locales_mpq_test_files + script_dir = Path(__file__).parent + test_file = "cats.txt" + target_file = script_dir / "data" / "mpq_with_many_locales.mpq" + + expected_output = { + "enUS cats.txt", + "deDE cats.txt", + "esES cats.txt", + } + verify_archive_content(binary_path, target_file, expected_output) + + # Removing without specifying locale means removing using locale 0 = enUS + result = subprocess.run( + [str(binary_path), "remove", str(test_file), str(target_file)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + + expected_output = { + "deDE cats.txt", + "esES cats.txt", + } + verify_archive_content(binary_path, target_file, expected_output) + + + result = subprocess.run( + [str(binary_path), "remove", str(test_file), str(target_file), "--locale", "esES"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}" + + expected_output = { + "deDE cats.txt", + } + verify_archive_content(binary_path, target_file, expected_output) + + +def verify_archive_content(binary_path, target_file, expected_output): + # Verify that the archive has the expected content + result = subprocess.run( + [str(binary_path), "list", "-d", str(target_file), "-p", "locale"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + output_lines = set(result.stdout.splitlines()) + assert output_lines == expected_output, f"Unexpected output: {output_lines}"