From ccaf656c250bbfec5e5c6d31668f0df4e82a0beb Mon Sep 17 00:00:00 2001 From: Miku AuahDark Date: Sat, 11 Mar 2017 12:57:31 +0700 Subject: [PATCH] Complete API changing. Everything should work now. Fixed V3 decryption which gives invalid output for the first decrypted byte. --- README.md | 6 +- VersionInfo.rc | 6 +- src/CN_Decrypter.cc | 28 +- src/DecrypterContext.h | 141 +++++++--- src/EN_Decrypter.cc | 26 +- src/Helper.cc | 127 +++++---- src/HonokaMiku.cc | 576 ++++++++++++++++++++--------------------- src/JP_Decrypter.cc | 28 +- src/TW_Decrypter.cc | 24 +- src/V1_Decrypter.cc | 18 ++ src/V3_Decrypter.cc | 74 ++++-- 11 files changed, 612 insertions(+), 442 deletions(-) diff --git a/README.md b/README.md index 7a8e5c7..ee9e3c7 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ Basically it allows decryption of LL!SIF game files so that it can be opened wit File decryption support ======================= -HonokaMiku supports decryption of SIF EN/WW, JP, TW, and CN game files, from version 1 encryption format to version 3 encryption format. +HonokaMiku supports decryption of SIF EN/WW, JP, TW, and CN game files, from version 1 encryption format to version 4 encryption format. -It is also possible to decrypt Playground game files not from LL!SIF if you have the MD5 prefix key by using the API +It is also possible to decrypt Playground game files not from LL!SIF if you have the MD5 prefix key by using the API. Documentation ============= @@ -27,4 +27,4 @@ Implementation In Other Languages ================================= Did you rewrite HonokaMiku to other languages? Send me your work and I'll add it in here. -* [PHP Implementation](https://github.com/MikuAuahDark/SIFUniDecrypt) by [MikuAuahDark](https://github.com/MikuAuahDark) +* ~~[PHP Implementation](https://github.com/MikuAuahDark/SIFUniDecrypt) by [MikuAuahDark](https://github.com/MikuAuahDark)~~ (Correspond to HonokaMiku v4.2.1) diff --git a/VersionInfo.rc b/VersionInfo.rc index bbba313..33e6b9b 100644 --- a/VersionInfo.rc +++ b/VersionInfo.rc @@ -5,10 +5,10 @@ #define HONOKAMIKU_VERSION_MAJOR 4 #define HONOKAMIKU_VERSION_MINOR 3 -#define HONOKAMIKU_VERSION_PATCH 0 +#define HONOKAMIKU_VERSION_PATCH 1 //#define HONOKAMIKU_VERSION_STRING "" _STR2(HONOKAMIKU_VERSION_MAJOR) "." _STR2(HONOKAMIKU_VERSION_MINOR) "." _STR2(HONOKAMIKU_VERSION_PATCH) "" -#define HONOKAMIKU_VERSION_STRING "5.0.0 beta-1" -#define HONOKAMIKU_VERSION_STRING_RC "5.0.0 beta-1" +#define HONOKAMIKU_VERSION_STRING "5.0.0 beta-2" +#define HONOKAMIKU_VERSION_STRING_RC "5.0.0 beta-2" #define HONOKAMIKU_VERSION ((HONOKAMIKU_VERSION_MAJOR*100000000)+(HONOKAMIKU_VERSION_MINOR*100000)+(HONOKAMIKU_VERSION_PATCH)) #ifdef RC_INVOKED diff --git a/src/CN_Decrypter.cc b/src/CN_Decrypter.cc index d6384a5..c5c665e 100644 --- a/src/CN_Decrypter.cc +++ b/src/CN_Decrypter.cc @@ -24,18 +24,38 @@ static const uint32_t cn_key_tables[64] = { 0xcd84d15bu, 0xa0290f82u, 0xd3e95afcu, 0x9c6a97b4u }; -HonokaMiku::CN3_Dctx::CN3_Dctx(const void* header, const char* filename): HonokaMiku::V3_Dctx(HonokaMiku::GetPrefixFromGameId(7), header, filename) {} +//////////////////// +// Version 2 code // +//////////////////// + +HonokaMiku::CN2_Dctx::CN2_Dctx(const void* header, const char* filename):V2_Dctx(GetPrefixFromGameId(5), header, filename) {} + +uint32_t HonokaMiku::CN2_Dctx::get_id() +{ + return HONOKAMIKU_GAMETYPE_CN | HONOKAMIKU_DECRYPT_V2; +} + +//////////////////// +// Version 3 code // +//////////////////// + +HonokaMiku::CN3_Dctx::CN3_Dctx(const void* header, const char* filename):V3_Dctx(GetPrefixFromGameId(7), header, filename) {} const uint32_t* HonokaMiku::CN3_Dctx::_getKeyTables() { return cn_key_tables; } -void HonokaMiku::CN3_Dctx::final_setup(const char* filename, const void* block_rest, int force_version) +uint32_t HonokaMiku::CN3_Dctx::get_id() +{ + return HONOKAMIKU_GAMETYPE_CN | HONOKAMIKU_DECRYPT_V3; +} + +void HonokaMiku::CN3_Dctx::final_setup(const char* filename, const void* block_rest, int32_t force_version) { finalDecryptV3(this, 1847, filename, block_rest, force_version); } -HonokaMiku::CN3_Dctx* HonokaMiku::CN3_Dctx::encrypt_setup(const char* filename,void* hdr_out) +HonokaMiku::CN3_Dctx* HonokaMiku::CN3_Dctx::encrypt_setup(const char* filename, void* hdr_out, int32_t fv) { CN3_Dctx* dctx = new CN3_Dctx; - setupEncryptV3(dctx, HonokaMiku::GetPrefixFromGameId(7), 1847, filename, hdr_out); + setupEncryptV3(dctx, HonokaMiku::GetPrefixFromGameId(7), 1847, filename, hdr_out, fv); return dctx; } diff --git a/src/DecrypterContext.h b/src/DecrypterContext.h index 916b1f5..5517b15 100644 --- a/src/DecrypterContext.h +++ b/src/DecrypterContext.h @@ -13,8 +13,62 @@ #include +#define HONOKAMIKU_GAMETYPE_JP 0x0000 +#define HONOKAMIKU_GAMETYPE_EN 0x0001 +#define HONOKAMIKU_GAMETYPE_TW 0x0002 +#define HONOKAMIKU_GAMETYPE_CN 0x0003 + +#define HONOKAMIKU_DECRYPT_V1 0x00010000 +#define HONOKAMIKU_DECRYPT_V2 0x00020000 +#define HONOKAMIKU_DECRYPT_V3 0x00030000 +#define HONOKAMIKU_DECRYPT_V4 0x00040000 +#define HONOKAMIKU_DECRYPT_V5 0x00050000 +#define HONOKAMIKU_DECRYPT_V6 0x00060000 +#define HONOKAMIKU_DECRYPT_V7 0x00070000 + namespace HonokaMiku { + /// \brief Gets game key prefix for specificed game id. + /// \param game_id The game ID. Valid game IDs are + /// 1. EN/WW Version 2 + /// 2. JP Version 2/KR + /// 3. TW Version 2 + /// 4. JP Version 3 + /// 5. CN Version 2 + /// 6. EN/WW Version 3 + /// 7. CN Version 3 + /// 8. TW Version 3 + /// \returns prefix key of specificed game ID or NULL if game ID is invalid + /// \note If you just want to get prefix key, the version doesn't matter. + inline const char* GetPrefixFromGameId(char game_id) + { + switch(game_id) + { + case 1: + case 6: return "BFd3EnkcKa"; + case 2: + case 4: return "Hello"; + case 3: + case 8: return "M2o2B7i3M6o6N88"; + case 5: + case 7: return "iLbs0LpvJrXm3zjdhAr4"; + default: return NULL; + } + } + + /// \brief same as GetPrefixFromGameId() except this one operates on `HONOKAMIKU_GAMETYPE_*` constants + inline const char* GetPrefixFromGameType(uint32_t gt) + { + switch(gt) + { + case HONOKAMIKU_GAMETYPE_JP: return "Hello"; + case HONOKAMIKU_GAMETYPE_EN: return "BFd3EnkcKa"; + case HONOKAMIKU_GAMETYPE_TW: return "M2o2B7i3M6o6N88"; + case HONOKAMIKU_GAMETYPE_CN: return "iLbs0LpvJrXm3zjdhAr4"; + default: return NULL; + } + } + class V2_Dctx; class V3_Dctx; @@ -55,6 +109,8 @@ namespace HonokaMiku /// \param filename File name that want to be decrypted. This affects the key calculation. /// \param block_rest The next 12-bytes header of Version 3 encrypted file. virtual void final_setup(const char* filename, const void* block_rest, int32_t fv = 0) = 0; + /// \brief Function to get the decrypter filename + virtual uint32_t get_id() = 0; protected: inline DecrypterContext() {} /// \brief The key update function. Used to update the key. Used internally and protected @@ -64,7 +120,7 @@ namespace HonokaMiku /// For encrypt_setup static members for SIF EN, TW, KR, and CN. Used internally void setupEncryptV2(V2_Dctx* dctx, const char* prefix, const char* filename, void* hdr_out); /// For encrypt_setup static members for Version 3. Used internally - void setupEncryptV3(V3_Dctx* dctx, const char* prefix, uint16_t name_sum_base, const char* filename, void* hdr_out); + void setupEncryptV3(V3_Dctx* dctx, const char* prefix, uint16_t name_sum_base, const char* filename, void* hdr_out, int32_t force_version = 0); /// To finalize version 3 decrypter void finalDecryptV3(V3_Dctx* dctx, uint32_t expected_sum_name, const char* filename, const void* block_rest, int32_t force_version = 0); @@ -72,6 +128,8 @@ namespace HonokaMiku class V1_Dctx: public DecrypterContext { protected: + int32_t game_ver; + inline V1_Dctx() {} void update(); public: @@ -79,6 +137,7 @@ namespace HonokaMiku /// \param key_prefix String prepended before MD5 calculation /// \param filename File name that want to be decrypted. V1_Dctx(const char* key_prefix, const char* filename); + uint32_t get_id(); void decrypt_block(void* buffer, uint32_t len); void decrypt_block(void* dest, const void* src, uint32_t len); void goto_offset(uint32_t offset); @@ -134,7 +193,7 @@ namespace HonokaMiku static V3_Dctx* encrypt_setup(const char* prefix, const unsigned int* key_tables, const char* filename, void* hdr_out); virtual void final_setup(const char* , const void* , int ) = 0; - friend void setupEncryptV3(V3_Dctx* , const char* , uint16_t , const char* , void* ); + friend void setupEncryptV3(V3_Dctx* , const char* , uint16_t , const char* , void* , int32_t ); friend void finalDecryptV3(V3_Dctx* , uint32_t , const char* , const void* , int32_t ); }; @@ -151,10 +210,11 @@ namespace HonokaMiku /// \param filename File name that want to be decrypted. This affects the key calculation. /// \exception std::runtime_error The header does not match and this decrypter context can't decrypt it. JP3_Dctx(const void* header, const char* filename); + uint32_t get_id(); /// \brief Creates SIF JP decrypter context specialized for encryption. /// \param filename File name that want to be encrypted. This affects the key calculation. /// \param hdr_out Pointer with size of 16-bytes to store the file header. - static JP3_Dctx* encrypt_setup(const char* filename, void* hdr_out); + static JP3_Dctx* encrypt_setup(const char* filename, void* hdr_out, int32_t force_ver = 0); void final_setup(const char* filename, const void* block_rest, int force_ver = 0); }; @@ -170,10 +230,11 @@ namespace HonokaMiku /// \param filename File name that want to be decrypted. This affects the key calculation. /// \exception std::runtime_error The header does not match and this decrypter context can't decrypt it. EN3_Dctx(const void* header, const char* filename); + uint32_t get_id(); /// \brief Creates SIF EN decrypter context specialized for encryption. (version 3) /// \param filename File name that want to be encrypted. This affects the key calculation. /// \param hdr_out Pointer with size of 16-bytes to store the file header. - static EN3_Dctx* encrypt_setup(const char* filename, void* hdr_out); + static EN3_Dctx* encrypt_setup(const char* filename, void* hdr_out, int32_t force_ver = 0); void final_setup(const char* filename, const void* block_rest, int32_t force_ver = 0); }; @@ -189,10 +250,11 @@ namespace HonokaMiku /// \param filename File name that want to be decrypted. This affects the key calculation. /// \exception std::runtime_error The header does not match and this decrypter context can't decrypt it. TW3_Dctx(const void* header, const char* filename); + uint32_t get_id(); /// \brief Creates SIF TW decrypter context specialized for encryption. (version 3) /// \param filename File name that want to be encrypted. This affects the key calculation. /// \param hdr_out Pointer with size of 16-bytes to store the file header. - static TW3_Dctx* encrypt_setup(const char* filename, void* hdr_out); + static TW3_Dctx* encrypt_setup(const char* filename, void* hdr_out, int32_t force_ver = 0); void final_setup(const char* filename, const void* block_rest, int32_t force_ver = 0); }; @@ -208,10 +270,11 @@ namespace HonokaMiku /// \param filename File name that want to be decrypted. This affects the key calculation. /// \exception std::runtime_error The header does not match and this decrypter context can't decrypt it. CN3_Dctx(const void* header, const char* filename); + uint32_t get_id(); /// \brief Creates SIF CN decrypter context specialized for encryption. (version 3) /// \param filename File name that want to be encrypted. This affects the key calculation. /// \param hdr_out Pointer with size of 16-bytes to store the file header. - static CN3_Dctx* encrypt_setup(const char* filename, void* hdr_out); + static CN3_Dctx* encrypt_setup(const char* filename, void* hdr_out, int32_t force_ver = 0); void final_setup(const char* filename, const void* block_rest, int32_t force_ver = 0); }; @@ -225,14 +288,15 @@ namespace HonokaMiku /// \param header The first 4-bytes contents of the file /// \param filename File name that want to be decrypted. This affects the key calculation. /// \exception std::runtime_error The header does not match and this decrypter context can't decrypt it. - EN2_Dctx(const void* header, const char* filename):V2_Dctx("BFd3EnkcKa", header, filename) {} + EN2_Dctx(const void* header, const char* filename); + uint32_t get_id(); /// \brief Creates SIF EN decrypter context specialized for encryption. /// \param filename File name that want to be encrypted. This affects the key calculation. /// \param hdr_out Pointer with size of 16-bytes to store the file header. inline static EN2_Dctx* encrypt_setup(const char* filename, void* hdr_out) { EN2_Dctx* dctx = new EN2_Dctx(); - setupEncryptV2(dctx, "BFd3EnkcKa", filename, hdr_out); + setupEncryptV2(dctx, GetPrefixFromGameId(1), filename, hdr_out); return dctx; } }; @@ -247,14 +311,15 @@ namespace HonokaMiku /// \param header The first 4-bytes contents of the file /// \param filename File name that want to be decrypted. This affects the key calculation. /// \exception std::runtime_error The header does not match and this decrypter context can't decrypt it. - TW2_Dctx(const void* header, const char* filename):V2_Dctx("M2o2B7i3M6o6N88", header, filename) {} + TW2_Dctx(const void* header, const char* filename); + uint32_t get_id(); /// \brief Creates SIF TW decrypter context specialized for encryption. /// \param filename File name that want to be encrypted. This affects the key calculation. /// \param hdr_out Pointer with size of 16-bytes to store the file header. inline static TW2_Dctx* encrypt_setup(const char* filename, void* hdr_out) { TW2_Dctx* dctx = new TW2_Dctx(); - setupEncryptV2(dctx,"M2o2B7i3M6o6N88", filename, hdr_out); + setupEncryptV2(dctx, GetPrefixFromGameId(3), filename, hdr_out); return dctx; } }; @@ -269,14 +334,15 @@ namespace HonokaMiku /// \param header The first 4-bytes contents of the file /// \param filename File name that want to be decrypted. This affects the key calculation. /// \exception std::runtime_error The header does not match and this decrypter context can't decrypt it. - JP2_Dctx(const void* header, const char* filename):V2_Dctx("Hello", header, filename) {} + JP2_Dctx(const void* header, const char* filename); + uint32_t get_id(); /// \brief Creates SIF JP decrypter context specialized for encryption. (Version 2) /// \param filename File name that want to be encrypted. This affects the key calculation. /// \param hdr_out Pointer with size of 16-bytes to store the file header. inline static JP2_Dctx* encrypt_setup(const char* filename, void* hdr_out) { JP2_Dctx* dctx = new JP2_Dctx(); - setupEncryptV2(dctx, "Hello", filename, hdr_out); + setupEncryptV2(dctx, GetPrefixFromGameId(2), filename, hdr_out); return dctx; } }; @@ -292,14 +358,15 @@ namespace HonokaMiku /// \param header The first 4-bytes contents of the file /// \param filename File name that want to be decrypted. This affects the key calculation. /// \exception std::runtime_error The header does not match and this decrypter context can't decrypt it. - CN2_Dctx(const void* header, const char* filename):V2_Dctx("iLbs0LpvJrXm3zjdhAr4", header, filename) {} + CN2_Dctx(const void* header, const char* filename); + uint32_t get_id(); /// \brief Creates SIF CN decrypter context specialized for encryption. /// \param filename File name that want to be encrypted. This affects the key calculation. /// \param hdr_out Pointer with size of 16-bytes to store the file header. inline static CN2_Dctx* encrypt_setup(const char* filename, void* hdr_out) { - CN2_Dctx* dctx=new CN2_Dctx(); - setupEncryptV2(dctx, "iLbs0LpvJrXm3zjdhAr4", filename,hdr_out); + CN2_Dctx* dctx = new CN2_Dctx(); + setupEncryptV2(dctx, GetPrefixFromGameId(5), filename, hdr_out); return dctx; } }; @@ -320,15 +387,15 @@ namespace HonokaMiku /// \param header The first 4-bytes contents of the file /// \param game_type Pointer to store the game ID. See GetPrefixFromGameId() for valid game IDs. /// \returns DecrypterContext or NULL if no suitable decryption method is available. - DecrypterContext* FindSuitable(const char* filename, const void* header, char* game_type = NULL); + DecrypterContext* FindSuitable(const char* filename, const void* header); /// \brief Creates decrypter context based the game ID. - /// \param game_id The game ID. Valid game IDs can be seen in HonokaMiku.cc Line 86 + /// \param game_prop The game ID. Valid game IDs can be seen in HonokaMiku.cc Line 86 /// \param header The first 4-bytes contents of the file /// \param filename File name that want to be decrypted. This affects the key calculation. /// \returns DecrypterContext or NULL if specificed game ID is invalid /// \exception std::runtime_error Thrown if header is not valid for decryption - DecrypterContext* CreateFrom(char game_id, const void* header, const char* filename); + DecrypterContext* RequestDecrypter(uint32_t game_prop, const void* header, const char* filename); /// \brief Creates decrypter context for encryption based the game ID. /// \param game_id The game ID. See GetPrefixFromGameId() for valid game IDs. @@ -336,33 +403,23 @@ namespace HonokaMiku /// \param header_out Pointer to store the file header. The memory size should be 16-bytes /// to reserve space for Version 3 decrypter. /// \returns DecrypterContext ready for encryption - DecrypterContext* EncryptPrepare(char game_id, const char* filename, void* header_out); + DecrypterContext* RequestEncrypter(uint32_t game_prop, const char* filename, void* header_out); - /// \brief Gets game key prefix for specificed game id. - /// \param game_id The game ID. Valid game IDs are - /// 1. EN/WW Version 2 - /// 2. JP Version 2/KR - /// 3. TW Version 2 - /// 4. JP Version 3 - /// 5. CN Version 2 - /// 6. EN/WW Version 3 - /// 7. CN Version 3 - /// 8. TW Version 3 - /// \returns prefix key of specificed game ID or NULL if game ID is invalid - /// \note If you just want to get prefix key, the version doesn't matter. - inline const char* GetPrefixFromGameId(char game_id) + /// \brief Get header size for specific decryption types + /// \param dectype `HONOAMIKU_DECRYPT_*` constants + /// \returns header size (or -1 if unknown) + inline int32_t GetHeaderSize(uint32_t dectype) { - switch(game_id) + switch(dectype & 0xFFFF0000) { - case 1: - case 6: return "BFd3EnkcKa"; - case 2: - case 4: return "Hello"; - case 3: - case 8: return "M2o2B7i3M6o6N88"; - case 5: - case 7: return "iLbs0LpvJrXm3zjdhAr4"; - default: return NULL; + case HONOKAMIKU_DECRYPT_V1: return 0; + case HONOKAMIKU_DECRYPT_V2: return 4; + case HONOKAMIKU_DECRYPT_V3: + case HONOKAMIKU_DECRYPT_V4: + case HONOKAMIKU_DECRYPT_V5: + case HONOKAMIKU_DECRYPT_V6: + case HONOKAMIKU_DECRYPT_V7: return 16; + default: return (-1); } } } diff --git a/src/EN_Decrypter.cc b/src/EN_Decrypter.cc index f239438..da0191e 100644 --- a/src/EN_Decrypter.cc +++ b/src/EN_Decrypter.cc @@ -24,18 +24,38 @@ static const uint32_t en_key_tables[64] = { 2963448367u ,3316646782u,322755307u ,3531653795u }; -HonokaMiku::EN3_Dctx::EN3_Dctx(const void* header, const char* filename): HonokaMiku::V3_Dctx(HonokaMiku::GetPrefixFromGameId(1), header, filename) {} +//////////////////// +// Version 2 code // +//////////////////// + +HonokaMiku::EN2_Dctx::EN2_Dctx(const void* header, const char* filename):V2_Dctx(GetPrefixFromGameId(1), header, filename) {} + +uint32_t HonokaMiku::EN2_Dctx::get_id() +{ + return HONOKAMIKU_GAMETYPE_EN | HONOKAMIKU_DECRYPT_V2; +} + +//////////////////// +// Version 3 code // +//////////////////// + +HonokaMiku::EN3_Dctx::EN3_Dctx(const void* header, const char* filename): V3_Dctx(GetPrefixFromGameId(1), header, filename) {} const uint32_t* HonokaMiku::EN3_Dctx::_getKeyTables() { return en_key_tables; } +uint32_t HonokaMiku::EN3_Dctx::get_id() +{ + return HONOKAMIKU_GAMETYPE_EN | HONOKAMIKU_DECRYPT_V3; +} + void HonokaMiku::EN3_Dctx::final_setup(const char* filename, const void* block_rest, int force_version) { finalDecryptV3(this, 844, filename, block_rest, force_version); } -HonokaMiku::EN3_Dctx* HonokaMiku::EN3_Dctx::encrypt_setup(const char* filename,void* hdr_out) +HonokaMiku::EN3_Dctx* HonokaMiku::EN3_Dctx::encrypt_setup(const char* filename, void* hdr_out, int force_version) { EN3_Dctx* dctx = new EN3_Dctx; - setupEncryptV3(dctx, HonokaMiku::GetPrefixFromGameId(1), 844, filename, hdr_out); + setupEncryptV3(dctx, HonokaMiku::GetPrefixFromGameId(1), 844, filename, hdr_out, force_version); return dctx; } diff --git a/src/Helper.cc b/src/Helper.cc index 467bdcd..fad2395 100644 --- a/src/Helper.cc +++ b/src/Helper.cc @@ -8,66 +8,91 @@ #include "DecrypterContext.h" -HonokaMiku::DecrypterContext* HonokaMiku::FindSuitable(const char* filename, const void* header, char* game_type) +#define MAKE_FACTORY_FUNCTION(gametype) \ + static HonokaMiku::DecrypterContext* factory_##gametype(uint32_t dec, const char* filename, const void* header) \ + { \ + HonokaMiku::DecrypterContext* x = NULL; \ + \ + switch(dec) \ + { \ + case HONOKAMIKU_DECRYPT_V1: \ + x = new HonokaMiku::V1_Dctx(HonokaMiku::GetPrefixFromGameType(HONOKAMIKU_GAMETYPE_##gametype), filename); break; \ + case HONOKAMIKU_DECRYPT_V2: \ + x = new HonokaMiku::gametype##2_Dctx(header, filename); break; \ + case HONOKAMIKU_DECRYPT_V3: \ + case HONOKAMIKU_DECRYPT_V4: \ + case HONOKAMIKU_DECRYPT_V5: \ + case HONOKAMIKU_DECRYPT_V6: \ + case HONOKAMIKU_DECRYPT_V7: \ + x = new HonokaMiku::gametype##3_Dctx(header, filename); break; \ + } \ + return x; \ + } + +MAKE_FACTORY_FUNCTION(JP); +MAKE_FACTORY_FUNCTION(EN); +MAKE_FACTORY_FUNCTION(TW); +MAKE_FACTORY_FUNCTION(CN); + +typedef HonokaMiku::DecrypterContext*(*FactoryFunc)(uint32_t , const char* , const void* ); + + +FactoryFunc DecrypterConstructors[] = { + &factory_JP, + &factory_EN, + &factory_TW, + &factory_CN +}; + +HonokaMiku::DecrypterContext* HonokaMiku::FindSuitable(const char* filename, const void* header) { - char temp; DecrypterContext* dctx = NULL; - if(game_type == NULL) game_type = &temp; - try { dctx = new EN3_Dctx(header, filename); - *game_type = 6; } catch(std::runtime_error& ) { try { dctx = new JP3_Dctx(header, filename); - *game_type = 2; } catch(std::runtime_error& ) { try { dctx = new EN2_Dctx(header, filename); - *game_type = 1; } catch(std::runtime_error& ) { try { dctx = new JP2_Dctx(header, filename); - *game_type = 4; } catch(std::runtime_error& ) { try { dctx = new TW2_Dctx(header, filename); - *game_type = 3; } catch(std::runtime_error) { try { dctx = new CN2_Dctx(header, filename); - *game_type = 5; } catch(std::runtime_error) { try { dctx = new CN3_Dctx(header, filename); - *game_type = 7; } catch(std::runtime_error) { try { dctx = new TW3_Dctx(header, filename); - *game_type = 8; } catch(std::runtime_error) {} @@ -82,31 +107,14 @@ HonokaMiku::DecrypterContext* HonokaMiku::FindSuitable(const char* filename, con return dctx; } -HonokaMiku::DecrypterContext* HonokaMiku::CreateFrom(char game_id, const void* header, const char* filename) +HonokaMiku::DecrypterContext* HonokaMiku::RequestDecrypter(uint32_t game_prop, const void* header, const char* filename) { try { - switch(game_id) - { - case 1: - return new EN2_Dctx(header, filename); - case 2: - return new JP3_Dctx(header, filename); - case 3: - return new TW2_Dctx(header, filename); - case 4: - return new JP2_Dctx(header, filename); - case 5: - return new CN2_Dctx(header, filename); - case 6: - return new EN3_Dctx(header, filename); - case 7: - return new CN3_Dctx(header, filename); - case 8: - return new TW3_Dctx(header, filename); - default: - return NULL; - } + if((game_prop & 0xFFFF) > HONOKAMIKU_GAMETYPE_CN) + return NULL; + + return DecrypterConstructors[game_prop & 0xFFFF](game_prop & 0xFFFF0000U, filename, header); } catch(std::runtime_error& ) { @@ -114,27 +122,38 @@ HonokaMiku::DecrypterContext* HonokaMiku::CreateFrom(char game_id, const void* h } } -HonokaMiku::DecrypterContext* HonokaMiku::EncryptPrepare(char game_id, const char* filename, void* header_out) +HonokaMiku::DecrypterContext* HonokaMiku::RequestEncrypter(uint32_t game_prop, const char* filename, void* header_out) { - switch(game_id) + uint32_t dectype = game_prop & 0xFFFF0000U; + uint32_t gt = game_prop & 0xFFFF; + + if(dectype == HONOKAMIKU_DECRYPT_V1) + return new V1_Dctx(GetPrefixFromGameType(game_prop & 0xFFFF), filename); + else if(dectype == HONOKAMIKU_DECRYPT_V2) { - case 1: - return EN2_Dctx::encrypt_setup(filename, header_out); - case 2: - return JP3_Dctx::encrypt_setup(filename, header_out); - case 3: - return TW2_Dctx::encrypt_setup(filename, header_out); - case 4: - return JP2_Dctx::encrypt_setup(filename, header_out); - case 5: - return CN2_Dctx::encrypt_setup(filename, header_out); - case 6: - return EN3_Dctx::encrypt_setup(filename, header_out); - case 7: - return CN3_Dctx::encrypt_setup(filename, header_out); - case 8: - return TW3_Dctx::encrypt_setup(filename, header_out); - default: - return NULL; + switch(gt) + { + case HONOKAMIKU_GAMETYPE_JP: return JP2_Dctx::encrypt_setup(filename, header_out); + case HONOKAMIKU_GAMETYPE_EN: return EN2_Dctx::encrypt_setup(filename, header_out); + case HONOKAMIKU_GAMETYPE_TW: return TW2_Dctx::encrypt_setup(filename, header_out); + case HONOKAMIKU_GAMETYPE_CN: return CN2_Dctx::encrypt_setup(filename, header_out); + default: return NULL; + } + } + else if(dectype >= HONOKAMIKU_DECRYPT_V3) + { + int32_t fv = dectype >> 16; + fv = fv == 0xFFFF ? 0 : fv; + + switch(gt) + { + case HONOKAMIKU_GAMETYPE_JP: return JP3_Dctx::encrypt_setup(filename, header_out, fv); + case HONOKAMIKU_GAMETYPE_EN: return EN3_Dctx::encrypt_setup(filename, header_out, fv); + case HONOKAMIKU_GAMETYPE_TW: return TW3_Dctx::encrypt_setup(filename, header_out, fv); + case HONOKAMIKU_GAMETYPE_CN: return CN3_Dctx::encrypt_setup(filename, header_out, fv); + default: return NULL; + } } + else + return NULL; } diff --git a/src/HonokaMiku.cc b/src/HonokaMiku.cc index bbf64a3..148197a 100644 --- a/src/HonokaMiku.cc +++ b/src/HonokaMiku.cc @@ -46,7 +46,7 @@ const char* CompilerName() strcpy(compilername,"IBM XL C/C++ Version " __xlc__); #elif defined(_MSC_VER) /* Microsoft Visual Studio. --------------------------------- */ - sprintf(compilername,"Visual Studio Version %02d.%02d.%05d.%02d", int(_MSC_VER/100), int(_MSC_VER)%100, int(_MSC_FULL_VER)%100000, _MSC_BUILD); + sprintf(compilername,"MSVC %02d.%02d.%05d.%02d", int(_MSC_VER/100), int(_MSC_VER)%100, int(_MSC_FULL_VER)%100000, _MSC_BUILD); #elif defined(__PGI) /* Portland Group PGCC/PGCPP. ------------------------------- */ sprintf(compilername, "PGCC/PGCPP Version %02d.%d.%d", __PGIC__, __PGIC_MINOR, __PGIC_PATCHLEVEL__); @@ -66,7 +66,6 @@ const char* CompilerName() return compilername; } - // Copied from MSVC strnicmp.c int msvcr110_strnicmp (const char * first, const char * last, size_t count) { @@ -95,284 +94,232 @@ int msvcr110_strnicmp (const char * first, const char * last, size_t count) } } +bool AssembleGameName(int game_prop, char* dest) +{ + const char* gamepref = NULL; + int32_t ver = game_prop >> 16; + + if(ver > 255) + return false; + + switch(game_prop & 0xFFFF) + { + case HONOKAMIKU_GAMETYPE_JP: + gamepref = "JP"; + break; + case HONOKAMIKU_GAMETYPE_EN: + gamepref = "EN"; + break; + case HONOKAMIKU_GAMETYPE_TW: + gamepref = "TW"; + break; + case HONOKAMIKU_GAMETYPE_CN: + gamepref = "CN"; + break; + case 0xFFFF: + gamepref = "Unk"; + default: + return false; + } + + return sprintf(dest, "%s (Version %d) game file", gamepref, ver) > 0; +} + +// Must be deleted with delete[] +char* AssembleGameName(int game_prop) +{ + char* temp = new char[32]; + + if(AssembleGameName(game_prop, temp)) + return temp; + + delete[] temp; + return NULL; +} + +int32_t GetGameProp(const char* str) +{ + uint32_t game_prop = (-1); + + // Single character check + if(str[1] == 0 || str[2] == 0) + { + uint32_t defver = (str[1] == 0 || str[1] > 57 || str[1] < 49 ? HONOKAMIKU_DECRYPT_V3 : int32_t(str[1] - 48) << 16); + + if(str[0] == 'j') + game_prop = HONOKAMIKU_GAMETYPE_JP | defver; + else if(str[0] == 'w') + game_prop = HONOKAMIKU_GAMETYPE_EN | defver; + else if(str[0] == 't') + game_prop = HONOKAMIKU_GAMETYPE_TW | defver; + else if(str[0] == 'c') + game_prop = HONOKAMIKU_GAMETYPE_JP | defver; + } + + if(game_prop != 0xFFFFFFFF) + return game_prop; + + // Multi-character-check + uint32_t defver = (-1); + + if(strlen(str) == 9) + { + defver = int32_t(str[8] - 48); + + if(defver < 1 || defver > 9) + return (-1); + else + defver <<= 16; + } + else + defver = HONOKAMIKU_DECRYPT_V3; + + if(msvcr110_strnicmp(str, "sif-jp", 6) == 0) + return HONOKAMIKU_GAMETYPE_JP | defver; + else if( + msvcr110_strnicmp(str, "sif-ww", 6) == 0 || + msvcr110_strnicmp(str, "sif-en", 6) == 0 + ) + return HONOKAMIKU_GAMETYPE_EN | defver; + else if(msvcr110_strnicmp(str, "sif-tw", 6) == 0) + return HONOKAMIKU_GAMETYPE_TW | defver; + else if(msvcr110_strnicmp(str, "sif-cn", 6) == 0) + return HONOKAMIKU_GAMETYPE_CN | defver; + + return (-1); +} + static char usage_string[] = "Usage: %s [output file=input] [options]\n" " and [output file] can be - for stdin and stdout.\n" "\nOptions:\n" - " -1 Encrypt/decrypt version 1 game files.\n" - " --olddecrypt Also needs one of the game file flag\n" - " to be set. Encryption mode will be\n" - " ignored if this option is set.\n" - " New decrypt option can't be specificed\n" - " if this option is specificed.\n" - "\n" - " -b Use basename as decrypt/encrypt\n" - " --basename key. Required if reading from stdin.\n" + " -b Use basename as decrypt/encrypt\n" + " -basename key. Required if reading from stdin.\n" "\n" - " -c[3] Assume is SIF CN game file.\n" - " --sif-cn[-v3]" + " -c[1|2|3] Assume is SIF CN game file.\n" + " -sif-cn[-v1|v2|v3] Defaults to version 3\n" "\n" " -d Detect game file type only. [output file]\n" - " --detect and the other parameters is omitted.\n" + " -detect and the other parameters is omitted.\n" "\n" " -e Encrypt instead of decrypting\n" - " --encrypt it. If you use this, one of the game file\n" + " -encrypt it. If you use this, one of the game file\n" " flag must be specificed.\n" "\n" " -h Show this message\n" " -?\n" - " --help\n" + " -help\n" "\n" - " -j[3] Assume is SIF JP game file.\n" - " --sif-jp[-v3]\n" + " -j[1|2|3|4] Assume is SIF JP game file.\n" + " -sif-jp[-v1|v2|v3|v4] Defaults to version 3\n" "\n" - " -t[3] Assume is SIF TW game file.\n" - " --sif-tw[-v3]\n" + " -t[1|2|3] Assume is SIF TW game file.\n" + " -sif-tw[-v1|v2|v3] Defaults to version 3\n" "\n" " -v Show version information.\n" - " --version\n" + " -version\n" "\n" - " -w[3] Assume is SIF EN game file.\n" - " --sif-en[-v3]\n" - " --sif-ww[-v3]\n" + " -w[1|2|3] Assume is SIF EN game file.\n" + " -sif-en[-v1|v2|v3] Defaults to version 3\n" + " -sif-ww[-v1|v2|v3]\n" "\n" - " -x Cross-encrypt to another game file.\n" - " --cross-encrypt can be w, j, t, or c\n" - " Add \"3\" after the code to use version\n" - " 3 encrypt/decrypt algorithm\n" + " -x Cross-encrypt to another game file.\n" + " -cross-encrypt can be w, j, t, or c for short\n" + " name (add 1, 2, or 3 to set version).\n" + " For long name, see argument options \n" + " above." "\n"; -int g_InPos = 0; // Input filename argv position -int g_OutPos = 0; // Output filename argv position -const char* g_Basename = NULL; // Basename -const char* g_ProgramName; // Executable name. -char g_DecryptGame = 0; // 0 = not specificed; 1 = ww/en; 2 = jp; 3 = tw; 4 = kr; 5 = cn -char g_XEncryptGame = 0; // 0 = cross-encryption not specificed; 1 = ww/en; 2 = jp; 3 = tw; 4 = kr; 5 = cn -bool g_Encrypt = false; // Encrypt mode? -bool g_TestMode = false; // Detect only. -bool g_Version1 = false; // Encrypt/decrypt version 1 game files (SIF v1.x) - -char get_gametype(const char* a) -{ - if(msvcr110_strnicmp(a, "w", 2) == 0 || msvcr110_strnicmp(a, "sif-en", 7) == 0 || msvcr110_strnicmp(a, "sif-ww", 7) == 0) - return 1; - else if(msvcr110_strnicmp(a, "w3", 3) == 0 || msvcr110_strnicmp(a, "sif-en-v3", 10) == 0 || msvcr110_strnicmp(a, "sif-ww-v3", 10) == 0) - return 6; - else if(msvcr110_strnicmp(a, "j", 2) == 0 || msvcr110_strnicmp(a, "sif-jp", 7) == 0) - return 4; - else if(msvcr110_strnicmp(a, "j3", 3) == 0 || msvcr110_strnicmp(a, "sif-jp-v3", 10) == 0) - return 2; - else if(msvcr110_strnicmp(a, "t", 2) == 0 || msvcr110_strnicmp(a, "sif-tw", 7) == 0) - return 3; - else if(msvcr110_strnicmp(a, "t3", 3) == 0 || msvcr110_strnicmp(a, "sif-tw-v3", 10) == 0) - return 8; - else if(msvcr110_strnicmp(a, "c", 2) == 0 || msvcr110_strnicmp(a, "sif-cn", 7) == 0) - return 5; - else if(msvcr110_strnicmp(a, "c3", 3) == 0 || msvcr110_strnicmp(a, "sif-cn-v3", 10) == 0) - return 7; - return 0; -} - -const char* gameid_to_string(char gameid) -{ - switch(gameid) - { - case 1: - return "EN (Version 2) game file"; - case 2: - return "JP (Version 3) game file"; - case 3: - return "TW (Version 2) game file"; - case 4: - return "JP (Version 2) game file"; - case 5: - return "CN (Version 2) game file"; - case 6: - return "EN (Version 3) game file"; - case 7: - return "CN (Version 3) game file"; - case 8: - return "TW (Version 3) game file"; - default: - return "Unknown"; - } -} - -const char* gameid_to_string_v1(char gameid) -{ - switch(gameid) - { - case 1: - case 6: - return "EN game file"; - case 2: - case 4: - return "JP game file"; - case 3: - case 8: - return "TW game file"; - case 5: - case 7: - return "CN game file"; - default: - return "Unknown"; - } -} +int32_t g_InPos = 0; // Input filename argv position +int g_OutPos = 0; // Output filename argv position +const char* g_Basename = NULL; // Basename +const char* g_ProgramName; // Executable name. +uint32_t g_DecryptGame = 0xFFFFFFFFU; // Bitwise now +uint32_t g_XEncryptGame = 0xFFFFFFFFU; // Bitwise now +bool g_Encrypt = false; // Encrypt mode? +bool g_TestMode = false; // Detect only? -void parse_args(int argc,char* argv[]) +void parse_args(int argc, char* argv[]) { int i = 1; - for(; i < argc; i++) + + for(char* arg = argv[i]; i < argc; arg = argv[++i]) { - if(argv[i][0]=='-' && argv[i][1] != '\0') + if(*arg == '-' && *++arg != 0) { - char s = tolower(argv[i][1]); - if(s == '-') - { - // Long-name argument parse - char* start_arg = argv[i] + 2; - - if(msvcr110_strnicmp(start_arg, "basename", 9) == 0) - { - i++; - - if(argv[i] == NULL) - fprintf(stderr, "Ignoring --basename\n"); - else - g_Basename = __DctxGetBasename(argv[i]); - } + bool arg_f = false; - else if(msvcr110_strnicmp(start_arg, "cross-encrypt", 14) == 0) + // Arguments that need 1 parameter + if(argv[i + 1] != NULL) + { + if( + msvcr110_strnicmp("b", arg, 2) == 0 || + msvcr110_strnicmp("basename", arg, 9) == 0 + ) { - i++; - - if(argv[i] == NULL) - fprintf(stderr, "Ignoring --cross-encrypt\n"); - else - { - g_XEncryptGame = get_gametype(argv[i]); + g_Basename = argv[++i]; - if(g_XEncryptGame == 0) fprintf(stderr, "Ignoring --cross-encrypt: Unknown game type %s\n", argv[i]); - } + arg_f = true; } - - else if(msvcr110_strnicmp(start_arg, "encrypt", 8) == 0) - g_Encrypt=true; - - else if(msvcr110_strnicmp(start_arg, "detect", 7) == 0) - g_TestMode=true; - - else if(msvcr110_strnicmp(start_arg, "help", 5) == 0) + else if( + msvcr110_strnicmp("x", arg, 2) == 0 || + msvcr110_strnicmp("cross-encrypt", arg, 14) == 0 + ) { - fprintf(stderr, usage_string, g_ProgramName); - exit(0); - } + if((g_XEncryptGame = GetGameProp(argv[i + 1])) == (-1)) + fprintf(stderr, "Cross-encrypt: Invalid game '%s'", argv[i + 1]); - else if(msvcr110_strnicmp(start_arg, "olddecrypt", 10) == 0) - g_Version1 = true; - - else if(msvcr110_strnicmp(start_arg, "version", 8) == 0) - { - fprintf(stderr, "Version " HONOKAMIKU_VERSION_STRING " (%d)\n", HONOKAMIKU_VERSION); - fputs("Build at " __DATE__ " " __TIME__ "\n", stderr); - fprintf(stderr, "Compiled using %s\n\n", CompilerName()); - - exit(0); - } - else if((g_DecryptGame = get_gametype(start_arg)) == 0) - fprintf(stderr, "Ignoring %s\n", argv[i]); - } - // Short argument name parse - else if(s == '1') - g_Version1 = true; - - else if(s == 'b') - { - if(argv[i][2] == 0) - { i++; - if(argv[i] == NULL) - fprintf(stderr, "Ignoring -b\n"); - else - g_Basename = argv[i]; + arg_f = true; } - else - g_Basename = argv[i] + 2; } - else if(s == 'c') - if(argv[i][2] == '3') - g_DecryptGame = 7; - else - g_DecryptGame = 5; - - else if(s == 'd') - g_TestMode = true; - - else if(s == 'e') - g_Encrypt = true; - else if(s == 'h' || s == '?') + if(arg_f == false) { - fprintf(stderr, usage_string, g_ProgramName); - exit(0); - } - - else if(s == 'j') - if(argv[i][2] == '3') - g_DecryptGame = 2; - else - g_DecryptGame = 4; - - else if(s == 't') - if(argv[i][2] == '3') - g_DecryptGame = 8; - else - g_DecryptGame = 3; - - else if(s == 'v') - { - fprintf(stderr, "Version " HONOKAMIKU_VERSION_STRING " (%d)\n", HONOKAMIKU_VERSION); - fputs("Build at " __DATE__ " " __TIME__ "\n", stderr); - fprintf(stderr, "Compiled using %s\n\n", CompilerName()); - - exit(0); - } - - else if(s == 'w') - if(argv[i][2] == '3') - g_DecryptGame = 6; - else - g_DecryptGame = 1; - - else if(s == 'x') - { - char* target = NULL; - if(argv[i][2] == 0) + // No parameter argument + if( + msvcr110_strnicmp("d", arg, 2) == 0 || + msvcr110_strnicmp("detect", arg, 7) == 0 + ) + g_TestMode = true; + else if( + msvcr110_strnicmp("h", arg, 2) == 0 || + msvcr110_strnicmp("?", arg, 2) == 0 || + msvcr110_strnicmp("help", arg, 5) == 0 + ) { - i++; - - if(argv[i] == NULL) - fprintf(stderr, "Ignoring -x\n"); - else - g_XEncryptGame = get_gametype(target = argv[i]); + fprintf(stderr, usage_string, g_ProgramName); + exit(0); } - else + else if( + msvcr110_strnicmp("v", arg, 2) == 0 || + msvcr110_strnicmp("version", arg, 8) == 0 + ) { - g_XEncryptGame = get_gametype(target = argv[i] + 2); - - if(g_XEncryptGame == 0) fprintf(stderr, "Ignoring -x: Unknown game type %s\n", target); + fprintf(stderr, + "Version %s (%03d%03d%05d)\n" + "Build at " __DATE__ " " __TIME__ "\n" + "Compiled with %s\n" + , HONOKAMIKU_VERSION_STRING, HONOKAMIKU_VERSION_MAJOR, HONOKAMIKU_VERSION_MINOR, HONOKAMIKU_VERSION_PATCH, + CompilerName() + ); + exit(0); } + else if( + msvcr110_strnicmp("e", arg, 2) == 0 || + msvcr110_strnicmp("encrypt", arg, 8) == 0 + ) + g_Encrypt = true; + else if((g_DecryptGame = GetGameProp(arg)) == (-1)) + fprintf(stderr, "Unknown argument: %s\n", arg); } - - else - fprintf(stderr, "Ignoring %s: Unknown option\n", argv[i]); } else if(g_InPos == 0) g_InPos = i; else if(g_OutPos == 0) g_OutPos = i; else - fprintf(stderr, "Ignoring %s\n", argv[i]); + fprintf(stderr, "Ignored: %s\n", argv[i]); } } @@ -401,14 +348,7 @@ void check_args(char* argv[]) g_Basename = __DctxGetBasename(argv[g_InPos]); } - if(g_Version1 && g_DecryptGame == 0) - { - fputs("Error: version 1 decryption requires game file switch\n\n", stderr); - fprintf(stderr, usage_string, g_ProgramName); - - exit(EINVAL); - } - else if(g_Encrypt && g_DecryptGame == 0) + else if(g_Encrypt && g_DecryptGame == (-1)) { fputs("Error: encrypt mode requires game file switch\n\n", stderr); fprintf(stderr, usage_string, g_ProgramName); @@ -422,12 +362,6 @@ void check_args(char* argv[]) exit(EINVAL); } - - if(g_Encrypt && g_Version1) - { - fputs("Warning: encrypt mode option ignored for version 1\n", stderr); - g_Encrypt = false; - } } int main(int argc, char* argv[]) @@ -438,7 +372,7 @@ int main(int argc, char* argv[]) unsigned char header_buffer[16]; char* filename_input; char* filename_output; - char* _reserved_memory = new char[2*1024*1024]; // 2 MB of memory. If we're running out of memory, delete this. + char* _reserved_memory = new char[2*1024*1024]; // 2 MB of memory. Used for gamefile string and OOM handling mechanism size_t file_contents_length = 0; // This is the file size size_t file_contents_size = 4096; // This is the memory size size_t header_size = 4; // Used on encrypt mode @@ -462,7 +396,7 @@ int main(int argc, char* argv[]) return 1; } - parse_args(argc,argv); + parse_args(argc, argv); check_args(argv); filename_input = argv[g_InPos]; @@ -486,11 +420,14 @@ int main(int argc, char* argv[]) if(g_TestMode) { - char expected = g_DecryptGame; + uint32_t expected = g_DecryptGame; + fputs("Detecting: ", stderr); if(fread(header_buffer, 1, 4, file_stream) != 4) { + toosmallfilebuffer: + delete[] _reserved_memory; fclose(file_stream); @@ -498,23 +435,41 @@ int main(int argc, char* argv[]) return EBADF; } - dctx = HonokaMiku::FindSuitable(g_Basename, header_buffer, &g_DecryptGame); - - delete[] _reserved_memory; + dctx = HonokaMiku::FindSuitable(g_Basename, header_buffer); if (dctx == NULL) { + delete[] _reserved_memory; fputs("Unknown\n", stderr); if(expected > 0) return (-1); } else { - fprintf(stderr, "%s\n", gameid_to_string(g_DecryptGame)); + if(dctx->version >= 3) + { + if(fread(header_buffer, 1, 12, file_stream) != 12) + goto toosmallfilebuffer; - if(expected > 0) + try + { + dctx->final_setup(g_Basename, header_buffer); + } + catch(std::runtime_error& e) + { + fprintf(stderr, "\nError: %s\n", e.what()); + + return EBADF; + } + } + + AssembleGameName(g_DecryptGame = dctx->get_id(), _reserved_memory); + fprintf(stderr, "%s\n", _reserved_memory); + + if(expected != 0xFFFFFFFFU) { - fprintf(stderr, "Expected: %s\n", gameid_to_string(expected)); + AssembleGameName(expected, _reserved_memory + 512); + fprintf(stderr, "Expected: %s\n", _reserved_memory + 512); return expected == g_DecryptGame ? 0 : (-1); } @@ -539,19 +494,14 @@ int main(int argc, char* argv[]) if(g_Encrypt) { - fprintf(stderr, "Encrypt as: %s\n", gameid_to_string(g_DecryptGame)); + dctx = HonokaMiku::RequestEncrypter(g_DecryptGame, g_Basename, header_buffer); - dctx = HonokaMiku::EncryptPrepare(g_DecryptGame, g_Basename, header_buffer); + AssembleGameName(dctx->get_id(), _reserved_memory); + fprintf(stderr, "Encrypt as: %s\n", _reserved_memory); } else { - if(g_Version1) - { - fprintf(stderr, "Version 1 decrypt: %s\n", gameid_to_string_v1(g_DecryptGame)); - - dctx = new HonokaMiku::V1_Dctx(HonokaMiku::GetPrefixFromGameId(g_DecryptGame), g_Basename); - } - else if(fread(header_buffer, 1, 4, file_stream) != 4) + if(fread(header_buffer, 1, 4, file_stream) != 4) { file2small_byte_buffer: @@ -563,11 +513,12 @@ int main(int argc, char* argv[]) return EBADF; } - else if(g_DecryptGame > 0) + else if(g_DecryptGame != 0xFFFFFFFF) { - fprintf(stderr, "Decrypt as: %s\n", gameid_to_string(g_DecryptGame)); + AssembleGameName(g_DecryptGame, _reserved_memory); + fprintf(stderr, "Decrypt as: %s\n", _reserved_memory); - dctx = HonokaMiku::CreateFrom(g_DecryptGame, header_buffer, g_Basename); + dctx = HonokaMiku::RequestDecrypter(g_DecryptGame, header_buffer, g_Basename); if(dctx == NULL) { @@ -580,20 +531,19 @@ int main(int argc, char* argv[]) return EINVAL; } - if(dctx->version == 3) + if(dctx->version >= 3) { - version3_finalize_setup: - if(fread(header_buffer, 1, 12, file_stream) != 12) { delete dctx; + fputc('\n', stderr); goto file2small_byte_buffer; } try { - dctx->final_setup(g_Basename, header_buffer); + dctx->final_setup(g_Basename, header_buffer, g_DecryptGame >> 16); } catch(std::runtime_error& e) { @@ -611,7 +561,7 @@ int main(int argc, char* argv[]) { fputs("Auto-detecting: ", stderr); - dctx = HonokaMiku::FindSuitable(g_Basename, header_buffer, &g_DecryptGame); + dctx = HonokaMiku::FindSuitable(g_Basename, header_buffer); if (dctx == NULL) { @@ -623,10 +573,34 @@ int main(int argc, char* argv[]) return EINVAL; } - fprintf(stderr, "%s\n", gameid_to_string(g_DecryptGame)); + if(dctx->version >= 3) + { + if(fread(header_buffer, 1, 12, file_stream) != 12) + { + fputc('\n', stderr); + delete dctx; + + goto file2small_byte_buffer; + } + + try + { + dctx->final_setup(g_Basename, header_buffer); + } + catch(std::runtime_error& e) + { + delete dctx; + delete[] _reserved_memory; + free(file_contents); + fclose(file_stream); + + fprintf(stderr, "\nError: %s\n", e.what()); + return EBADF; + } + } - if(dctx->version == 3) - goto version3_finalize_setup; + AssembleGameName(g_DecryptGame = dctx->get_id(), _reserved_memory); + fprintf(stderr, "%s\n", _reserved_memory); } } @@ -635,11 +609,31 @@ int main(int argc, char* argv[]) { HonokaMiku::Dctx* cross_dctx = NULL; static const size_t chunk_size = 4096; // Edit if necessary + size_t version1_consideration = 0; unsigned char* byte_buffer; - if(g_XEncryptGame > 0) + try { - fprintf(stderr, "Cross-encrypt to: %s", gameid_to_string(g_XEncryptGame)); + byte_buffer = new uint8_t[chunk_size]; + } + catch(std::bad_alloc& ) + { + free(file_contents); + + goto not_enough_memory; + } + + if(dctx->version == 1 && g_Encrypt == false) + { + version1_consideration = 4; + + memcpy(byte_buffer, header_buffer, 4); + } + + if(g_XEncryptGame != 0xFFFFFFFF) + { + AssembleGameName(g_XEncryptGame, _reserved_memory + 512); + fprintf(stderr, "Cross-encrypt to: %s", _reserved_memory + 512); if(g_XEncryptGame == g_DecryptGame) { @@ -649,28 +643,14 @@ int main(int argc, char* argv[]) fputc('\n', stderr); - if(!g_Version1) - { - g_Encrypt = true; - cross_dctx = HonokaMiku::EncryptPrepare(g_XEncryptGame, g_Basename, header_buffer); - } - else - cross_dctx = new HonokaMiku::V1_Dctx(HonokaMiku::GetPrefixFromGameId(g_XEncryptGame), g_Basename); - + g_Encrypt = true; + cross_dctx = HonokaMiku::RequestEncrypter(g_XEncryptGame, g_Basename, header_buffer); } - try - { - byte_buffer = new unsigned char[chunk_size]; - } - catch(std::bad_alloc& ) + while(size_t read_bytes = fread(byte_buffer + version1_consideration, 1, chunk_size - version1_consideration, file_stream) + version1_consideration) { - free(file_contents); + version1_consideration = 0; - goto not_enough_memory; - } - while(size_t read_bytes = fread(byte_buffer, 1, chunk_size, file_stream)) - { for(size_t free_size = file_contents_size - file_contents_length; read_bytes > free_size; ) { if(read_bytes > free_size) @@ -727,11 +707,9 @@ int main(int argc, char* argv[]) // If we're encrypting, write header first if(g_Encrypt) { + header_size = HonokaMiku::GetHeaderSize(dctx->get_id()); - if(dctx->version == 3) - header_size = 16; - - if(fwrite(header_buffer, 1, header_size, file_stream) != header_size) + if(header_size > 0 && fwrite(header_buffer, 1, header_size, file_stream) != header_size) { write_error: diff --git a/src/JP_Decrypter.cc b/src/JP_Decrypter.cc index d06aaa6..be497f6 100644 --- a/src/JP_Decrypter.cc +++ b/src/JP_Decrypter.cc @@ -30,19 +30,39 @@ static const uint32_t jp_lng_key_tables[12] = { 0x00000018U, 0x00010101U, 0x00415927U, 0x00000008U, }; -HonokaMiku::JP3_Dctx::JP3_Dctx(const void* header, const char* filename): HonokaMiku::V3_Dctx(HonokaMiku::GetPrefixFromGameId(2), header, filename) {} +//////////////////// +// Version 2 code // +//////////////////// + +HonokaMiku::JP2_Dctx::JP2_Dctx(const void* header, const char* filename):V2_Dctx(GetPrefixFromGameId(2), header, filename) {} + +uint32_t HonokaMiku::JP2_Dctx::get_id() +{ + return HONOKAMIKU_GAMETYPE_JP | HONOKAMIKU_DECRYPT_V2; +} + +//////////////////// +// Version 3 code // +//////////////////// + +HonokaMiku::JP3_Dctx::JP3_Dctx(const void* header, const char* filename):V3_Dctx(GetPrefixFromGameId(2), header, filename) {} const uint32_t* HonokaMiku::JP3_Dctx::_getKeyTables() { return jp_key_tables; } const uint32_t* HonokaMiku::JP3_Dctx::_getLngKeyTables() { return jp_lng_key_tables; } -void HonokaMiku::JP3_Dctx::final_setup(const char* filename, const void* block_rest, int force_version) +uint32_t HonokaMiku::JP3_Dctx::get_id() +{ + return HONOKAMIKU_GAMETYPE_JP | (version << 16); +} + +void HonokaMiku::JP3_Dctx::final_setup(const char* filename, const void* block_rest, int32_t force_version) { finalDecryptV3(this, 500, filename, block_rest, force_version); } -HonokaMiku::JP3_Dctx* HonokaMiku::JP3_Dctx::encrypt_setup(const char* filename,void* hdr_out) +HonokaMiku::JP3_Dctx* HonokaMiku::JP3_Dctx::encrypt_setup(const char* filename,void* hdr_out, int32_t fv) { JP3_Dctx* dctx = new JP3_Dctx; - setupEncryptV3(dctx, HonokaMiku::GetPrefixFromGameId(2), 500, filename, hdr_out); + setupEncryptV3(dctx, HonokaMiku::GetPrefixFromGameId(2), 500, filename, hdr_out, fv); return dctx; } diff --git a/src/TW_Decrypter.cc b/src/TW_Decrypter.cc index 2ae2d2f..bac9eed 100644 --- a/src/TW_Decrypter.cc +++ b/src/TW_Decrypter.cc @@ -24,18 +24,38 @@ static const uint32_t tw_key_tables[64] = { 0x94D63581U, 0x49A7FAD6U, 0x7BEDDD15U, 0xC6913CEDU, }; +//////////////////// +// Version 2 code // +//////////////////// + +HonokaMiku::TW2_Dctx::TW2_Dctx(const void* header, const char* filename):V2_Dctx(HonokaMiku::GetPrefixFromGameId(3), header, filename) {} + +uint32_t HonokaMiku::TW2_Dctx::get_id() +{ + return HONOKAMIKU_GAMETYPE_TW | HONOKAMIKU_DECRYPT_V2; +} + +//////////////////// +// Version 3 code // +//////////////////// + HonokaMiku::TW3_Dctx::TW3_Dctx(const void* header, const char* filename): HonokaMiku::V3_Dctx(HonokaMiku::GetPrefixFromGameId(3), header, filename) {} const uint32_t* HonokaMiku::TW3_Dctx::_getKeyTables() { return tw_key_tables; } +uint32_t HonokaMiku::TW3_Dctx::get_id() +{ + return HONOKAMIKU_GAMETYPE_TW | HONOKAMIKU_DECRYPT_V3; +} + void HonokaMiku::TW3_Dctx::final_setup(const char* filename, const void* block_rest, int force_version) { finalDecryptV3(this, 1051, filename, block_rest, force_version); } -HonokaMiku::TW3_Dctx* HonokaMiku::TW3_Dctx::encrypt_setup(const char* filename,void* hdr_out) +HonokaMiku::TW3_Dctx* HonokaMiku::TW3_Dctx::encrypt_setup(const char* filename, void* hdr_out, int force_version) { TW3_Dctx* dctx = new TW3_Dctx; - setupEncryptV3(dctx, HonokaMiku::GetPrefixFromGameId(3), 1051, filename, hdr_out); + setupEncryptV3(dctx, HonokaMiku::GetPrefixFromGameId(3), 1051, filename, hdr_out, force_version); return dctx; } diff --git a/src/V1_Decrypter.cc b/src/V1_Decrypter.cc index b058a24..a651174 100644 --- a/src/V1_Decrypter.cc +++ b/src/V1_Decrypter.cc @@ -31,6 +31,24 @@ HonokaMiku::V1_Dctx::V1_Dctx(const char* prefix, const char* filename) (mctx.digestRaw[1] << 16) | (mctx.digestRaw[2] << 8) | (mctx.digestRaw[3]); + + if(strcmp(prefix, GetPrefixFromGameType(HONOKAMIKU_GAMETYPE_JP)) == 0) + game_ver = HONOKAMIKU_GAMETYPE_JP; + else if(strcmp(prefix, GetPrefixFromGameType(HONOKAMIKU_GAMETYPE_EN)) == 0) + game_ver = HONOKAMIKU_GAMETYPE_EN; + else if(strcmp(prefix, GetPrefixFromGameType(HONOKAMIKU_GAMETYPE_TW)) == 0) + game_ver = HONOKAMIKU_GAMETYPE_TW; + else if(strcmp(prefix, GetPrefixFromGameType(HONOKAMIKU_GAMETYPE_CN)) == 0) + game_ver = HONOKAMIKU_GAMETYPE_CN; + else + game_ver = 0xFFFF; + + version = 1; +} + +uint32_t HonokaMiku::V1_Dctx::get_id() +{ + return HONOKAMIKU_DECRYPT_V1 | game_ver; } void HonokaMiku::V1_Dctx::decrypt_block(void* b, uint32_t size) diff --git a/src/V3_Decrypter.cc b/src/V3_Decrypter.cc index 21160d2..64b3d2f 100644 --- a/src/V3_Decrypter.cc +++ b/src/V3_Decrypter.cc @@ -28,7 +28,7 @@ _jumpFunc(&jumpV3) mctx.Init(); mctx.Update(reinterpret_cast(prefix), strlen(prefix)); - mctx.Update((unsigned char*)basename, strlen(basename)); + mctx.Update(reinterpret_cast(basename), strlen(basename)); mctx.Final(); memcpy(digcopy, mctx.digestRaw + 4, 3); @@ -47,7 +47,7 @@ _jumpFunc(&jumpV3) version = 3; } -void HonokaMiku::finalDecryptV3(V3_Dctx* dctx, unsigned int expected_sum_name, const char* filename, const void* block_rest, int force_version) +void HonokaMiku::finalDecryptV3(V3_Dctx* dctx, unsigned int expected_sum_name, const char* filename, const void* block_rest, int32_t force_version) { // Already assumed that the first 4 bytes already processed above. if (!dctx->is_finalized) @@ -65,8 +65,7 @@ void HonokaMiku::finalDecryptV3(V3_Dctx* dctx, unsigned int expected_sum_name, c if (name_sum == expected_sum) { dctx->init_key = dctx->_getKeyTables()[name_sum & 0x3F]; - dctx->update_key = dctx->init_key; - dctx->xor_key = dctx->init_key >> 24; + dctx->xor_key = dctx->update_key = dctx->init_key; dctx->shift_val = 24; dctx->add_val = 2531011; dctx->mul_val = 214013; @@ -114,13 +113,13 @@ void HonokaMiku::finalDecryptV3(V3_Dctx* dctx, unsigned int expected_sum_name, c } } -void HonokaMiku::setupEncryptV3(HonokaMiku::V3_Dctx* dctx, const char* prefix, unsigned short name_sum_base, const char* filename, void* hdr_out) +void HonokaMiku::setupEncryptV3(HonokaMiku::V3_Dctx* dctx, const char* prefix, unsigned short name_sum_base, const char* filename, void* hdr_out, int32_t fv) { MD5 mctx; - const char* basename=__DctxGetBasename(filename); - uint8_t hdr_create[16]; + const char* basename = __DctxGetBasename(filename); + const uint32_t* lcg_ktbl = dctx->_getLngKeyTables(); + uint8_t* hdr_create = reinterpret_cast(hdr_out); uint8_t digcopy[3]; - uint16_t key_picker=name_sum_base; mctx.Init(); mctx.Update(reinterpret_cast(prefix), strlen(prefix)); @@ -129,31 +128,50 @@ void HonokaMiku::setupEncryptV3(HonokaMiku::V3_Dctx* dctx, const char* prefix, u memcpy(digcopy, mctx.digestRaw + 4, 3); memset(hdr_create, 0, 16); + hdr_create[3] = 12; digcopy[0] = ~digcopy[0]; digcopy[1] = ~digcopy[1]; digcopy[2] = ~digcopy[2]; + memcpy(hdr_create, &digcopy, 3); - hdr_create[3]=12; - - memcpy(hdr_create,&digcopy,3); - - for(;*basename != 0; key_picker += *basename, basename++) {} - - hdr_create[10] = key_picker >> 8; - hdr_create[11] = key_picker & 0xFF; + if(fv == 0 || fv == 3) + { + uint16_t key_picker = name_sum_base; - dctx->init_key = dctx->_getKeyTables()[hdr_create[11] & 0x3F]; - dctx->update_key = dctx->init_key; - dctx->xor_key = dctx->init_key >> 24; - dctx->pos = 0; - dctx->version = 3; - dctx->shift_val = 24; - dctx->add_val = 2531011; - dctx->mul_val = 214013; - dctx->is_finalized = true; + for(;*basename != 0; key_picker += *basename, basename++) {} - memcpy(hdr_out, hdr_create, 16); + hdr_create[10] = key_picker >> 8; + hdr_create[11] = key_picker & 0xFF; + + dctx->init_key = dctx->_getKeyTables()[hdr_create[11] & 0x3F]; + dctx->xor_key = dctx->update_key = dctx->init_key; + dctx->pos = 0; + dctx->version = 3; + dctx->shift_val = 24; + dctx->add_val = 2531011; + dctx->mul_val = 214013; + dctx->is_finalized = true; + } + else if(fv == 4 && lcg_ktbl != NULL) + { + hdr_create[4] = 0x2C; + hdr_create[7] = 2; + + dctx->init_key = (mctx.digestRaw[8] << 24) | + (mctx.digestRaw[9] << 16) | + (mctx.digestRaw[10] << 8) | + mctx.digestRaw[11]; + dctx->xor_key = dctx->update_key = dctx->init_key; + dctx->pos = 0; + dctx->version = 4; + dctx->mul_val = lcg_ktbl[0]; + dctx->shift_val = lcg_ktbl[2]; + dctx->add_val = lcg_ktbl[1]; + dctx->is_finalized = true; + } + else + throw std::runtime_error("No suitable or invalid encryption method."); } void HonokaMiku::V3_Dctx::decryptV3(V3_Dctx* dctx, void* b, uint32_t size) @@ -163,7 +181,7 @@ void HonokaMiku::V3_Dctx::decryptV3(V3_Dctx* dctx, void* b, uint32_t size) uint32_t i; for(i = dctx->xor_key; size; i = (dctx->update_key = dctx->mul_val * dctx->update_key + dctx->add_val), size--) - *buffer++ ^= i >> dctx->shift_val; + *buffer++ ^= uint8_t(i >> dctx->shift_val); dctx->xor_key = i; } @@ -219,7 +237,7 @@ void HonokaMiku::V3_Dctx::decrypt_block(void* _d, const void* _s, uint32_t size) memcpy(_d, _s, size); _decryptFunc(this, out_buffer, size); - pos+=size; + pos += size; return; }