add PSO GC snapshot decoder
This commit is contained in:
@@ -394,6 +394,7 @@ newserv has many CLI options, which can be used to access functionality other th
|
||||
* Encrypt or decrypt data using Episode 3's trivial scheme (`encrypt-trivial-data`, `decrypt-trivial-data`)
|
||||
* Encrypt or decrypt data using the Challenge Mode text algorithm (`encrypt-challenge-data`, `decrypt-challenge-data`)
|
||||
* Encrypt or decrypt PSO GC save data (.gci files) (`encrypt-gci-save`, `decrypt-gci-save`)
|
||||
* Convert a PSO GC or Episode 3 snapshot file to a BMP image (`decode-gci-snapshot`)
|
||||
* Find the likely round1 or round2 seed for a corrupt save file (`salvage-gci`)
|
||||
* Run a brute-force search for a decryption seed (`find-decryption-seed`)
|
||||
* Decode Shift-JIS text to UTF-16 (`decode-sjis`)
|
||||
|
||||
+27
@@ -273,6 +273,7 @@ enum class Behavior {
|
||||
DECRYPT_CHALLENGE_DATA,
|
||||
ENCRYPT_GCI_SAVE,
|
||||
DECRYPT_GCI_SAVE,
|
||||
DECODE_GCI_SNAPSHOT,
|
||||
FIND_DECRYPTION_SEED,
|
||||
SALVAGE_GCI,
|
||||
DECODE_QUEST_FILE,
|
||||
@@ -314,6 +315,7 @@ static bool behavior_takes_input_filename(Behavior b) {
|
||||
(b == Behavior::ENCRYPT_CHALLENGE_DATA) ||
|
||||
(b == Behavior::DECRYPT_CHALLENGE_DATA) ||
|
||||
(b == Behavior::DECRYPT_GCI_SAVE) ||
|
||||
(b == Behavior::DECODE_GCI_SNAPSHOT) ||
|
||||
(b == Behavior::SALVAGE_GCI) ||
|
||||
(b == Behavior::ENCRYPT_GCI_SAVE) ||
|
||||
(b == Behavior::DECODE_QUEST_FILE) ||
|
||||
@@ -347,6 +349,7 @@ static bool behavior_takes_output_filename(Behavior b) {
|
||||
(b == Behavior::DECRYPT_CHALLENGE_DATA) ||
|
||||
(b == Behavior::DECRYPT_GCI_SAVE) ||
|
||||
(b == Behavior::ENCRYPT_GCI_SAVE) ||
|
||||
(b == Behavior::DECODE_GCI_SNAPSHOT) ||
|
||||
(b == Behavior::ENCODE_QST) ||
|
||||
(b == Behavior::DISASSEMBLE_QUEST_SCRIPT) ||
|
||||
(b == Behavior::CONVERT_ITEMRT_REL_TO_JSON) ||
|
||||
@@ -520,6 +523,8 @@ int main(int argc, char** argv) {
|
||||
behavior = Behavior::DECRYPT_GCI_SAVE;
|
||||
} else if (!strcmp(argv[x], "encrypt-gci-save")) {
|
||||
behavior = Behavior::ENCRYPT_GCI_SAVE;
|
||||
} else if (!strcmp(argv[x], "decode-gci-snapshot")) {
|
||||
behavior = Behavior::DECODE_GCI_SNAPSHOT;
|
||||
} else if (!strcmp(argv[x], "find-decryption-seed")) {
|
||||
behavior = Behavior::FIND_DECRYPTION_SEED;
|
||||
} else if (!strcmp(argv[x], "salvage-gci")) {
|
||||
@@ -639,6 +644,8 @@ int main(int argc, char** argv) {
|
||||
} else {
|
||||
filename += ".dec";
|
||||
}
|
||||
} else if (behavior == Behavior::DECODE_GCI_SNAPSHOT) {
|
||||
filename += ".bmp";
|
||||
} else if (behavior == Behavior::DISASSEMBLE_QUEST_SCRIPT) {
|
||||
filename += ".txt";
|
||||
} else if (behavior == Behavior::CONVERT_ITEMRT_REL_TO_JSON) {
|
||||
@@ -968,6 +975,26 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::DECODE_GCI_SNAPSHOT: {
|
||||
auto data = read_input_data();
|
||||
StringReader r(data);
|
||||
const auto& header = r.get<PSOGCIFileHeader>();
|
||||
try {
|
||||
header.check();
|
||||
} catch (const exception& e) {
|
||||
log_warning("File header failed validation (%s)", e.what());
|
||||
}
|
||||
const auto& file = r.get<PSOGCSnapshotFile>();
|
||||
if (!file.checksum_correct()) {
|
||||
log_warning("File internal checksum is incorrect");
|
||||
}
|
||||
|
||||
auto img = file.decode_image();
|
||||
string saved = img.save(Image::Format::WINDOWS_BITMAP);
|
||||
write_output_data(saved.data(), saved.size());
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::SALVAGE_GCI: {
|
||||
uint64_t likely_round1_seed = 0xFFFFFFFFFFFFFFFF;
|
||||
if (system_filename) {
|
||||
|
||||
@@ -115,3 +115,42 @@ string decrypt_gci_fixed_size_file_data_section_for_salvage(
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
bool PSOGCSnapshotFile::checksum_correct() const {
|
||||
uint32_t crc = crc32("\0\0\0\0", 4);
|
||||
crc = crc32(&this->width, sizeof(*this) - sizeof(this->checksum), crc);
|
||||
return (crc == this->checksum);
|
||||
}
|
||||
|
||||
static uint32_t decode_rgb565(uint16_t c) {
|
||||
// Input: rrrrrggg gggbbbbb
|
||||
// Output: rrrrrrrr gggggggg bbbbbbbb aaaaaaaa
|
||||
return ((c << 16) & 0xF8000000) | ((c << 11) & 0x07000000) | // R
|
||||
((c << 13) & 0x00FC0000) | ((c << 7) & 0x00030000) | // G
|
||||
((c << 11) & 0x0000F800) | ((c << 6) & 0x00000700) | // B
|
||||
0x000000FF; // A
|
||||
}
|
||||
|
||||
Image PSOGCSnapshotFile::decode_image() const {
|
||||
if (this->width != 256) {
|
||||
throw runtime_error("width is incorrect");
|
||||
}
|
||||
if (this->height != 192) {
|
||||
throw runtime_error("height is incorrect");
|
||||
}
|
||||
|
||||
// 4x4 blocks of pixels
|
||||
Image ret(this->width, this->height, false);
|
||||
size_t offset = 0;
|
||||
for (size_t y = 0; y < this->height; y += 4) {
|
||||
for (size_t x = 0; x < this->width; x += 4) {
|
||||
for (size_t yy = 0; yy < 4; yy++) {
|
||||
for (size_t xx = 0; xx < 4; xx++) {
|
||||
uint32_t color = decode_rgb565(this->pixels[offset++]);
|
||||
ret.write_pixel(x + xx, y + yy, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <algorithm>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Image.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <string>
|
||||
@@ -279,6 +280,23 @@ struct PSOGCGuildCardFile {
|
||||
/* E28C */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PSOGCSnapshotFile {
|
||||
/* 00000 */ be_uint32_t checksum;
|
||||
/* 00004 */ be_uint16_t width;
|
||||
/* 00006 */ be_uint16_t height;
|
||||
/* 00008 */ parray<be_uint16_t, 0xC000> pixels;
|
||||
/* 18008 */ uint8_t unknown_a1; // Always 0x18?
|
||||
/* 18009 */ uint8_t unknown_a2;
|
||||
/* 1800A */ be_int16_t max_players;
|
||||
/* 1800C */ parray<be_uint32_t, 12> players_present;
|
||||
/* 1803C */ parray<be_uint32_t, 12> player_levels;
|
||||
/* 1806C */ parray<ptext<char, 0x18>, 12> player_names;
|
||||
/* 1818C */
|
||||
|
||||
bool checksum_correct() const;
|
||||
Image decode_image() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
template <bool IsBigEndian>
|
||||
std::string decrypt_gci_or_vms_v2_data_section(
|
||||
const void* data_section, size_t size, uint32_t round1_seed, size_t max_decrypt_bytes = 0) {
|
||||
|
||||
Reference in New Issue
Block a user