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
43 changes: 36 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ The `mpqcli` program has the following subcommands:

- `version`: Print the tool version number
- `about`: Print information about the tool
- `info`: Print information about an MPQ archive properties
- `info`: Print information about MPQ archive properties
- `create`: Create an MPQ archive from a target directory
- `add`: Add a file to an existing MPQ archive (not implemented)
- `remove`: Remove a file from an existing MPQ archive (not implemented)
- `add`: Add a file to an existing MPQ archive
- `remove`: Remove a file from an existing MPQ archive
- `list`: List files in a target MPQ archive
- `extract`: Extract one/all files from a target MPQ archive
- `read`: Read a specific file to stdout
Expand Down Expand Up @@ -144,22 +144,42 @@ Use the `-s` or `--sign` argument to cryptographically sign an MPQ archive with
mpqcli create --version 1 --sign <target_directory>
```

### Create an MPQ archive with a given locale

Use the `--locale` argument to specify the locale that all added files will have in the archive. Note that subsequent added files will have the default locale unless the `--locale` argument is specified again.

```
mpqcli create <target_directory> --locale koKR
```

### Add a file to an existing archive

Add a local file to an already existing MPQ archive.

```
echo "For The Horde!" > fth.txt
mpqcli add fth.txt wow-patch.mpq
$ echo "For The Horde" > fth.txt
$ mpqcli add fth.txt wow-patch.mpq
[+] Adding file for locale 0: fth.txt
```

Alternatively, you can add a file to a specific subdirectory using the `-p` or `--path` argument.

```
echo "For The Alliance!" > fta.txt
mpqcli add fta.txt wow-patch.mpq --path <path/in/archive>
$ echo "For The Alliance" > fta.txt
$ mpqcli add fta.txt wow-patch.mpq --path texts
[+] Adding file for locale 0: texts\fta.txt
```

### Add files to an MPQ archive with a given locale

Use the `--locale` argument to specify the locale that the added file will have in the archive. Note that subsequent added files will have the default locale unless the `--locale` argument is specified again.

```
$ mpqcli add allianz.txt --locale deDE
[+] Adding file for locale 1031: allianz.txt
```


### Remove a file from an existing archive

Remove a file from an existing MPQ archive.
Expand Down Expand Up @@ -325,6 +345,15 @@ mpqcli read "WoW.exe" wow-patch.mpq | xxd
00000030: 4504 8080 0280 9002 8098 0281 d801 0585 E...............
```

### Read one specific file with locale

Use the `--locale` argument to specify the locale of the file to read. If there is no file with the requested name and locale, the default locale will be used instead.

```
mpqcli read "rez\stat_txt.tbl" Patch_rt.mpq --locale ptPT
```


### Verify an MPQ archive

