add extract-ppk action
This commit is contained in:
@@ -107,6 +107,7 @@ set(SOURCES
|
||||
src/PatchServer.cc
|
||||
src/PlayerFilesManager.cc
|
||||
src/PlayerSubordinates.cc
|
||||
src/PPKArchive.cc
|
||||
src/ProxyCommands.cc
|
||||
src/ProxyServer.cc
|
||||
src/PSOEncryption.cc
|
||||
|
||||
@@ -742,6 +742,7 @@ The data formats that newserv can convert to/from are:
|
||||
| Quest map (.dat) | None | `disassemble-quest-map` |
|
||||
| AFS archive (.afs) | None | `extract-afs` |
|
||||
| BML archive (.bml) | None | `extract-bml` |
|
||||
| PPK archive (.ppk) | None | `extract-ppk` |
|
||||
| GSL archive (.gsl) | `generate-gsl` | `extract-gsl` |
|
||||
| GVM texture (.gvm) | `encode-gvm` | None |
|
||||
| Bitmap font (.fon) | `encode-bitmap-font` | `decode-bitmap-font` |
|
||||
|
||||
+14
-4
@@ -34,6 +34,7 @@
|
||||
#include "ImageEncoder.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "NetworkAddresses.hh"
|
||||
#include "PPKArchive.hh"
|
||||
#include "PSOGCObjectGraph.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "PatchServer.hh"
|
||||
@@ -1688,7 +1689,7 @@ void a_extract_archive_fn(phosg::Arguments& args) {
|
||||
for (const auto& entry_it : arch.all_entries()) {
|
||||
auto e = arch.get(entry_it.first);
|
||||
string out_file = output_prefix + entry_it.first;
|
||||
phosg::save_file(out_file.c_str(), e.first, e.second);
|
||||
phosg::save_file(out_file, e.first, e.second);
|
||||
fprintf(stderr, "... %s\n", out_file.c_str());
|
||||
}
|
||||
} else if (args.get<string>(0) == "extract-bml") {
|
||||
@@ -1710,6 +1711,13 @@ void a_extract_archive_fn(phosg::Arguments& args) {
|
||||
fprintf(stderr, "... %s\n", out_file.c_str());
|
||||
}
|
||||
}
|
||||
} else if (args.get<string>(0) == "extract-ppk") {
|
||||
auto files = decode_ppk_file(*data_shared, args.get<string>("password", true));
|
||||
for (const auto& [filename, data] : files) {
|
||||
string out_file = output_prefix + filename;
|
||||
phosg::save_file(out_file, data);
|
||||
fprintf(stderr, "... %s\n", out_file.c_str());
|
||||
}
|
||||
} else {
|
||||
throw logic_error("unimplemented archive type");
|
||||
}
|
||||
@@ -1717,16 +1725,18 @@ void a_extract_archive_fn(phosg::Arguments& args) {
|
||||
|
||||
Action a_extract_afs("extract-afs", nullptr, a_extract_archive_fn);
|
||||
Action a_extract_gsl("extract-gsl", nullptr, a_extract_archive_fn);
|
||||
Action a_extract_bml("extract-bml", "\
|
||||
Action a_extract_bml("extract-bml", nullptr, a_extract_archive_fn);
|
||||
Action a_extract_ppk("extract-ppk", "\
|
||||
extract-afs [INPUT-FILENAME] [--big-endian]\n\
|
||||
extract-gsl [INPUT-FILENAME] [--big-endian]\n\
|
||||
extract-bml [INPUT-FILENAME] [--big-endian]\n\
|
||||
Extract all files from an AFS, GSL, or BML archive into the current\n\
|
||||
extract-ppk [INPUT-FILENAME] [--big-endian]\n\
|
||||
Extract all files from an AFS, GSL, BML, or PPK archive into the current\n\
|
||||
directory. input-filename may be specified. If output-filename is\n\
|
||||
specified, then it is treated as a prefix which is prepended to the\n\
|
||||
filename of each file contained in the archive. If --big-endian is given,\n\
|
||||
the archive header is read in GameCube format; otherwise it is read in\n\
|
||||
PC/BB format.\n",
|
||||
PC/BB format. For PPK archives, the --password= option is required.\n",
|
||||
a_extract_archive_fn);
|
||||
|
||||
Action a_encode_sjis(
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "AFSArchive.hh"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
#include "Compression.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct Entry {
|
||||
pstring<TextEncoding::ASCII, 0x80> filename;
|
||||
le_uint32_t unknown_a1;
|
||||
le_uint32_t decompressed_size;
|
||||
le_uint32_t compressed_size;
|
||||
le_uint32_t checksum;
|
||||
// Data follows immediately here
|
||||
// Trailer: le_uint32_t entry_size; //
|
||||
};
|
||||
|
||||
static void decrypt_ppk_data(std::string& data, const std::string& filename, const std::string& password) {
|
||||
if (password.size() > 0xFF) {
|
||||
throw runtime_error("password is too long");
|
||||
}
|
||||
|
||||
uint8_t key[0x100];
|
||||
for (size_t z = 0; z < 0x100; z++) {
|
||||
key[z] = z ^ filename[z % filename.size()];
|
||||
}
|
||||
for (size_t z = 0; z < password.size(); z++) {
|
||||
key[z + 1] ^= password[z];
|
||||
}
|
||||
for (size_t z = 0; z < 0xFC; z++) {
|
||||
key[z + 4] ^= key[z];
|
||||
}
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
data[z] ^= key[z & 0xFF];
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> decode_ppk_file(const std::string& data, const std::string& password) {
|
||||
phosg::StringReader r(data);
|
||||
|
||||
uint32_t signature = r.get_u32b();
|
||||
if (signature != 0x50503130 && signature != 0x4D5A5000) { // 'PP10' or 'MZP\0'
|
||||
throw runtime_error("file is not a ppk archive");
|
||||
}
|
||||
|
||||
unordered_map<string, string> ret;
|
||||
for (size_t offset = r.size() - 4; offset >= 4;) {
|
||||
uint32_t size = r.pget_u32l(offset) ^ 0x12345678;
|
||||
uint32_t entry_offset = offset - size;
|
||||
const auto& entry = r.pget<Entry>(entry_offset);
|
||||
string data = r.pread(entry_offset + sizeof(Entry), entry.compressed_size);
|
||||
string filename = entry.filename.decode();
|
||||
decrypt_ppk_data(data, phosg::tolower(filename), password);
|
||||
uint32_t checksum = phosg::crc32(data.data(), data.size());
|
||||
if (checksum != entry.checksum) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"incorrect checksum for file %s (expected %08" PRIX32 "; received %08" PRIX32 ")",
|
||||
filename.c_str(), entry.checksum.load(), checksum));
|
||||
}
|
||||
if (entry.compressed_size < entry.decompressed_size) {
|
||||
data = prs_decompress(data);
|
||||
}
|
||||
if (!ret.emplace(filename, data).second) {
|
||||
throw runtime_error(phosg::string_printf("archive contains multiple files with the same name (%s)", filename.c_str()));
|
||||
}
|
||||
offset = entry_offset - 4;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
std::unordered_map<std::string, std::string> decode_ppk_file(const std::string& data, const std::string& password);
|
||||
Reference in New Issue
Block a user