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
14 changes: 13 additions & 1 deletion src/mpq.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,19 @@ int ListFiles(HANDLE hArchive, const std::string& listfileName, bool listAll, bo
}
DWORD result = SFileEnumLocales(hArchive, findData.cFileName, fileLocales, &maxLocales, 0);

if (result == ERROR_INSUFFICIENT_BUFFER) {
if (result == ERROR_INVALID_PARAMETER) {
// This ought to mean that the file name is unknown, whereupon `SFileEnumLocales` exits early
// since its check for `IsPseudoFileName` returns true. If that is the case, it will not have
// populated `fileLocales` or have updated `maxLocales`.
// Just set the maxLocales to 1 and list the file with the unknown name once.
maxLocales = 1;
fileLocales[0] = defaultLocale;

} else if (result == ERROR_INVALID_HANDLE || result == ERROR_NOT_SUPPORTED) {
std::cerr << "[!] Internal error for file: " << findData.cFileName << std::endl;
continue;

} else if (result == ERROR_INSUFFICIENT_BUFFER) {
std::cerr << "[!] There are more than " << maxLocales << " locales for the file: " << findData.cFileName <<
". Will only list the " << maxLocales << " first files." << std::endl;
}
Expand Down
Binary file added test/data/mpq_without_internal_listfile.mpq
Binary file not shown.
235 changes: 211 additions & 24 deletions test/test_extract.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import shutil
import subprocess
from pathlib import Path

Expand Down Expand Up @@ -36,6 +37,11 @@ def test_extract_mpq_default_options(binary_path, generate_test_files):
script_dir = Path(__file__).parent
test_file = script_dir / "data" / "mpq_with_output_v1.mpq"

# Create output_file path without suffix (default extract behavior is MPQ without extension)
output_dir = test_file.with_suffix("")
if output_dir.exists():
shutil.rmtree(output_dir)

expected_output = {
"cats.txt",
"dogs.txt",
Expand All @@ -56,15 +62,12 @@ def test_extract_mpq_default_options(binary_path, generate_test_files):
# Create expected_lines set based on expected output with prefix
expected_lines = {f"[*] Extracted: {line}" for line in expected_output}

# Create output_file path without suffix (default extract behavior is MPQ without extension)
output_file = test_file.with_suffix("")

# Create output_files set based on directory contents (not full path)
output_files = set(fi.name for fi in output_file.glob("*"))
output_files = set(fi.name for fi in output_dir.glob("*"))

assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}"
assert output_lines == expected_lines, f"Unexpected output: {output_lines}"
assert output_file.exists(), "Output directory was not created"
assert output_dir.exists(), "Output directory was not created"
assert output_files == expected_output, f"Unexpected files: {output_files}"


Expand All @@ -81,6 +84,8 @@ def test_extract_mpq_output_directory_specified(binary_path, generate_test_files
script_dir = Path(__file__).parent
test_file = script_dir / "data" / "mpq_with_output_v1.mpq"
output_dir = script_dir / "data" / "extracted"
if output_dir.exists():
shutil.rmtree(output_dir)

expected_output = {
"cats.txt",
Expand All @@ -102,15 +107,12 @@ def test_extract_mpq_output_directory_specified(binary_path, generate_test_files
# Create expected_lines set based on expected output with prefix
expected_lines = {f"[*] Extracted: {line}" for line in expected_output}

# Create output_file path without suffix (default extract behavior is MPQ without extension)
output_file = output_dir.with_suffix("")

# Create output_files set based on directory contents (not full path)
output_files = set(fi.name for fi in output_file.glob("*"))
output_files = set(fi.name for fi in output_dir.glob("*"))

assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}"
assert output_lines == expected_lines, f"Unexpected output: {output_lines}"
assert output_file.exists(), "Output directory was not created"
assert output_dir.exists(), "Output directory was not created"
assert output_files == expected_output, f"Unexpected files: {output_files}"


Expand All @@ -127,8 +129,8 @@ def test_extract_file_from_mpq_output_directory_specified(binary_path, generate_
script_dir = Path(__file__).parent
test_file = script_dir / "data" / "mpq_with_output_v1.mpq"
output_dir = script_dir / "data" / "extracted_file"
for fi in output_dir.glob("*"):
fi.unlink(missing_ok=True)
if output_dir.exists():
shutil.rmtree(output_dir)


expected_output = {
Expand Down Expand Up @@ -173,8 +175,8 @@ def test_extract_file_from_mpq_with_locale(binary_path, generate_locales_mpq_tes
output_dir = script_dir / "data" / "extracted_file"
file_to_extract = "cats.txt"
locale = "esES"
for fi in output_dir.glob("*"):
fi.unlink(missing_ok=True)
if output_dir.exists():
shutil.rmtree(output_dir)

result = subprocess.run(
[str(binary_path), "extract", "-o", str(output_dir), "-f", file_to_extract, str(test_file), "--locale", locale],
Expand Down Expand Up @@ -209,8 +211,8 @@ def test_extract_file_from_mpq_with_default_locale(binary_path, generate_locales
test_file = script_dir / "data" / "mpq_with_many_locales.mpq"
output_dir = script_dir / "data" / "extracted_file"
file_to_extract = "cats.txt"
for fi in output_dir.glob("*"):
fi.unlink(missing_ok=True)
if output_dir.exists():
shutil.rmtree(output_dir)


result = subprocess.run(
Expand Down Expand Up @@ -247,8 +249,8 @@ def test_extract_file_from_mpq_with_illegal_locale(binary_path, generate_locales
output_dir = script_dir / "data" / "extracted_file"
file_to_extract = "cats.txt"
locale = "nosuchlocale"
for fi in output_dir.glob("*"):
fi.unlink(missing_ok=True)
if output_dir.exists():
shutil.rmtree(output_dir)


result = subprocess.run(
Expand Down Expand Up @@ -286,8 +288,8 @@ def test_extract_file_from_mpq_with_locale_not_in_file(binary_path, generate_loc
output_dir = script_dir / "data" / "extracted_file"
file_to_extract = "cats.txt"
locale = "ptPT" # There is no file for this locale
for fi in output_dir.glob("*"):
fi.unlink(missing_ok=True)
if output_dir.exists():
shutil.rmtree(output_dir)

result = subprocess.run(
[str(binary_path), "extract", "-o", str(output_dir), "-f", file_to_extract, str(test_file), "--locale", locale],
Expand Down Expand Up @@ -322,8 +324,8 @@ def test_extract_file_from_mpq_with_no_locale_argument_and_no_default_locale(bin
test_file = script_dir / "data" / "mpq_with_one_locale.mpq"
output_dir = script_dir / "data" / "extracted_file"
file_to_extract = "cats.txt" # There is a file by this name, but for locale esES
for fi in output_dir.glob("*"):
fi.unlink(missing_ok=True)
if output_dir.exists():
shutil.rmtree(output_dir)


result = subprocess.run(
Expand Down Expand Up @@ -363,8 +365,8 @@ def test_extract_file_from_mpq_with_wrong_locale_argument_and_no_default_locale(
output_dir = script_dir / "data" / "extracted_file"
file_to_extract = "cats.txt" # There is a file by this name, but for locale esES
locale = "deDE"
for fi in output_dir.glob("*"):
fi.unlink(missing_ok=True)
if output_dir.exists():
shutil.rmtree(output_dir)


result = subprocess.run(
Expand All @@ -388,3 +390,188 @@ def test_extract_file_from_mpq_with_wrong_locale_argument_and_no_default_locale(
assert stdout_output_lines == expected_stdout_output, f"Unexpected output: {stdout_output_lines}"
assert stderr_output_lines == expected_stderr_output, f"Unexpected output: {stderr_output_lines}"
assert not output_file.exists(), "Output directory was not created"


def test_extract_all_files_from_mpq_without_providing_listfile(binary_path, generate_test_files):
"""
Test file extraction of all files from MPQ archive with no internal listfile, when no external one is provided

This test checks:
- That files from an MPQs with no internal listfile can still be extracted.
"""
_ = generate_test_files
script_dir = Path(__file__).parent
test_file = script_dir / "data" / "mpq_without_internal_listfile.mpq"
output_dir = script_dir / "data" / "extracted_file"
if output_dir.exists():
shutil.rmtree(output_dir)


expected_output = {
"File00000000.xxx",
"File00000001.xxx",
"File00000002.xxx",
}

result = subprocess.run(
[str(binary_path), "extract", "-o", str(output_dir), str(test_file)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)

output_lines = set(result.stdout.splitlines())

# Create expected_lines set based on expected output with prefix
expected_lines = {f"[*] Extracted: {line}" for line in expected_output}

# Create output_file path without suffix (default extract behavior is MPQ without extension)
output_file = output_dir.with_suffix("")

# Create output_files set based on directory contents (not full path)
output_files = set(fi.name for fi in output_file.glob("*"))

assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}"
assert output_lines == expected_lines, f"Unexpected output: {output_lines}"
assert output_file.exists(), "Output directory was not created"
assert output_files == expected_output, f"Unexpected files: {output_files}"


def test_extract_single_file_from_mpq_without_providing_listfile(binary_path, generate_test_files):
"""
Test file extraction for MPQ archive with no internal listfile and without providing an external one

This test checks:
- That files from an MPQs with no internal listfile can still be extracted.
"""
_ = generate_test_files
script_dir = Path(__file__).parent
test_file = script_dir / "data" / "mpq_without_internal_listfile.mpq"
output_dir = script_dir / "data" / "extracted_file"
file_to_extract = "File00000001.xxx"
if output_dir.exists():
shutil.rmtree(output_dir)


expected_output = {
"File00000001.xxx",
}

result = subprocess.run(
[str(binary_path), "extract", "-o", str(output_dir), "-f", file_to_extract, str(test_file)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)

output_lines = set(result.stdout.splitlines())

# Create expected_lines set based on expected output with prefix
expected_lines = {f"[*] Extracted: {line}" for line in expected_output}

# Create output_file path without suffix (default extract behavior is MPQ without extension)
output_file = output_dir.with_suffix("")

# Create output_files set based on directory contents (not full path)
output_files = set(fi.name for fi in output_file.glob("*"))

assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}"
assert output_lines == expected_lines, f"Unexpected output: {output_lines}"
assert output_file.exists(), "Output directory was not created"
assert output_files == expected_output, f"Unexpected files: {output_files}"


def test_extract_all_files_from_mpq_providing_partial_external_listfile(binary_path, generate_test_files):
"""
Test file extraction of all files from MPQ archive containing no internal listfile,
when providing a partially complete external listfile

This test checks:
- That files from an MPQs with no internal listfile can still be extracted.
"""
_ = generate_test_files
script_dir = Path(__file__).parent
test_file = script_dir / "data" / "mpq_without_internal_listfile.mpq"
output_dir = script_dir / "data" / "extracted_file"
if output_dir.exists():
shutil.rmtree(output_dir)
listfile = script_dir / "data" / "listfile.txt"
listfile.write_text("cats.txt")


expected_output = {
"File00000000.xxx",
"(signature)",
"cats.txt",
}

result = subprocess.run(
[str(binary_path), "extract", "-o", str(output_dir), "-l", listfile, str(test_file)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)

output_lines = set(result.stdout.splitlines())

# Create expected_lines set based on expected output with prefix
expected_lines = {f"[*] Extracted: {line}" for line in expected_output}

# Create output_file path without suffix (default extract behavior is MPQ without extension)
output_file = output_dir.with_suffix("")

# Create output_files set based on directory contents (not full path)
output_files = set(fi.name for fi in output_file.glob("*"))

assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}"
assert output_lines == expected_lines, f"Unexpected output: {output_lines}"
assert output_file.exists(), "Output directory was not created"
assert output_files == expected_output, f"Unexpected files: {output_files}"


def test_extract_all_files_from_mpq_providing_complete_external_listfile(binary_path, generate_test_files):
"""
Test file extraction of all files from MPQ archive containing no internal listfile,
when providing a complete external listfile

This test checks:
- That files from an MPQs with no internal listfile can still be extracted.
"""
_ = generate_test_files
script_dir = Path(__file__).parent
test_file = script_dir / "data" / "mpq_without_internal_listfile.mpq"
output_dir = script_dir / "data" / "extracted_file"
if output_dir.exists():
shutil.rmtree(output_dir)
listfile = script_dir / "data" / "listfile.txt"
listfile.write_text("cats.txt\ndogs.txt")


expected_output = {
"(signature)",
"dogs.txt",
"cats.txt",
}

result = subprocess.run(
[str(binary_path), "extract", "-o", str(output_dir), "-l", listfile, str(test_file)],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)

output_lines = set(result.stdout.splitlines())

# Create expected_lines set based on expected output with prefix
expected_lines = {f"[*] Extracted: {line}" for line in expected_output}

# Create output_file path without suffix (default extract behavior is MPQ without extension)
output_file = output_dir.with_suffix("")

# Create output_files set based on directory contents (not full path)
output_files = set(fi.name for fi in output_file.glob("*"))

assert result.returncode == 0, f"mpqcli failed with error: {result.stderr}"
assert output_lines == expected_lines, f"Unexpected output: {output_lines}"
assert output_file.exists(), "Output directory was not created"
assert output_files == expected_output, f"Unexpected files: {output_files}"
Loading