Check the digital signature of an MPQ archive, by verifying the signature in the archive with a collection of known Blizzard public keys (bundled in Stormlib library). The tool will print if verification is successful or fails, as well as return `0` for success and any other value for failure.
Expand Down
20 changes: 17 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ int main(int argc, char **argv) {
create->add_flag("-s,--sign", createSignArchive, "Sign the MPQ archive (default false)");
create->add_option("-v,--version", createMpqVersion, "Set the MPQ archive version (default 1)")
->check(CLI::Range(1, 2));
create->add_option("--locale", baseLocale, "Locale to use for added files")
->check(LocaleValid);

// Subcommand: Add
CLI::App *add = app.add_subcommand("add", "Add a file to an existing MPQ archive");
Expand All @@ -102,6 +104,8 @@ int main(int argc, char **argv) {
->required()
->check(CLI::ExistingFile);
add->add_option("-p,--path", basePath, "Path within MPQ archive");
add->add_option("--locale", baseLocale, "Locale to use for added file")
->check(LocaleValid);

// Subcommand: Remove
CLI::App *remove = app.add_subcommand("remove", "Remove file from an existing MPQ archive");
Expand Down Expand Up @@ -141,6 +145,7 @@ int main(int argc, char **argv) {
read->add_option("target", baseTarget, "Target MPQ archive")
->required()
->check(CLI::ExistingFile);
read->add_option("--locale", baseLocale, "Preferred locale for read file");

// Subcommand: Verify
CLI::App *verify = app.add_subcommand("verify", "Verify the MPQ archive");
Expand Down Expand Up @@ -208,7 +213,9 @@ int main(int argc, char **argv) {
// Create the MPQ archive and add files
HANDLE hArchive = CreateMpqArchive(outputFile, fileCount, createMpqVersion);
if (hArchive) {
AddFiles(hArchive, baseTarget);
LCID locale = LangToLocale(baseLocale);
AddFiles(hArchive, baseTarget, locale);

if (createSignArchive) {
SignMpqArchive(hArchive);
}
Expand Down Expand Up @@ -242,7 +249,9 @@ int main(int argc, char **argv) {
archivePath = WindowsifyFilePath(archiveFullPath.u8string());
}

AddFile(hArchive, baseFile, archivePath);
LCID locale = LangToLocale(baseLocale);

AddFile(hArchive, baseFile, archivePath, locale);
CloseMpqArchive(hArchive);
}

Expand Down Expand Up @@ -302,8 +311,13 @@ int main(int argc, char **argv) {
return 1;
}

LCID locale = LangToLocale(baseLocale);
if (baseLocale != "default" && locale == defaultLocale) {
std::cout << "[!] Warning: The locale '" << baseLocale << "' is unknown. Will use default locale instead." << std::endl;
}

uint32_t fileSize;
char* fileContent = ReadFile(hArchive, baseFile.c_str(), &fileSize);
char* fileContent = ReadFile(hArchive, baseFile.c_str(), &fileSize, locale);
if (fileContent == NULL) {
return 1;
}
Expand Down
29 changes: 18 additions & 11 deletions src/mpq.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ HANDLE CreateMpqArchive(std::string outputArchiveName, int32_t fileCount, int32_
return hMpq;
}

int AddFiles(HANDLE hArchive, const std::string& target) {
int AddFiles(HANDLE hArchive, const std::string& target, LCID locale) {
// We need to "clean" the target path to ensure it is a valid directory
// and to strip any directory structure from the files we add
fs::path targetPath = fs::path(target);
Expand All @@ -166,14 +166,13 @@ int AddFiles(HANDLE hArchive, const std::string& target) {
// Normalise path for MPQ
std::string archiveFilePath = WindowsifyFilePath(inputFilePath.u8string());

AddFile(hArchive, entry.path().u8string(), archiveFilePath);
AddFile(hArchive, entry.path().u8string(), archiveFilePath, locale);
}
}
return 0;
}

int AddFile(HANDLE hArchive, const fs::path& localFile, const std::string& archiveFilePath) {
std::cout << "[+] Adding file: " << archiveFilePath << std::endl;
int AddFile(HANDLE hArchive, const fs::path& localFile, const std::string& archiveFilePath, LCID locale) {

// Return if file doesn't exist on disk
if (!fs::exists(localFile)) {
Expand All @@ -182,11 +181,18 @@ int AddFile(HANDLE hArchive, const fs::path& localFile, const std::string& archi
}

// Check if file exists in MPQ archive
bool hasFile = SFileHasFile(hArchive, archiveFilePath.c_str());
if (hasFile) {
std::cerr << "[!] File already exists in MPQ archive: " << archiveFilePath << " Skipping..." << std::endl;
return -1;
SFileSetLocale(locale);
HANDLE hFile;
if (SFileOpenFileEx(hArchive, archiveFilePath.c_str(), SFILE_OPEN_FROM_MPQ, &hFile)) {
int32_t fileLocale = GetFileInfo<int32_t>(hFile, SFileInfoLocale);
if (fileLocale == locale) {
std::cerr << "[!] File for locale " << locale << " already exists in MPQ archive: " << archiveFilePath
<< " - Skipping..." << std::endl;
return -1;
}
}
SFileCloseFile(hFile);
std::cout << "[+] Adding file for locale " << locale << ": " << archiveFilePath << std::endl;

// Verify that we are not exceeding maxFile size of the archive, and if we do, increase it
int32_t numberOfFiles = GetFileInfo<int32_t>(hArchive, SFileMpqNumberOfFiles);
Expand All @@ -202,7 +208,7 @@ int AddFile(HANDLE hArchive, const fs::path& localFile, const std::string& archi
}
}

// Set file attributes in MPQ archive (compression and encryption)
// Set file attributes in the MPQ archive (compression and encryption)
DWORD dwFlags = MPQ_FILE_COMPRESS | MPQ_FILE_ENCRYPTED;
DWORD dwCompression = MPQ_COMPRESSION_ZLIB;

Expand Down Expand Up @@ -415,7 +421,8 @@ int ListFiles(HANDLE hArchive, const std::string& listfileName, bool listAll, bo
return 0;
}

char* ReadFile(HANDLE hArchive, const char *szFileName, unsigned int *fileSize) {
char* ReadFile(HANDLE hArchive, const char *szFileName, unsigned int *fileSize, LCID preferredLocale) {
SFileSetLocale(preferredLocale);
if (!SFileHasFile(hArchive, szFileName)) {
std::cerr << "[!] Failed: File doesn't exist: " << szFileName << std::endl;
return NULL;
Expand Down Expand Up @@ -549,7 +556,7 @@ int32_t PrintMpqSignature(HANDLE hArchive, std::string target) {
} else if (signatureType == SIGNATURE_TYPE_WEAK) {
const char* szFileName = "(signature)";
uint32_t fileSize;
char* fileContent = ReadFile(hArchive, szFileName, &fileSize);
char* fileContent = ReadFile(hArchive, szFileName, &fileSize, defaultLocale);

if (fileContent == NULL) {
std::cerr << "[!] Failed to read weak signature file." << std::endl;
Expand Down
6 changes: 3 additions & 3 deletions src/mpq.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ int SignMpqArchive(HANDLE hArchive);
int ExtractFiles(HANDLE hArchive, const std::string& output, const std::string &listfileName);
int ExtractFile(HANDLE hArchive, const std::string& output, const std::string& fileName, bool keepFolderStructure);
HANDLE CreateMpqArchive(std::string outputArchiveName, int32_t fileCount, int32_t mpqVersion);
int AddFiles(HANDLE hArchive, const std::string& inputPath);
int AddFile(HANDLE hArchive, const fs::path& localFile, const std::string& archiveFilePath);
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 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);
char* ReadFile(HANDLE hArchive, const char *szFileName, unsigned int *fileSize, LCID preferredLocale);
void PrintMpqInfo(HANDLE hArchive, const std::string& infoProperty);
uint32_t VerifyMpqArchive(HANDLE hArchive);
int32_t PrintMpqSignature(HANDLE hArchive, std::string target);
Expand Down
Loading