Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
5 changes: 4 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
}

Expand Down
9 changes: 5 additions & 4 deletions src/mpq.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion src/mpq.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>& propertiesToPrint);
char* ReadFile(HANDLE hArchive, const char *szFileName, unsigned int *fileSize, LCID preferredLocale);
void PrintMpqInfo(HANDLE hArchive, const std::string& infoProperty);
Expand Down
2 changes: 1 addition & 1 deletion test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
111 changes: 97 additions & 14 deletions test/test_remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)],
Expand All @@ -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}"