commit c3673f67f4bc17e453aa188024946e31b7412e84 Author: Eren <66168591+Elpisdev@users.noreply.github.com> Date: Wed Nov 20 17:03:31 2024 +0300 xd diff --git a/unsegareborn.sln b/unsegareborn.sln new file mode 100644 index 0000000..e07aa4c --- /dev/null +++ b/unsegareborn.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33717.318 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "unsegareborn", "unsegareborn\unsegareborn.vcxproj", "{4B2C8A0E-0BE1-4ED1-8108-6124C58F3A46}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4B2C8A0E-0BE1-4ED1-8108-6124C58F3A46}.Debug|x64.ActiveCfg = Debug|x64 + {4B2C8A0E-0BE1-4ED1-8108-6124C58F3A46}.Debug|x64.Build.0 = Debug|x64 + {4B2C8A0E-0BE1-4ED1-8108-6124C58F3A46}.Debug|x86.ActiveCfg = Debug|Win32 + {4B2C8A0E-0BE1-4ED1-8108-6124C58F3A46}.Debug|x86.Build.0 = Debug|Win32 + {4B2C8A0E-0BE1-4ED1-8108-6124C58F3A46}.Release|x64.ActiveCfg = Release|x64 + {4B2C8A0E-0BE1-4ED1-8108-6124C58F3A46}.Release|x64.Build.0 = Release|x64 + {4B2C8A0E-0BE1-4ED1-8108-6124C58F3A46}.Release|x86.ActiveCfg = Release|Win32 + {4B2C8A0E-0BE1-4ED1-8108-6124C58F3A46}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {8F828A92-6320-4CD6-A10C-67E947970079} + EndGlobalSection +EndGlobal diff --git a/unsegareborn/bootid.c b/unsegareborn/bootid.c new file mode 100644 index 0000000..0abec04 --- /dev/null +++ b/unsegareborn/bootid.c @@ -0,0 +1,7 @@ +#include "bootid.h" +#include + +void format_timestamp(const Timestamp* ts, char* buffer, size_t buffer_size) { + snprintf(buffer, buffer_size, "%04d%02d%02d%02d%02d%02d", + ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second); +} \ No newline at end of file diff --git a/unsegareborn/bootid.h b/unsegareborn/bootid.h new file mode 100644 index 0000000..5bd875f --- /dev/null +++ b/unsegareborn/bootid.h @@ -0,0 +1,74 @@ +#ifndef BOOTID_H +#define BOOTID_H + +#include +#include + +static const uint8_t BOOTID_KEY[16] = { + 0x09, 0xCA, 0x5E, 0xFD, 0x30, 0xC9, 0xAA, 0xEF, + 0x38, 0x04, 0xD0, 0xA7, 0xE3, 0xFA, 0x71, 0x20 +}; + +static const uint8_t BOOTID_IV[16] = { + 0xB1, 0x55, 0xC2, 0x2C, 0x2E, 0x7F, 0x04, 0x91, + 0xFA, 0x7F, 0x0F, 0xDC, 0x21, 0x7A, 0xFF, 0x90 +}; + +enum ContainerType { + CONTAINER_TYPE_OS = 0x00, + CONTAINER_TYPE_APP = 0x01, + CONTAINER_TYPE_OPTION = 0x02 +}; + +#pragma pack(push, 1) + +typedef struct { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t unk1; +} Timestamp; + +typedef struct { + uint8_t release; + uint8_t minor; + uint16_t major; +} Version; + +typedef union { + Version version; + uint8_t option[4]; +} GameVersion; + +typedef struct { + uint32_t crc32; + uint32_t length; + uint8_t signature[4]; + uint8_t unk1; + uint8_t container_type; + uint8_t sequence_number; + bool use_custom_iv; + uint8_t game_id[4]; + Timestamp target_timestamp; + GameVersion target_version; + uint64_t block_count; + uint64_t block_size; + uint64_t header_block_count; + uint64_t unk2; + uint8_t os_id[3]; + uint8_t os_generation; + Timestamp source_timestamp; + Version source_version; + Version os_version; + uint8_t padding[8]; + uint8_t extra_padding[4]; +} BootId; + +#pragma pack(pop) + +void format_timestamp(const Timestamp* ts, char* buffer, size_t buffer_size); + +#endif // BOOTID_H \ No newline at end of file diff --git a/unsegareborn/crypto.c b/unsegareborn/crypto.c new file mode 100644 index 0000000..63abc58 --- /dev/null +++ b/unsegareborn/crypto.c @@ -0,0 +1,344 @@ +#include "crypto.h" +#include +#include +#include +#include +#include + +void calculate_page_iv(uint64_t file_offset, const uint8_t* file_iv, uint8_t* page_iv) { + for (int i = 0; i < 16; i++) { + page_iv[i] = file_iv[i] ^ ((file_offset >> (8 * (i % 8))) & 0xFF); + } +} + +bool calculate_file_iv( + const uint8_t key[16], + const uint8_t expected_header[16], + const uint8_t* first_page, + uint8_t out_iv[16] +) { + uint8_t iv[16]; + uint8_t header[16]; + memcpy(header, first_page, 16); + + calculate_page_iv(0, expected_header, iv); + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + return false; + + int len; + int plaintext_len; + + if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) { + EVP_CIPHER_CTX_free(ctx); + return false; + } + + EVP_CIPHER_CTX_set_padding(ctx, 0); + + uint8_t decrypted[16]; + + if (1 != EVP_DecryptUpdate(ctx, decrypted, &len, header, 16)) { + EVP_CIPHER_CTX_free(ctx); + return false; + } + plaintext_len = len; + + if (1 != EVP_DecryptFinal_ex(ctx, decrypted + len, &len)) { + EVP_CIPHER_CTX_free(ctx); + return false; + } + plaintext_len += len; + + EVP_CIPHER_CTX_free(ctx); + + if (plaintext_len != 16) + return false; + + memcpy(out_iv, decrypted, 16); + + return true; +} + +typedef struct { + const char* game_id; + uint8_t key[16]; + uint8_t iv[16]; + bool has_iv; +} GameKeyEntry; + +// game keys array +static const GameKeyEntry game_keys[] = { + // Nu Firmware / Hardware Test + {"SBZS", {0x2e, 0xcb, 0xcf, 0xf6, 0x5c, 0xe0, 0xab, 0xec, 0xc1, 0x05, 0x47, 0xf8, 0xac, 0x83, 0x51, 0xd8}, {0xf2, 0xac, 0x6c, 0x28, 0x17, 0xd0, 0x57, 0x4b, 0xba, 0x11, 0x3d, 0x49, 0x7e, 0x31, 0x9f, 0x3e}, true}, + + // KEY CHIP NU FACTORY / KEY CHIP NUSX FACTORY + {"SBZT", {0x9a, 0xb9, 0xce, 0x55, 0xed, 0x9c, 0x19, 0x4a, 0x71, 0x5a, 0x73, 0xa7, 0x69, 0x9f, 0x79, 0x5b}, {0x85, 0x52, 0xde, 0x88, 0xfe, 0xdd, 0xa6, 0xe8, 0x59, 0x36, 0x9f, 0xb0, 0x00, 0xf4, 0x4d, 0x5b}, true}, + + // Nu Firmware / Hardware Test + {"SBZU", {0xeb, 0x12, 0x28, 0x25, 0x4c, 0xdd, 0x30, 0x77, 0xeb, 0x3e, 0x44, 0x1c, 0x02, 0x27, 0xbf, 0x40}, {0x3f, 0x9b, 0x46, 0x76, 0x11, 0x8c, 0xee, 0x12, 0x9f, 0xe2, 0xf1, 0xcb, 0x27, 0x47, 0xbc, 0xa5}, true}, + + // Project DIVA Arcade Future Tone + {"SBZV", {0x32, 0x74, 0xa3, 0x99, 0x59, 0x4d, 0x84, 0x77, 0x96, 0x25, 0x94, 0x0b, 0x69, 0xc0, 0x2d, 0x3f}, {0x67, 0x5b, 0xa6, 0x6d, 0x29, 0xc8, 0x79, 0x23, 0xf5, 0xf1, 0x54, 0xc4, 0x06, 0xaf, 0xee, 0x42}, true}, + + // Wonderland Wars + {"SDAP", {0x41, 0xb5, 0x02, 0x7c, 0x5e, 0x99, 0xd9, 0x4a, 0xa9, 0x33, 0x5d, 0x6d, 0x71, 0x83, 0x8e, 0xcf}, {0x41, 0xb5, 0x02, 0x7c, 0x5e, 0x99, 0xd9, 0x4a, 0xa9, 0x33, 0x5d, 0x6d, 0x71, 0x83, 0x8e, 0xcf}, true}, + + // Herobank Arcade + {"SDAQ", {0xc2, 0x8f, 0x22, 0xbc, 0x1b, 0x33, 0x9a, 0xe6, 0x41, 0x80, 0x73, 0x98, 0x86, 0xdc, 0x83, 0xd6}, {0x0a, 0x29, 0xfd, 0x14, 0x5d, 0x72, 0xbf, 0x8d, 0xed, 0xd4, 0x36, 0x02, 0x5d, 0xf0, 0xa9, 0xfc}, true}, + + // Uranai Collection: Torotte + {"SDAV", {0xee, 0xd9, 0x55, 0x13, 0x26, 0x6a, 0x49, 0x9a, 0x55, 0xe2, 0x65, 0xb0, 0x49, 0x16, 0x9c, 0x44}, {0x84, 0xc0, 0xe5, 0x93, 0x1d, 0x91, 0xa6, 0xa4, 0x77, 0xd6, 0x2c, 0x27, 0x15, 0x46, 0x05, 0x6e}, true}, + + // Shin Kouchuu Ouja Mushiking + {"SDBE", {0x70, 0x53, 0xfb, 0x94, 0x45, 0x72, 0xe5, 0xb6, 0x31, 0xa6, 0x65, 0xce, 0xf4, 0xb5, 0xbc, 0xdd}, {0xae, 0x4d, 0x7e, 0x88, 0x40, 0x02, 0xc7, 0x9e, 0xb3, 0x57, 0x11, 0x55, 0x4d, 0x61, 0x30, 0x57}, true}, + + // E-DEL Sand + {"SDBN", {0xc1, 0xf1, 0x4a, 0xe2, 0xe8, 0x5b, 0x09, 0x5e, 0x31, 0x3c, 0x8b, 0xae, 0xc1, 0x25, 0x80, 0x5e}, {0x3c, 0x53, 0x8e, 0xea, 0x66, 0x25, 0x1a, 0xcd, 0x54, 0x04, 0xb9, 0x3f, 0x89, 0x76, 0xa7, 0xf7}, true}, + + // CHUNITHM + {"SDBT", {0xa6, 0xa8, 0x70, 0x67, 0x1f, 0xd4, 0x32, 0xec, 0x63, 0x7a, 0xdf, 0x7a, 0x82, 0x2f, 0x97, 0xda}, {0x2c, 0x27, 0x7f, 0x31, 0xcd, 0x55, 0x0c, 0xfa, 0x2c, 0x99, 0x3b, 0x4d, 0xd5, 0x6b, 0x85, 0xae}, true}, + + // Sonic Dash Extreme + {"SDBX", {0x3d, 0xc1, 0x9c, 0x2d, 0x0c, 0x20, 0xac, 0x19, 0x9d, 0x5f, 0xa4, 0x6e, 0x7f, 0x63, 0x35, 0xa6}, {0xd8, 0xf0, 0x29, 0xec, 0x90, 0xfe, 0x55, 0xbe, 0x67, 0x58, 0x4f, 0x74, 0x2c, 0x55, 0xef, 0x8b}, true}, + + // Kancolle Arcade + {"SDBZ", {0x52, 0x1b, 0xde, 0x44, 0x60, 0xf4, 0x18, 0x4e, 0xdd, 0x87, 0x91, 0x36, 0xad, 0xee, 0xa5, 0xee}, {0x1b, 0x83, 0x24, 0x03, 0x2d, 0xb6, 0x9d, 0x7b, 0x09, 0x54, 0x79, 0x4a, 0xa2, 0x29, 0xfe, 0x68}, true}, + + // crossbeats REV. + {"SDCA", {0x16, 0x49, 0x49, 0x0a, 0x03, 0xd6, 0xc2, 0xae, 0xc1, 0xc4, 0x96, 0x98, 0x2c, 0xb0, 0x40, 0x5c}, {0x46, 0x80, 0x71, 0x1c, 0x7e, 0x67, 0xa2, 0x6f, 0x92, 0x30, 0xd5, 0xaf, 0x74, 0xb5, 0xdc, 0xfb}, true}, + + // nailpuri + {"SDCD", {0x43, 0xb3, 0x85, 0x02, 0xd8, 0xf6, 0xd3, 0xc7, 0xb0, 0x2b, 0x95, 0xfc, 0x28, 0xdb, 0x53, 0x08}, {0x6d, 0xfc, 0xb9, 0x4b, 0xf7, 0x4f, 0x15, 0x2b, 0x55, 0xf3, 0xe0, 0xc7, 0xf3, 0x5b, 0x44, 0xb5}, true}, + + // Luigi's Mansion Arcade + {"SDCF", {0xdf, 0x98, 0x68, 0x83, 0xda, 0x83, 0x75, 0x38, 0xe3, 0x7b, 0x95, 0x9a, 0x3e, 0x41, 0x17, 0xcd}, {0xda, 0xbf, 0x53, 0x97, 0x38, 0x85, 0x2f, 0x17, 0x71, 0x48, 0x11, 0xaf, 0x70, 0x43, 0x5a, 0x83}, true}, + + // Mario & Sonic at Rio Olympic Games + {"SDCH", {0xe2, 0xda, 0x76, 0x9e, 0x94, 0xf1, 0xd3, 0xac, 0xa1, 0x93, 0x0c, 0xdb, 0xe0, 0x70, 0x8c, 0x9f}, {0xc7, 0xdc, 0xce, 0x20, 0x3c, 0x84, 0xab, 0x04, 0x77, 0x23, 0x6d, 0x69, 0x75, 0x70, 0xda, 0xdc}, true}, + + // KEY CHIP NUSX EDB SOC + {"SDCR", {0x49, 0x61, 0xa5, 0x1f, 0xd3, 0x6f, 0x14, 0xe7, 0x26, 0x64, 0xf5, 0x23, 0x73, 0x05, 0x21, 0x60}, {0x25, 0xd7, 0xd1, 0x34, 0x1a, 0x28, 0x2c, 0x5e, 0x0a, 0x34, 0xc6, 0x45, 0x62, 0xc0, 0x23, 0xec}, true}, + + // Celevie + {"SDCT", {0xd6, 0xae, 0x51, 0xf1, 0x0e, 0xc7, 0x6d, 0xa9, 0x3c, 0x98, 0x18, 0x00, 0xfc, 0x3a, 0xd3, 0xcb}, {0xfb, 0x8e, 0x43, 0xe2, 0x80, 0xd3, 0x30, 0xd0, 0x65, 0x81, 0x73, 0x2f, 0x2e, 0x11, 0xa6, 0xdc}, true}, + + // CYTUS Ω + {"SDCX", {0x79, 0x50, 0x4c, 0xcc, 0x50, 0x9b, 0x67, 0xd1, 0xf7, 0xa3, 0xf5, 0x93, 0xe6, 0xf9, 0xd9, 0xd6}, {0x15, 0x51, 0xea, 0x89, 0x26, 0xf2, 0xae, 0xe2, 0x33, 0xee, 0xc3, 0x09, 0xde, 0x3e, 0x5f, 0x3c}, true}, + + // KEY CHIP NUSX1.1 TDW + {"SDDB", {0x87, 0x56, 0x79, 0xb2, 0xcd, 0x16, 0x37, 0x96, 0x2b, 0x0d, 0xb2, 0x5c, 0x51, 0xfb, 0x21, 0xa6}, {0x8e, 0xf4, 0x47, 0x22, 0xa0, 0x56, 0x6e, 0x8f, 0x57, 0x23, 0x56, 0x24, 0x56, 0x87, 0xfb, 0xe5}, true}, + + // Sangokushi Taisen + {"SDDD", {0x56, 0x4e, 0x96, 0x78, 0x73, 0xde, 0x6c, 0xbc, 0xd2, 0x2e, 0xfe, 0xca, 0x69, 0x52, 0xe9, 0xdc}, {0x4e, 0x3d, 0xd4, 0x65, 0xcf, 0x09, 0xcd, 0x82, 0xb2, 0x59, 0xf7, 0xbe, 0xd5, 0xfc, 0x2d, 0x6d}, true}, + + // Initial D Arcade Stage Zero + {"SDDF", {0x65, 0x05, 0x85, 0x73, 0xa0, 0xcb, 0x81, 0x74, 0x9e, 0x69, 0x4a, 0xe1, 0x64, 0xc6, 0x1b, 0x04}, {0x98, 0x1c, 0x4f, 0x45, 0xe3, 0xc6, 0x95, 0x8f, 0x05, 0x4e, 0x5d, 0x00, 0x91, 0x6b, 0xdf, 0x2b}, true}, + + // KEY CHIP NU1.1 ESC + {"SDDJ", {0x63, 0x0f, 0xe5, 0x22, 0x76, 0x53, 0x7b, 0xd7, 0xfb, 0x26, 0x7a, 0xdf, 0x17, 0x5f, 0x4e, 0x99}, {0xdc, 0x57, 0x55, 0xbe, 0x57, 0xde, 0xd2, 0xcd, 0xb3, 0x44, 0x33, 0xbb, 0xba, 0x22, 0x04, 0xff}, true}, + + // APM3 Sample Program 2 + {"SDDL", {0x99, 0x24, 0x58, 0x29, 0x5f, 0xd0, 0x6d, 0x6a, 0x8a, 0xf0, 0xdf, 0xb3, 0xf6, 0x85, 0x4c, 0x19}, {0x84, 0x84, 0x90, 0x6d, 0x4c, 0xd5, 0xfd, 0x22, 0x5e, 0x03, 0x28, 0x43, 0xed, 0x37, 0x49, 0x5d}, true}, + + // ALLS MX Factory Dummy + {"SDDM", {0x01, 0x27, 0x95, 0x82, 0x10, 0xf6, 0xae, 0x9b, 0xde, 0xb8, 0x97, 0x50, 0x18, 0xb5, 0xaf, 0x24}, {0x18, 0x17, 0x16, 0xba, 0xdc, 0xcf, 0xf4, 0xbc, 0x2b, 0x1e, 0x29, 0xae, 0x02, 0xa1, 0xbb, 0xbb}, true}, + + // Demo ID + {"SDDN", {0x41, 0xdd, 0x8e, 0x66, 0x29, 0x01, 0x17, 0xac, 0x67, 0xd3, 0x11, 0xa2, 0xf0, 0xa6, 0x41, 0x6e}, {0x73, 0xe1, 0x8e, 0x84, 0x18, 0xf6, 0xce, 0xef, 0xb1, 0x1e, 0x27, 0x67, 0xfd, 0xea, 0x19, 0x0c}, true}, + + // Soul Reverse + {"SDDP", {0xcf, 0x6d, 0x64, 0x42, 0x7e, 0xec, 0xa4, 0x76, 0x74, 0xe1, 0x7b, 0xcd, 0x46, 0xd1, 0xea, 0x8c}, {0xce, 0x51, 0x74, 0x09, 0x3d, 0x26, 0xca, 0x2a, 0x31, 0xb5, 0x85, 0x41, 0xe8, 0x5a, 0xc2, 0x76}, true}, + + // SEGA World Driver Championship + {"SDDS", {0x16, 0x1b, 0xec, 0x6d, 0x90, 0x98, 0x9d, 0x0e, 0x26, 0xd7, 0x91, 0x17, 0x06, 0x07, 0xa4, 0x40}, {0x81, 0xdc, 0x26, 0xa2, 0x70, 0x28, 0xe2, 0x09, 0x23, 0x32, 0x03, 0x8a, 0xa1, 0xbf, 0xfc, 0x47}, true}, + + // O.N.G.E.K.I. + {"SDDT", {0x3f, 0x76, 0x58, 0x72, 0x8b, 0x95, 0x17, 0xd3, 0x31, 0x4e, 0x68, 0x4f, 0xa2, 0xe2, 0xa0, 0x45}, {0x41, 0x57, 0x88, 0x33, 0xc5, 0x47, 0xaa, 0xff, 0x04, 0xdb, 0x59, 0x7a, 0x6e, 0x9e, 0xb7, 0x84}, true}, + + // Let's dzuri GO! + {"SDDU", {0x64, 0x9a, 0xe9, 0x98, 0x26, 0x25, 0xf9, 0x0c, 0x55, 0xaf, 0x86, 0x71, 0x3c, 0x55, 0xd3, 0xfd}, {0x18, 0x71, 0x16, 0xfc, 0x46, 0x47, 0xa7, 0xd3, 0xb6, 0xf2, 0x30, 0x3a, 0x34, 0xf0, 0xa2, 0xfe}, true}, + + // ALLS X / X2 Research & Development + {"SDDW", {0x11, 0x85, 0x65, 0xd3, 0x44, 0xf3, 0xe1, 0x4c, 0xa6, 0x92, 0x99, 0xee, 0xac, 0x04, 0x9b, 0xb9}, {0x9d, 0x6d, 0x39, 0x2e, 0xc3, 0x5e, 0xd9, 0x4e, 0xf9, 0xfe, 0x0a, 0x5b, 0xe0, 0x57, 0x39, 0x81}, true}, + + // KEY CHIP ALLS X FACTORY + {"SDDX", {0x42, 0x8b, 0xff, 0x0f, 0x9e, 0x7a, 0xaf, 0xc1, 0x69, 0xa7, 0xa7, 0x57, 0x51, 0xff, 0xda, 0x98}, {0xf8, 0x25, 0x05, 0x94, 0xf4, 0x25, 0x33, 0x2c, 0x6d, 0x34, 0x9d, 0x7e, 0xa0, 0xe8, 0x66, 0x69}, true}, + + // Shin Kouchuu Ouja Mushiking TWN + {"SDEA", {0x9f, 0x9c, 0xf1, 0x48, 0xac, 0x3c, 0x50, 0xaa, 0xf9, 0x25, 0xaf, 0x1d, 0xfb, 0x27, 0xf5, 0x8b}, {0x4d, 0x8e, 0xbb, 0xd9, 0x71, 0x89, 0x6b, 0x8a, 0x4a, 0x3d, 0xd8, 0x4a, 0x23, 0xb3, 0x29, 0xfc}, true}, + + // WCCF FOOTISTA + {"SDEB", {0xd5, 0x11, 0xed, 0x69, 0x04, 0x15, 0xf6, 0x35, 0x98, 0x43, 0xa1, 0x34, 0xfd, 0x47, 0x83, 0x6a}, {0xac, 0x13, 0x9b, 0x38, 0x2a, 0xcd, 0xd1, 0x12, 0xe3, 0x15, 0x64, 0xea, 0x7f, 0x38, 0x18, 0x6c}, true}, + + // Chrono Regalia + {"SDEC", {0xf2, 0x72, 0xe5, 0x01, 0x68, 0x63, 0xaf, 0x2b, 0xa0, 0x33, 0x7f, 0x50, 0xde, 0x68, 0x6f, 0x6e}, {0x53, 0x27, 0xe1, 0x32, 0x63, 0x1e, 0x7f, 0x71, 0xb6, 0x1b, 0xe7, 0xcc, 0x0d, 0xf3, 0x82, 0xce}, true}, + + // CARD MAKER + {"SDED", {0x21, 0xfc, 0xec, 0x77, 0x9a, 0x16, 0x76, 0x9f, 0x52, 0x77, 0xa3, 0x6f, 0xb5, 0x42, 0x99, 0x2c}, {0x22, 0xb5, 0x02, 0x39, 0xf1, 0xb4, 0x0c, 0xcc, 0x3e, 0x55, 0xa2, 0xd6, 0x9c, 0x69, 0xb1, 0x60}, true}, + + // House of the Dead: Scarlet Dawn + {"SDEE", {0x19, 0x1e, 0xb7, 0x44, 0x06, 0x72, 0xda, 0xb0, 0x8d, 0xdb, 0xb7, 0x19, 0x5e, 0xfb, 0x35, 0x6f}, {0xc2, 0x78, 0xb5, 0x38, 0x6d, 0xc3, 0x8b, 0xd7, 0x6d, 0x71, 0xdb, 0xcd, 0x82, 0x69, 0x54, 0xcf}, true}, + + // FiZ + { "SDEG", {0x72, 0x18, 0x53, 0xdb, 0xe2, 0xd3, 0x0b, 0xaf, 0xe2, 0x4f, 0x0e, 0xdb, 0xd2, 0x10, 0xde, 0xeb}, {0x4d, 0xfb, 0x0b, 0xce, 0xc8, 0x61, 0x59, 0xaa, 0xb2, 0x97, 0x16, 0x6b, 0xcd, 0x50, 0x9e, 0x6f}, true }, + + // Fate/Grand Order Arcade + { "SDEJ", {0x9d, 0xe1, 0xea, 0x6a, 0xe3, 0x8d, 0x90, 0x11, 0xf5, 0x5d, 0x8e, 0xe8, 0x64, 0x39, 0x5d, 0x24}, {0xf6, 0x0c, 0xde, 0x21, 0x98, 0x28, 0x76, 0xd1, 0x2d, 0x17, 0x66, 0x2a, 0x48, 0xd9, 0x08, 0x36}, true }, + + // ALL.Net P.ras multi Ver.3 + { "SDEM", {0x70, 0x06, 0x17, 0xf2, 0x93, 0x69, 0x6c, 0x07, 0xfb, 0x9f, 0x35, 0x6d, 0x3b, 0x99, 0x24, 0x0d}, {0x66, 0x7d, 0x02, 0x6d, 0x6c, 0xdf, 0x32, 0x9f, 0xf3, 0x51, 0xdb, 0xaf, 0x70, 0x98, 0xe8, 0x1d}, true }, + + // StarHorse4 (Server) / MESTA Medal Station + { "SDEP", {0xfa, 0x2b, 0x7c, 0xa5, 0x3a, 0x82, 0x3c, 0x15, 0x2d, 0x94, 0x09, 0x72, 0xcb, 0xf5, 0x32, 0xf5}, {0xf4, 0xaf, 0x35, 0x12, 0x0c, 0x48, 0x61, 0x77, 0x04, 0xbb, 0x5b, 0x84, 0x71, 0x79, 0x7a, 0x62}, true }, + + // Initial D Arcade Stage Zero (CHN) + { "SDER", {0x7d, 0x73, 0x36, 0x7e, 0xbb, 0x21, 0x8e, 0xc8, 0x29, 0x30, 0xd5, 0x8d, 0xc6, 0xd7, 0x95, 0x0b}, {0x97, 0x88, 0xc3, 0xec, 0xa2, 0xdb, 0x6b, 0xa9, 0x2b, 0xac, 0x4f, 0x6f, 0x7b, 0x70, 0x63, 0x08}, true }, + + // House of the Dead: Scarlet Dawn (EXP) + { "SDET", {0x46, 0x43, 0xe7, 0xb2, 0xc3, 0x00, 0x6e, 0x02, 0x64, 0x16, 0x3e, 0xdc, 0x85, 0x45, 0xfb, 0x72}, {0x61, 0x2b, 0xca, 0x81, 0xea, 0x29, 0x58, 0xff, 0xba, 0xc3, 0x6f, 0x78, 0x0f, 0x1e, 0xd6, 0x88}, true }, + + // KEY CHIP ALLS X HDZ + { "SDEU", {0x23, 0xb3, 0xe9, 0xbb, 0x47, 0xe3, 0xac, 0x99, 0x98, 0xf6, 0xe6, 0xc1, 0xad, 0xc4, 0xae, 0x33}, {0xa9, 0x64, 0x71, 0x4c, 0xea, 0x60, 0x68, 0x84, 0x07, 0xbf, 0x55, 0x4b, 0xd1, 0xc2, 0x7e, 0xc2}, true }, + + // House of the Dead: Scarlet Dawn (CHN) + { "SDEV", {0x3c, 0x1f, 0x01, 0x8d, 0x88, 0x92, 0x6d, 0x98, 0x16, 0x3b, 0x07, 0xa1, 0x56, 0x3a, 0x48, 0x18}, {0xca, 0x73, 0x73, 0xc9, 0xc7, 0xdf, 0xeb, 0xac, 0x0f, 0xc2, 0x42, 0x54, 0xc0, 0x30, 0xe4, 0xad}, true }, + + // maimai DX + { "SDEZ", {0xd1, 0x36, 0xeb, 0xa0, 0x5d, 0x40, 0xe8, 0x26, 0x82, 0xe6, 0xaa, 0xd8, 0xd9, 0xe8, 0x68, 0x8c}, {0xc4, 0x84, 0xde, 0xea, 0xa0, 0x24, 0x9e, 0xf4, 0x66, 0x95, 0xf6, 0x36, 0x94, 0xb7, 0x37, 0x2f}, true }, + + // KEY CHIP ALLS X REC + { "SDFA", {0x8e, 0x81, 0x6b, 0x43, 0x62, 0xdb, 0x24, 0xa2, 0x30, 0x87, 0x78, 0x85, 0x86, 0x4d, 0x20, 0x6d}, {0x8e, 0x5a, 0x0b, 0xa6, 0xa0, 0xa1, 0x15, 0x0d, 0x47, 0xd1, 0x2b, 0xdb, 0x64, 0xde, 0xbb, 0xa7}, true }, + + // WACCA + { "SDFE", {0xf6, 0x17, 0x19, 0xc3, 0x71, 0xe5, 0xbc, 0xa6, 0x78, 0x8c, 0x13, 0x9a, 0x53, 0x09, 0x16, 0x17}, {0x67, 0xd4, 0x31, 0x73, 0xe3, 0x43, 0x81, 0x3f, 0xa2, 0x09, 0x7f, 0xd3, 0x29, 0x92, 0xa8, 0xe2}, true }, + + // SANDRA + { "SDFG", {0x33, 0x98, 0xfb, 0x86, 0xbf, 0xe6, 0x30, 0xa1, 0x49, 0x79, 0x41, 0x18, 0x79, 0x86, 0x1a, 0xc7}, {0xa7, 0x94, 0xc4, 0x9c, 0x2c, 0x76, 0x39, 0xcd, 0x80, 0x57, 0x18, 0x07, 0xc1, 0x72, 0x46, 0xff}, true }, + + // Kemono Friends 3: Planet Tours + { "SDFL", {0x24, 0x49, 0xb4, 0x80, 0x67, 0xb9, 0x17, 0x6a, 0x6e, 0x0f, 0x95, 0x63, 0x48, 0x1e, 0x97, 0xf4}, {0x61, 0x6f, 0x87, 0x10, 0x45, 0x46, 0x32, 0xeb, 0x4f, 0xb1, 0xd8, 0x9d, 0x8c, 0x19, 0xc1, 0x9a}, true }, + + // KEY CHIP ALLS X CASJ + { "SDFN", {0x29, 0xf6, 0x2e, 0x22, 0xc6, 0xa9, 0xfd, 0x8b, 0xe3, 0x27, 0x63, 0x1c, 0x68, 0x54, 0x64, 0x05}, {0x2a, 0x86, 0x09, 0x76, 0xe6, 0xd9, 0x85, 0x13, 0x82, 0x5f, 0x29, 0x1e, 0x56, 0xcf, 0xb5, 0xee}, true }, + + // KEY CHIP NUSX FUTURE + { "SDFP", {0x57, 0x0b, 0x87, 0x26, 0x3a, 0x7c, 0xa0, 0xaa, 0x4c, 0x13, 0x88, 0xe2, 0x04, 0xee, 0x6d, 0x4b}, {0x76, 0x40, 0x88, 0x60, 0x11, 0xa2, 0x30, 0x0a, 0x91, 0xfa, 0xd9, 0xf3, 0x6a, 0x8c, 0x47, 0x75}, true }, + + // StarHorse4 + { "SDFT", {0x92, 0xa2, 0x5f, 0x38, 0x8c, 0x50, 0x73, 0x7e, 0x39, 0xc3, 0xc2, 0xf0, 0x06, 0x64, 0x5f, 0x31}, {0xa9, 0x7e, 0x72, 0xf9, 0x90, 0x41, 0x74, 0x88, 0xcb, 0x4c, 0x67, 0xf8, 0xf0, 0xc3, 0xfb, 0x25}, true }, + + // Mario & Sonic at TOKYO Olympic + { "SDFV", {0xfe, 0x82, 0xdb, 0x9a, 0x60, 0x29, 0x5d, 0x82, 0x9b, 0x95, 0xf0, 0x3c, 0x22, 0x76, 0x01, 0x8b}, {0x34, 0xd8, 0x27, 0x72, 0xae, 0x18, 0x17, 0x4f, 0x0a, 0x18, 0x1d, 0xc5, 0x33, 0x99, 0xea, 0x9c}, true }, + + // maimai DX (EXP) + { "SDGA", {0x0a, 0x66, 0x10, 0xa6, 0x2e, 0xf6, 0x70, 0xc6, 0x5b, 0x7e, 0x7b, 0x17, 0x50, 0xff, 0xb7, 0xa1}, {0x17, 0xa2, 0xa2, 0x29, 0x15, 0xf8, 0x1c, 0x58, 0x96, 0xed, 0xbb, 0xa4, 0xc4, 0x12, 0x58, 0x5e}, true }, + + // maimai DX (CHN) + { "SDGB", {0x7c, 0xa4, 0xe6, 0xb6, 0xf3, 0xd6, 0xe8, 0xb2, 0x64, 0x72, 0x97, 0x38, 0x87, 0xd7, 0xfa, 0x3a}, {0x53, 0xfe, 0x71, 0x35, 0x76, 0x2d, 0xe3, 0xf9, 0x7e, 0x7f, 0xe7, 0x6b, 0x0f, 0xef, 0x3f, 0x27}, true }, + + // Puyo Puyo e-Sports Arcade + { "SDGH", {0xb3, 0xe3, 0x0e, 0x7e, 0xab, 0xac, 0x37, 0x67, 0xad, 0xe1, 0x3c, 0x69, 0xc9, 0xb2, 0xf2, 0x2b}, {0x03, 0xde, 0xae, 0xa3, 0x74, 0x2d, 0x69, 0x67, 0x5b, 0x36, 0xcd, 0xdc, 0x8b, 0x15, 0xac, 0x91}, true }, + + // WCCF FOOTISTA (EXP) + { "SDGK", {0x9d, 0xc4, 0xa1, 0x7f, 0xc3, 0x9f, 0xca, 0x5a, 0x8a, 0x35, 0x89, 0x84, 0x80, 0x1c, 0xaa, 0xa7}, {0xe0, 0x44, 0x5b, 0x11, 0xdc, 0xfa, 0x0d, 0xae, 0x56, 0xc8, 0x5e, 0x87, 0x87, 0xe1, 0x1d, 0x9b}, true }, + + // KEY CHIP ALLS X HDZ CASJ + { "SDGP", {0xc8, 0x7a, 0xb3, 0x12, 0x47, 0xe7, 0xb6, 0xff, 0x95, 0xfd, 0xd7, 0x9f, 0xb9, 0x1f, 0x9f, 0x37}, {0x24, 0x67, 0xab, 0x3c, 0x03, 0x1e, 0x3d, 0xc0, 0x56, 0x8b, 0x70, 0x77, 0xef, 0xd2, 0x7c, 0x36}, true }, + + // ROKUMEN + { "SDGQ", {0xc5, 0x35, 0x6d, 0xae, 0x7b, 0x06, 0x6b, 0xce, 0x88, 0x98, 0x4a, 0xec, 0x36, 0xde, 0xb6, 0x2d}, {0x4e, 0x9f, 0x29, 0x82, 0x46, 0x0e, 0x2f, 0xd9, 0x07, 0xbd, 0xe1, 0x57, 0x09, 0xed, 0xfb, 0xa7}, true }, + + // CHUNITHM (EXP) + { "SDGS", {0xa5, 0x15, 0x0c, 0xc5, 0x06, 0x5d, 0x2c, 0x59, 0xee, 0x2f, 0x8f, 0x33, 0x2c, 0xbd, 0x29, 0xd5}, {0x84, 0x01, 0x4d, 0x26, 0x69, 0x6f, 0x29, 0x0a, 0xd7, 0xea, 0xd7, 0x0c, 0x75, 0x49, 0xbd, 0x81}, true }, + + // Initial D THE ARCADE + { "SDGT", {0x9d, 0x0b, 0xba, 0x20, 0xd1, 0xe8, 0x4f, 0x24, 0x59, 0x39, 0x9f, 0x53, 0x83, 0xbe, 0xee, 0x72}, {0x5d, 0x34, 0x00, 0x13, 0xfd, 0xfb, 0x24, 0x64, 0xd2, 0x53, 0x09, 0x36, 0x02, 0xfe, 0x4b, 0x64}, true }, + + // Pokemon COROGARENA + { "SDGV", {0x57, 0x3f, 0x5c, 0x8c, 0xc4, 0x4f, 0x10, 0xf3, 0x1e, 0xc7, 0x49, 0xb6, 0x95, 0xeb, 0xe8, 0x86}, {0x6b, 0xfc, 0xa8, 0x6f, 0x9d, 0x20, 0x8a, 0x79, 0x44, 0xcd, 0xfc, 0x25, 0xea, 0x3c, 0xd2, 0x20}, true }, + + // Eiketsu Taisen: Sanzensekai no Hadou + { "SDGY", {0xc0, 0x4b, 0x66, 0x3a, 0x59, 0x05, 0x5a, 0xcb, 0xdf, 0xeb, 0xc6, 0xd3, 0xdf, 0x0e, 0x6a, 0x04}, {0x76, 0xfc, 0x5f, 0x1d, 0x88, 0x60, 0x51, 0x07, 0x94, 0x7d, 0x0c, 0x1f, 0xf3, 0x47, 0x02, 0x2d}, true }, + + // Hori a Tale + { "SDGZ", {0x9a, 0xd7, 0x4e, 0xfb, 0x20, 0x8d, 0x6e, 0xe4, 0xfe, 0x5e, 0xe7, 0x70, 0x33, 0x17, 0x12, 0xcf}, {0x45, 0xf6, 0xe5, 0x3f, 0x0e, 0xb8, 0xfa, 0xe6, 0x66, 0x5b, 0x45, 0x44, 0x4a, 0x61, 0xe2, 0x66}, true }, + + // CHUNITHM NEW!! + { "SDHD", {0x3a, 0xbd, 0x00, 0xd7, 0xa8, 0x20, 0xce, 0x86, 0x2e, 0xaf, 0x47, 0x4b, 0xf6, 0xc8, 0xf3, 0x3e}, {0x0f, 0x1e, 0x7e, 0xea, 0x78, 0xda, 0x7e, 0x03, 0x7e, 0x05, 0x52, 0xc2, 0x84, 0x3e, 0x1b, 0x6a}, true }, + + // Gutsdzuri GO! + { "SDHH", {0xfc, 0x6f, 0x88, 0x7f, 0x37, 0x17, 0xc5, 0xd6, 0x71, 0x31, 0x13, 0xb9, 0x2f, 0xa3, 0xfb, 0x27}, {0xad, 0x76, 0x60, 0x64, 0x60, 0xdb, 0xe1, 0xe9, 0x1e, 0x41, 0xbe, 0xf7, 0xab, 0x0c, 0x15, 0x35}, true }, + + // CHUNITHM (CHN) + { "SDHJ", {0x98, 0x5e, 0xa6, 0x6e, 0xcb, 0x5b, 0x1f, 0x20, 0x8c, 0x90, 0xe2, 0xb8, 0x98, 0xf0, 0xb0, 0x73}, {0x16, 0x4a, 0x65, 0x42, 0x2e, 0x7f, 0x01, 0xb7, 0xf1, 0xb0, 0x84, 0x9f, 0xc7, 0x73, 0x7c, 0xdb}, true }, + + // UFO CATCHER LINK STATION + { "SDHK", {0xbc, 0x92, 0xd6, 0x3c, 0x2a, 0x09, 0x9c, 0xa2, 0x31, 0x5a, 0x48, 0x3c, 0x30, 0x41, 0xfd, 0xd7}, {0xb1, 0x4d, 0x84, 0x49, 0xb6, 0xd4, 0x32, 0x5d, 0x83, 0xa2, 0x77, 0x4b, 0x13, 0xdd, 0x21, 0xff}, true }, + + // WACCA (CHN) + { "SDHN", {0x89, 0x21, 0x23, 0xa2, 0x6d, 0x7c, 0x03, 0xd4, 0x9e, 0xdd, 0x12, 0xa8, 0x0e, 0xe0, 0xc5, 0x8f}, {0x76, 0xaa, 0x15, 0xa6, 0x86, 0x8b, 0x8d, 0xbd, 0xf7, 0x20, 0x79, 0x06, 0x35, 0x4d, 0x51, 0x69}, true }, + + // meityromantic + { "SDHR", {0x1f, 0xb8, 0x97, 0xca, 0xb9, 0x7c, 0x81, 0x70, 0xa6, 0xac, 0x0a, 0x21, 0x68, 0x5c, 0x58, 0xd9}, {0xf9, 0xb6, 0x0f, 0x65, 0xb0, 0x1e, 0x8e, 0x83, 0x6a, 0x4b, 0xc2, 0x0f, 0x7d, 0x39, 0xfa, 0xf5}, true }, + + // ALLS System + { "ACA", {0xe4, 0x28, 0x1b, 0xcf, 0x48, 0xc4, 0xd2, 0x8e, 0xb0, 0x57, 0x72, 0xce, 0x6f, 0x98, 0x58, 0x7a}, {0x6c, 0xee, 0x7f, 0x5a, 0x2c, 0x4b, 0x5f, 0x1e, 0x93, 0xc5, 0x94, 0x91, 0x14, 0xff, 0x0b, 0x74}, true }, + + // Read from {game_id}.bin for unknown keys. + { NULL, {0}, {0}, false } +}; + +bool get_game_keys(const char* game_id, GameKeys* out_keys) { + const GameKeyEntry* entry = game_keys; + while (entry->game_id != NULL) { + if (strcmp(entry->game_id, game_id) == 0) { + memcpy(out_keys->key, entry->key, 16); + if (entry->has_iv) { + memcpy(out_keys->iv, entry->iv, 16); + out_keys->has_iv = true; + } + else { + memset(out_keys->iv, 0, 16); + out_keys->has_iv = false; + } + return true; + } + entry++; + } + + char filename[256]; + sprintf(filename, "%s.bin", game_id); + + FILE* file = fopen(filename, "rb"); + if (!file) { + return false; + } + + uint8_t buffer[32]; + size_t read_size = fread(buffer, 1, sizeof(buffer), file); + fclose(file); + + if (read_size == 16) { + memcpy(out_keys->key, buffer, 16); + memset(out_keys->iv, 0, 16); + out_keys->has_iv = false; + return true; + } + else if (read_size == 32) { + memcpy(out_keys->key, buffer, 16); + memcpy(out_keys->iv, buffer + 16, 16); + + if (memcmp(out_keys->iv, NTFS_HEADER, 16) == 0 || memcmp(out_keys->iv, EXFAT_HEADER, 16) == 0) { + out_keys->has_iv = false; + } + else { + out_keys->has_iv = true; + } + return true; + } + else { + return false; + } +} \ No newline at end of file diff --git a/unsegareborn/crypto.h b/unsegareborn/crypto.h new file mode 100644 index 0000000..ec4aa04 --- /dev/null +++ b/unsegareborn/crypto.h @@ -0,0 +1,44 @@ +#ifndef CRYPTO_H +#define CRYPTO_H + +#include +#include + +static const uint8_t NTFS_HEADER[16] = { + 0xeb, 0x52, 0x90, 0x4e, 0x54, 0x46, 0x53, 0x20, + 0x20, 0x20, 0x20, 0x00, 0x10, 0x01, 0x00, 0x00 +}; + +static const uint8_t EXFAT_HEADER[16] = { + 0xeb, 0x76, 0x90, 0x45, 0x58, 0x46, 0x41, 0x54, + 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const uint8_t OPTION_KEY[16] = { + 0x5c, 0x84, 0xa9, 0xe7, 0x26, 0xea, 0xa5, 0xdd, + 0x35, 0x1f, 0x2b, 0x07, 0x50, 0xc2, 0x36, 0x97 +}; + +static const uint8_t OPTION_IV[16] = { + 0xc0, 0x63, 0xbf, 0x6f, 0x56, 0x2d, 0x08, 0x4d, + 0x79, 0x63, 0xc9, 0x87, 0xf5, 0x28, 0x17, 0x61 +}; + +typedef struct { + uint8_t key[16]; + uint8_t iv[16]; + bool has_iv; +} GameKeys; + +void calculate_page_iv(uint64_t file_offset, const uint8_t* file_iv, uint8_t* page_iv); + +bool calculate_file_iv( + const uint8_t key[16], + const uint8_t expected_header[16], + const uint8_t* first_page, + uint8_t out_iv[16] +); + +bool get_game_keys(const char* game_id, GameKeys* out_keys); + +#endif // CRYPTO_H \ No newline at end of file diff --git a/unsegareborn/exfat.c b/unsegareborn/exfat.c new file mode 100644 index 0000000..c466fe4 --- /dev/null +++ b/unsegareborn/exfat.c @@ -0,0 +1,204 @@ +#include "exfat.h" +#include +#include + +#include +#define MKDIR(path) _mkdir(path) +#define PATH_SEPARATOR "\\" + +static bool create_directories(const char* path) { + char temp[MAX_PATH_LENGTH]; + char* p = NULL; + size_t len; + bool result = true; + + strncpy(temp, path, MAX_PATH_LENGTH - 1); + temp[MAX_PATH_LENGTH - 1] = '\0'; + len = strlen(temp); + + if (len > 0 && (temp[len - 1] == '/' || temp[len - 1] == '\\')) { + temp[len - 1] = '\0'; + } + + for (p = temp + 1; *p; p++) { + if (*p == '/' || *p == '\\') { + *p = '\0'; + if (MKDIR(temp) != 0 && errno != EEXIST) { + result = false; + break; + } + *p = PATH_SEPARATOR[0]; + } + } + + if (result && MKDIR(temp) != 0 && errno != EEXIST) { + result = false; + } + + return result; +} + +static uint32_t get_cluster_offset(ExfatContext* ctx, uint32_t cluster) { + return ctx->cluster_heap_offset_bytes + + ((cluster - 2) * ctx->bytes_per_cluster); +} + +static bool read_cluster(ExfatContext* ctx, uint32_t cluster, void* buffer) { + uint32_t offset = get_cluster_offset(ctx, cluster); + if (fseek(ctx->fp, offset, SEEK_SET) != 0) { + return false; + } + return fread(buffer, 1, ctx->bytes_per_cluster, ctx->fp) == ctx->bytes_per_cluster; +} + +static void combine_path(char* dest, size_t dest_size, const char* dir, const char* name) { + size_t dir_len = strlen(dir); + size_t name_len = strlen(name); + + if (dir_len + name_len + 2 > dest_size) { + dest[0] = '\0'; + return; + } + + strcpy(dest, dir); + if (dir_len > 0 && dir[dir_len - 1] != '/' && dir[dir_len - 1] != '\\') { + strcat(dest, PATH_SEPARATOR); + } + strcat(dest, name); +} + +static bool extract_file(ExfatContext* ctx, ExfatFileInfo* file, const char* output_path) { + FILE* out = fopen(output_path, "wb"); + if (!out) { + return false; + } + + uint32_t current_cluster = file->first_cluster; + uint64_t remaining = file->data_length; + uint8_t* buffer = malloc(ctx->bytes_per_cluster); + + if (!buffer) { + fclose(out); + return false; + } + + bool success = true; + while (remaining > 0 && success) { + if (!read_cluster(ctx, current_cluster, buffer)) { + success = false; + break; + } + + size_t write_size = (remaining > ctx->bytes_per_cluster) ? + ctx->bytes_per_cluster : (size_t)remaining; + + if (fwrite(buffer, 1, write_size, out) != write_size) { + success = false; + break; + } + + remaining -= write_size; + current_cluster++; + } + + free(buffer); + fclose(out); + return success; +} + +bool exfat_init(ExfatContext* ctx, const char* filename) { + memset(ctx, 0, sizeof(ExfatContext)); + + ctx->fp = fopen(filename, "rb"); + if (!ctx->fp) { + return false; + } + + if (fread(&ctx->boot_sector, sizeof(ExfatBootSector), 1, ctx->fp) != 1) { + fclose(ctx->fp); + return false; + } + + ctx->bytes_per_cluster = (1 << ctx->boot_sector.bytes_per_sector_shift) * + (1 << ctx->boot_sector.sectors_per_cluster_shift); + + ctx->cluster_heap_offset_bytes = ctx->boot_sector.cluster_heap_offset * + (1 << ctx->boot_sector.bytes_per_sector_shift); + + return true; +} + +static bool process_directory(ExfatContext* ctx, uint32_t cluster, + const char* output_dir) { + uint8_t* buffer = malloc(ctx->bytes_per_cluster); + if (!buffer) { + return false; + } + + if (!read_cluster(ctx, cluster, buffer)) { + free(buffer); + return false; + } + + uint32_t entries_per_cluster = ctx->bytes_per_cluster / EXFAT_ENTRY_SIZE; + uint8_t* entry = buffer; + + for (uint32_t i = 0; i < entries_per_cluster; i++) { + if (*entry == EXFAT_ENTRY_EOD) { + break; + } + + if (*entry == EXFAT_ENTRY_FILE) { + ExfatFileEntry* file_entry = (ExfatFileEntry*)entry; + ExfatStreamEntry* stream_entry = (ExfatStreamEntry*)(entry + EXFAT_ENTRY_SIZE); + ExfatFileNameEntry* name_entry = (ExfatFileNameEntry*)(entry + EXFAT_ENTRY_SIZE * 2); + + if (stream_entry->entry_type == EXFAT_ENTRY_STREAM) { + ExfatFileInfo file_info; + memset(&file_info, 0, sizeof(file_info)); + + char* name_ptr = file_info.name; + for (int j = 0; j < stream_entry->name_length && j < 15; j++) { + *name_ptr++ = (char)(name_entry->file_name[j] & 0xFF); + } + *name_ptr = '\0'; + + file_info.first_cluster = stream_entry->first_cluster; + file_info.data_length = stream_entry->data_length; + file_info.is_directory = (file_entry->file_attributes & 0x10) != 0; + + char full_path[MAX_PATH_LENGTH]; + combine_path(full_path, sizeof(full_path), output_dir, file_info.name); + + if (file_info.is_directory) { + if (create_directories(full_path)) { + process_directory(ctx, file_info.first_cluster, full_path); + } + } + else { + extract_file(ctx, &file_info, full_path); + } + } + } + + entry += EXFAT_ENTRY_SIZE; + } + + free(buffer); + return true; +} + +bool exfat_extract_all(ExfatContext* ctx, const char* output_dir) { + if (!create_directories(output_dir)) { + return false; + } + return process_directory(ctx, ctx->boot_sector.first_cluster_of_root_dir, + output_dir); +} + +void exfat_close(ExfatContext* ctx) { + if (ctx->fp) { + fclose(ctx->fp); + ctx->fp = NULL; + } +} \ No newline at end of file diff --git a/unsegareborn/exfat.h b/unsegareborn/exfat.h new file mode 100644 index 0000000..e0d7ef1 --- /dev/null +++ b/unsegareborn/exfat.h @@ -0,0 +1,100 @@ +#ifndef EXFAT_H +#define EXFAT_H + +#include +#include +#include + +#define EXFAT_ENTRY_SIZE 32 +#define MAX_PATH_LENGTH 256 +#define MAX_FILENAME_LENGTH 256 + +#define EXFAT_ENTRY_EOD 0x00 +#define EXFAT_ENTRY_BITMAP 0x81 +#define EXFAT_ENTRY_FILE 0x85 +#define EXFAT_ENTRY_STREAM 0xC0 +#define EXFAT_ENTRY_FILENAME 0xC1 + +#pragma pack(push, 1) + +typedef struct { + uint8_t jump_boot[3]; + uint8_t fs_name[8]; + uint8_t must_be_zero[53]; + uint64_t partition_offset; + uint64_t volume_length; + uint32_t fat_offset; + uint32_t fat_length; + uint32_t cluster_heap_offset; + uint32_t cluster_count; + uint32_t first_cluster_of_root_dir; + uint32_t volume_serial_number; + uint16_t fs_revision; + uint16_t volume_flags; + uint8_t bytes_per_sector_shift; + uint8_t sectors_per_cluster_shift; + uint8_t number_of_fats; + uint8_t drive_select; + uint8_t percent_in_use; + uint8_t reserved[7]; + uint8_t boot_code[390]; + uint16_t boot_signature; +} ExfatBootSector; + +typedef struct { + uint8_t entry_type; + uint8_t secondary_count; + uint16_t set_checksum; + uint16_t file_attributes; + uint16_t reserved1; + uint32_t create_timestamp; + uint32_t last_modified_timestamp; + uint32_t last_access_timestamp; + uint8_t create_10ms; + uint8_t last_modified_10ms; + uint8_t create_utc_offset; + uint8_t last_modified_utc_offset; + uint8_t last_access_utc_offset; + uint8_t reserved2[7]; +} ExfatFileEntry; + +typedef struct { + uint8_t entry_type; + uint8_t flags; + uint8_t reserved1; + uint8_t name_length; + uint16_t name_hash; + uint16_t reserved2; + uint64_t valid_data_length; + uint32_t reserved3; + uint32_t first_cluster; + uint64_t data_length; +} ExfatStreamEntry; + +typedef struct { + uint8_t entry_type; + uint8_t flags; + uint16_t file_name[15]; +} ExfatFileNameEntry; + +#pragma pack(pop) + +typedef struct { + char name[MAX_PATH_LENGTH]; + uint32_t first_cluster; + uint64_t data_length; + bool is_directory; +} ExfatFileInfo; + +typedef struct { + FILE* fp; + ExfatBootSector boot_sector; + uint32_t bytes_per_cluster; + uint32_t cluster_heap_offset_bytes; +} ExfatContext; + +bool exfat_init(ExfatContext* ctx, const char* filename); +bool exfat_extract_all(ExfatContext* ctx, const char* output_dir); +void exfat_close(ExfatContext* ctx); + +#endif // EXFAT_H \ No newline at end of file diff --git a/unsegareborn/main.c b/unsegareborn/main.c new file mode 100644 index 0000000..bf30d9f --- /dev/null +++ b/unsegareborn/main.c @@ -0,0 +1,517 @@ +#include +#include +#include +#include +#include +#include "bootid.h" +#include "crypto.h" +#include "exfat.h" +#include "ntfs.h" +#include +#include + +#define PAGE_SIZE 4096 +#define BUFFER_SIZE (PAGE_SIZE * 256) +#define MAX_PATH_LENGTH 256 + +char* g_output_filename = NULL; + +int process_file(const char* path, bool extract_fs) { + uint8_t* bootid_bytes = malloc(96); + uint8_t* read_buffer = malloc(BUFFER_SIZE); + uint8_t* write_buffer = malloc(BUFFER_SIZE); + uint8_t* decrypted_buffer = malloc(BUFFER_SIZE); + uint8_t iv[16]; + uint8_t page_iv[16]; + uint8_t key[16]; + + if (!bootid_bytes || !read_buffer || !write_buffer || !decrypted_buffer) { + printf("Memory allocation failed\n"); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + FILE* file = fopen(path, "rb"); + if (!file) { + printf("Could not open file %s\n", path); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + if (fread(bootid_bytes, 1, 96, file) != 96) { + printf("Could not read BootId from %s\n", path); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + uint8_t decrypted_bootid_bytes[96]; + int out_len = 0; + + EVP_CIPHER_CTX* bootid_ctx = EVP_CIPHER_CTX_new(); + if (!bootid_ctx) { + printf("Could not create cipher context\n"); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + EVP_DecryptInit_ex(bootid_ctx, EVP_aes_128_cbc(), NULL, BOOTID_KEY, BOOTID_IV); + EVP_CIPHER_CTX_set_padding(bootid_ctx, 0); + + if (!EVP_DecryptUpdate(bootid_ctx, decrypted_bootid_bytes, &out_len, bootid_bytes, 96)) { + printf("Could not decrypt BootId in %s\n", path); + EVP_CIPHER_CTX_free(bootid_ctx); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + int final_len = 0; + if (!EVP_DecryptFinal_ex(bootid_ctx, decrypted_bootid_bytes + out_len, &final_len)) { + printf("Could not finalize decryption\n"); + EVP_CIPHER_CTX_free(bootid_ctx); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + EVP_CIPHER_CTX_free(bootid_ctx); + + BootId bootid; + memcpy(&bootid, decrypted_bootid_bytes, sizeof(BootId)); + + if (bootid.container_type != CONTAINER_TYPE_OS && + bootid.container_type != CONTAINER_TYPE_APP && + bootid.container_type != CONTAINER_TYPE_OPTION) { + printf("Unknown container type %d\n", bootid.container_type); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + char target_timestamp_str[20]; + format_timestamp(&bootid.target_timestamp, target_timestamp_str, sizeof(target_timestamp_str)); + + char os_id[4]; + char game_id[5]; + memcpy(os_id, bootid.os_id, 3); + os_id[3] = '\0'; + memcpy(game_id, bootid.game_id, 4); + game_id[4] = '\0'; + + const char* id = (bootid.container_type == CONTAINER_TYPE_OS) ? os_id : game_id; + + GameKeys keys; + bool got_keys = false; + if (bootid.container_type == CONTAINER_TYPE_OS || bootid.container_type == CONTAINER_TYPE_APP) { + got_keys = get_game_keys(id, &keys); + } + else { + memcpy(keys.key, OPTION_KEY, 16); + memcpy(keys.iv, OPTION_IV, 16); + keys.has_iv = true; + got_keys = true; + } + + if (!got_keys) { + printf("Decryption key invalid or not found.\n"); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + uint64_t data_offset = bootid.header_block_count * bootid.block_size; + + bool has_iv = false; + memcpy(key, keys.key, 16); + + if (bootid.use_custom_iv) { + has_iv = false; + } + else { + has_iv = keys.has_iv; + if (has_iv) { + memcpy(iv, keys.iv, 16); + } + } + + if (!has_iv) { + if (fseek(file, data_offset, SEEK_SET) != 0) { + printf("Could not seek to data offset\n"); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + size_t read_size = fread(read_buffer, 1, PAGE_SIZE, file); + if (read_size != PAGE_SIZE) { + printf("Could not read data page\n"); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + if (bootid.container_type == CONTAINER_TYPE_OPTION) { + if (!calculate_file_iv(key, EXFAT_HEADER, read_buffer, iv)) { + printf("Could not calculate file IV\n"); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + } + else { + if (!calculate_file_iv(key, NTFS_HEADER, read_buffer, iv)) { + printf("Could not calculate file IV\n"); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + } + has_iv = true; + } + + char* output_filename = malloc(MAX_PATH_LENGTH); + if (!output_filename) { + printf("Memory allocation failed\n"); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + if (bootid.container_type == CONTAINER_TYPE_OS) { + snprintf(output_filename, MAX_PATH_LENGTH, "%s_%04d%02d%02d_%s_%d.ntfs", + os_id, + bootid.os_version.major, + bootid.os_version.minor, + bootid.os_version.release, + target_timestamp_str, + bootid.sequence_number); + } + else if (bootid.container_type == CONTAINER_TYPE_APP) { + if (bootid.sequence_number > 0) { + snprintf(output_filename, MAX_PATH_LENGTH, "%s_%d%02d%02d_%s_%d_%d%02d%02d.ntfs", + game_id, + bootid.target_version.version.major, + bootid.target_version.version.minor, + bootid.target_version.version.release, + target_timestamp_str, + bootid.sequence_number, + bootid.source_version.major, + bootid.source_version.minor, + bootid.source_version.release); + } + else { + snprintf(output_filename, MAX_PATH_LENGTH, "%s_%d%02d%02d_%s_%d.ntfs", + game_id, + bootid.target_version.version.major, + bootid.target_version.version.minor, + bootid.target_version.version.release, + target_timestamp_str, + bootid.sequence_number); + } + } + else if (bootid.container_type == CONTAINER_TYPE_OPTION) { + char option_str[5]; + memcpy(option_str, bootid.target_version.option, 4); + option_str[4] = '\0'; + snprintf(output_filename, MAX_PATH_LENGTH, "%s_%s_%s_%d.exfat", + game_id, + option_str, + target_timestamp_str, + bootid.sequence_number); + } + + FILE* output_file = fopen(output_filename, "wb"); + if (!output_file) { + printf("Could not create output file %s\n", output_filename); + free(output_filename); + fclose(file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + uint64_t output_size = (bootid.block_count - bootid.header_block_count) * bootid.block_size; + + if (fseek(file, data_offset, SEEK_SET) != 0) { + printf("Could not seek to data offset\n"); + fclose(file); + fclose(output_file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + + EVP_CIPHER_CTX* page_ctx = EVP_CIPHER_CTX_new(); + if (!page_ctx) { + printf("Could not create cipher context\n"); + fclose(file); + fclose(output_file); + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + return 1; + } + EVP_DecryptInit_ex(page_ctx, EVP_aes_128_cbc(), NULL, key, NULL); + EVP_CIPHER_CTX_set_padding(page_ctx, 0); + + printf("\nDecrypting file...\n"); + time_t last_update_time = time(NULL); + int last_percentage = -1; + + uint64_t total_bytes_read = 0; + uint64_t bytes_remaining = output_size; + + while (bytes_remaining > 0) { + size_t chunk_size = (bytes_remaining > BUFFER_SIZE) ? BUFFER_SIZE : (size_t)bytes_remaining; + + size_t read_size = fread(read_buffer, 1, chunk_size, file); + if (read_size != chunk_size) { + printf("\nCould not read data\n"); + break; + } + + size_t offset = 0; + while (offset < read_size) { + size_t block_size = (read_size - offset > PAGE_SIZE) ? PAGE_SIZE : (read_size - offset); + + uint64_t file_offset = data_offset + total_bytes_read + offset - data_offset; + calculate_page_iv(file_offset, iv, page_iv); + + EVP_DecryptInit_ex(page_ctx, NULL, NULL, NULL, page_iv); + + int out_len1 = 0, out_len2 = 0; + if (!EVP_DecryptUpdate(page_ctx, decrypted_buffer + offset, &out_len1, read_buffer + offset, block_size)) { + printf("\nCould not decrypt data\n"); + break; + } + if (!EVP_DecryptFinal_ex(page_ctx, decrypted_buffer + offset + out_len1, &out_len2)) { + printf("\nCould not finalize decryption\n"); + break; + } + if (out_len1 + out_len2 != (int)block_size) { + printf("\nDecrypted data size mismatch\n"); + break; + } + offset += block_size; + } + + if (fwrite(decrypted_buffer, 1, read_size, output_file) != read_size) { + printf("\nCould not write to output file\n"); + break; + } + + total_bytes_read += read_size; + bytes_remaining -= read_size; + + time_t current_time = time(NULL); + if (current_time != last_update_time) { + int percentage = (int)((total_bytes_read * 100) / output_size); + if (percentage != last_percentage) { + printf("\rProgress: %d%%", percentage); + fflush(stdout); + last_percentage = percentage; + } + last_update_time = current_time; + } + } + + printf("\rProgress: 100%%\n"); + + EVP_CIPHER_CTX_free(page_ctx); + + fclose(file); + fclose(output_file); + + memset(read_buffer, 0, BUFFER_SIZE); + memset(decrypted_buffer, 0, BUFFER_SIZE); + memset(page_iv, 0, sizeof(page_iv)); + memset(bootid_bytes, 0, 96); + memset(iv, 0, sizeof(iv)); + + printf("Decryption finalized: %s\n", output_filename); + + if (extract_fs) { + if (g_output_filename) { + free(g_output_filename); + } + g_output_filename = output_filename; + } + else { + free(output_filename); + } + + free(bootid_bytes); + free(read_buffer); + free(write_buffer); + free(decrypted_buffer); + + return 0; +} + +int main(int argc, char* argv[]) { + bool extract_fs = true; + int start_index = 1; + + if (argc < 2) { + printf("usage: unsegaREBORN [-no] [ ...]\n"); + printf(" -no Do not extract filesystem archives after decryption\n"); + return 0; + } + + if (strcmp(argv[1], "-no") == 0) { + extract_fs = false; + start_index = 2; + + if (argc < 3) { + printf("No input files specified\n"); + return 1; + } + } + + for (int i = start_index; i < argc; ++i) { + const char* file_path = argv[i]; + printf("Processing file: %s\n", file_path); + + if (process_file(file_path, extract_fs) == 0) { + if (extract_fs && g_output_filename) { + char output_dir[MAX_PATH_LENGTH]; + strncpy(output_dir, g_output_filename, sizeof(output_dir) - 1); + output_dir[sizeof(output_dir) - 1] = '\0'; + + char* ext = strrchr(output_dir, '.'); + if (ext) *ext = '\0'; + + if (strstr(g_output_filename, ".exfat") != NULL) { + ExfatContext ctx; + if (exfat_init(&ctx, g_output_filename)) { + if (exfat_extract_all(&ctx, output_dir)) { + printf("\nExFAT extraction completed successfully\n"); + } + else { + printf("\nFailed to extract ExFAT archive\n"); + } + exfat_close(&ctx); + } + else { + printf("\nFailed to initialize ExFAT context\n"); + } + } + else if (strstr(g_output_filename, ".ntfs") != NULL) { + NTFSContext ctx = { 0 }; + if (ntfs_init(&ctx, g_output_filename, output_dir)) { + printf("\nExtracting NTFS archive...\n"); + + if (ntfs_extract_all(&ctx)) { + printf("\nNTFS extraction completed successfully\n"); + + char vhd_path[MAX_PATH_LENGTH]; + bool found_child = false; + + for (int vhd_num = 0; vhd_num < 10; vhd_num++) { + snprintf(vhd_path, sizeof(vhd_path), "%s%sinternal_%d.vhd", + output_dir, PATH_SEPARATOR, vhd_num); + + FILE* test = fopen(vhd_path, "rb"); + if (!test) continue; + fclose(test); + + if (vhd_num > 0) { + printf("\nChild internal VHD identified, finalizing process.\n"); + found_child = true; + break; + } + + char vhd_output_dir[MAX_PATH_LENGTH]; + snprintf(vhd_output_dir, sizeof(vhd_output_dir), "%s%scontents", + output_dir, PATH_SEPARATOR); + + NTFSContext vhd_ctx = { 0 }; + if (ntfs_init(&vhd_ctx, vhd_path, vhd_output_dir)) { + printf("\nExtracting from internal VHD...\n"); + if (ntfs_extract_all(&vhd_ctx)) { + printf("\nInternal VHD extraction completed successfully\n"); + } + else { + printf("\nFailed to extract VHD contents\n"); + } + ntfs_close(&vhd_ctx); + } + else { + printf("\nFailed to open internal VHD\n"); + } + break; + } + } + else { + printf("\nFailed to extract NTFS archive\n"); + } + ntfs_close(&ctx); + } + else { + printf("\nFailed to initialize NTFS context\n"); + } + } + else { + printf("\nUnknown filesystem type for file %s\n", g_output_filename); + } + + free(g_output_filename); + g_output_filename = NULL; + } + } + else { + printf("Failed to process %s\n", file_path); + } + } + + if (g_output_filename) { + free(g_output_filename); + } + + return 0; +} \ No newline at end of file diff --git a/unsegareborn/ntfs.c b/unsegareborn/ntfs.c new file mode 100644 index 0000000..b045015 --- /dev/null +++ b/unsegareborn/ntfs.c @@ -0,0 +1,836 @@ +#include "ntfs.h" +#include + +static uint16_t swap16(uint16_t value) { + return ((value & 0xFF00) >> 8) | ((value & 0x00FF) << 8); +} + +static uint32_t swap32(uint32_t value) { + return ((value & 0xFF000000) >> 24) | + ((value & 0x00FF0000) >> 8) | + ((value & 0x0000FF00) << 8) | + ((value & 0x000000FF) << 24); +} + +static uint64_t swap64(uint64_t value) { + return ((value & 0xFF00000000000000ULL) >> 56) | + ((value & 0x00FF000000000000ULL) >> 40) | + ((value & 0x0000FF0000000000ULL) >> 24) | + ((value & 0x000000FF00000000ULL) >> 8) | + ((value & 0x00000000FF000000ULL) << 8) | + ((value & 0x0000000000FF0000ULL) << 24) | + ((value & 0x000000000000FF00ULL) << 40) | + ((value & 0x00000000000000FFULL) << 56); +} + +static bool create_directories(const char* path) { + char* tmp = _strdup(path); + if (!tmp) return false; + + bool success = true; + char* p = tmp; + + if (strlen(p) > 2) { + if (p[1] == ':') p += 2; + if (*p == '\\' || *p == '/') p++; + } + + while ((p = strchr(p, PATH_SEPARATOR[0])) != NULL) { + *p = '\0'; + if (strlen(tmp) > 0) { + if (MKDIR(tmp) != 0 && errno != EEXIST) { + success = false; + break; + } + } + *p = PATH_SEPARATOR[0]; + p++; + } + + if (success && strlen(tmp) > 0) { + if (MKDIR(tmp) != 0 && errno != EEXIST) { + success = false; + } + } + + free(tmp); + return success; +} + +static void convert_name_to_ascii(const uint16_t* utf16_name, int name_length, char* ascii_name) { + int i; + for (i = 0; i < name_length && i < MAX_FILENAME_LENGTH - 1; i++) { + ascii_name[i] = (char)(utf16_name[i] & 0xFF); + } + ascii_name[i] = '\0'; +} + +static bool ntfs_read(NTFSContext* ctx, void* buffer, uint64_t offset, size_t size) { + if (ctx->is_vhd) { + return vhd_read(&ctx->vhd, buffer, offset, size); + } + if (fseeko(ctx->raw.fp, offset, SEEK_SET) != 0) { + return false; + } + return fread(buffer, 1, size, ctx->raw.fp) == size; +} + +static bool read_file_info(NTFSContext* ctx, uint64_t ref_number, FileInfo* info) { + memset(info, 0, sizeof(FileInfo)); + + uint64_t mft_offset = ctx->mft_offset + (ref_number * ctx->mft_record_size); + uint8_t* record_buffer = malloc(ctx->mft_record_size); + if (!record_buffer) { + return false; + } + + bool success = false; + if (ntfs_read(ctx, record_buffer, mft_offset, ctx->mft_record_size)) { + const MFTRecordHeader* record = (const MFTRecordHeader*)record_buffer; + + if (memcmp(record->magic, "FILE", 4) == 0 && (record->flags & MFT_RECORD_IN_USE)) { + info->is_directory = (record->flags & MFT_RECORD_IS_DIRECTORY) != 0; + + const uint8_t* attr = (const uint8_t*)record + record->attrs_offset; + while (attr < (const uint8_t*)record + record->bytes_used) { + const AttributeHeader* header = (const AttributeHeader*)attr; + + if (header->type == 0xFFFFFFFF || header->length == 0) { + break; + } + + if (header->type == FILE_NAME_ATTR && !header->non_resident) { + const FileNameAttribute* fname = + (const FileNameAttribute*)(attr + header->data.resident.value_offset); + + if (fname->namespace != 2) { + convert_name_to_ascii(fname->name, fname->name_length, info->name); + info->parent_ref = fname->parent_directory & 0xFFFFFFFFFFFF; + info->valid = true; + success = true; + break; + } + } + + attr += header->length; + } + } + } + + free(record_buffer); + return success; +} + +static bool init_directory_cache(DirectoryCache* cache) { + cache->capacity = 1024; + cache->count = 0; + cache->directories = malloc(cache->capacity * sizeof(DirectoryInfo)); + if (!cache->directories) return false; + + cache->directories[0].ref_number = 5; + cache->directories[0].path[0] = '\0'; + cache->count = 1; + return true; +} + +static void free_directory_cache(DirectoryCache* cache) { + free(cache->directories); + cache->directories = NULL; + cache->capacity = 0; + cache->count = 0; +} + +static bool add_directory_to_cache(DirectoryCache* cache, uint64_t ref_number, const char* path) { + for (size_t i = 0; i < cache->count; i++) { + if (cache->directories[i].ref_number == ref_number) { + strncpy(cache->directories[i].path, path, MAX_PATH_LENGTH - 1); + cache->directories[i].path[MAX_PATH_LENGTH - 1] = '\0'; + return true; + } + } + + if (cache->count >= cache->capacity) { + size_t new_capacity = cache->capacity * 2; + DirectoryInfo* new_dirs = realloc(cache->directories, + new_capacity * sizeof(DirectoryInfo)); + if (!new_dirs) return false; + cache->directories = new_dirs; + cache->capacity = new_capacity; + } + + if (cache->count < cache->capacity) { + cache->directories[cache->count].ref_number = ref_number; + strncpy(cache->directories[cache->count].path, path, MAX_PATH_LENGTH - 1); + cache->directories[cache->count].path[MAX_PATH_LENGTH - 1] = '\0'; + cache->count++; + return true; + } + + return false; +} + +static const char* get_cached_path(DirectoryCache* cache, uint64_t ref_number) { + for (size_t i = 0; i < cache->count; i++) { + if (cache->directories[i].ref_number == ref_number) { + return cache->directories[i].path; + } + } + return NULL; +} + +static bool build_path_recursively(NTFSContext* ctx, uint64_t ref_number, char* buffer, size_t buffer_size) { + if (ref_number == 5) { + buffer[0] = '\0'; + return true; + } + + const char* cached_path = get_cached_path(&ctx->dir_cache, ref_number); + if (cached_path) { + strncpy(buffer, cached_path, buffer_size - 1); + buffer[buffer_size - 1] = '\0'; + return true; + } + + FileInfo info; + if (!read_file_info(ctx, ref_number, &info) || !info.valid) { + return false; + } + + if (info.name[0] == '$') { + return false; + } + + char parent_path[MAX_PATH_LENGTH]; + if (!build_path_recursively(ctx, info.parent_ref, parent_path, sizeof(parent_path))) { + return false; + } + + if (parent_path[0] == '\0') { + strncpy(buffer, info.name, buffer_size - 1); + } + else { + snprintf(buffer, buffer_size, "%s%s%s", parent_path, PATH_SEPARATOR, info.name); + } + buffer[buffer_size - 1] = '\0'; + + if (info.is_directory) { + add_directory_to_cache(&ctx->dir_cache, ref_number, buffer); + } + + return true; +} + +static void get_full_path(const NTFSContext* ctx, uint64_t parent_ref, const char* name, + char* out_path, size_t out_size) { + char parent_path[MAX_PATH_LENGTH]; + + if (!build_path_recursively(ctx, parent_ref, parent_path, sizeof(parent_path))) { + snprintf(out_path, out_size, "%s%s%s", + ctx->base_path, + PATH_SEPARATOR, + name); + return; + } + + if (parent_path[0] == '\0') { + snprintf(out_path, out_size, "%s%s%s", + ctx->base_path, + PATH_SEPARATOR, + name); + } + else { + snprintf(out_path, out_size, "%s%s%s%s%s", + ctx->base_path, + PATH_SEPARATOR, + parent_path, + PATH_SEPARATOR, + name); + } +} + +static bool extract_data_from_runs(NTFSContext* ctx, const DataRun* runs, int run_count, + uint64_t data_size, FILE* out_file) { + uint8_t* temp_buffer = malloc(BUFFER_SIZE); + if (!temp_buffer) return false; + + uint64_t total_written = 0; + bool success = true; + + for (int i = 0; i < run_count && total_written < data_size; i++) { + uint64_t cluster_offset = ctx->data_start_offset + + (runs[i].offset * ctx->bytes_per_cluster); + uint64_t length = runs[i].length * ctx->bytes_per_cluster; + + if (length > data_size - total_written) { + length = data_size - total_written; + } + + uint64_t remaining = length; + while (remaining > 0 && success) { + size_t to_read = (remaining > BUFFER_SIZE) ? BUFFER_SIZE : (size_t)remaining; + + if (!ntfs_read(ctx, temp_buffer, cluster_offset, to_read)) { + success = false; + break; + } + + if (fwrite(temp_buffer, 1, to_read, out_file) != to_read) { + success = false; + break; + } + + cluster_offset += to_read; + remaining -= to_read; + total_written += to_read; + } + } + + free(temp_buffer); + return success; +} + +static int parse_data_runs(const uint8_t* run_list, DataRun* runs, int max_runs) { + int count = 0; + uint64_t offset_base = 0; + const uint8_t* p = run_list; + + while (*p != 0 && count < max_runs) { + uint8_t header = *p++; + int length_size = header & 0xF; + int offset_size = header >> 4; + + if (length_size == 0) break; + + uint64_t length = 0; + for (int i = 0; i < length_size; i++) { + length |= ((uint64_t)*p++) << (i * 8); + } + + int64_t offset = 0; + if (offset_size > 0) { + for (int i = 0; i < offset_size; i++) { + offset |= ((uint64_t)*p++) << (i * 8); + } + if (offset & ((uint64_t)1 << ((offset_size * 8) - 1))) { + offset |= ~((uint64_t)(1ULL << (offset_size * 8)) - 1); + } + } + + offset_base += offset; + runs[count].offset = offset_base; + runs[count].length = length; + count++; + } + + return count; +} + +static bool extract_file(NTFSContext* ctx, const MFTRecordHeader* record, + const char* full_path) { + char parent_path[MAX_PATH_LENGTH]; + strncpy(parent_path, full_path, sizeof(parent_path) - 1); + parent_path[sizeof(parent_path) - 1] = '\0'; + + char* last_separator = strrchr(parent_path, PATH_SEPARATOR[0]); + if (last_separator) { + *last_separator = '\0'; + create_directories(parent_path); + } + + FILE* out_file = fopen(full_path, "wb"); + if (!out_file) { + printf("Failed to create file: %s\n", full_path); + return false; + } + + bool success = false; + const uint8_t* attr = (const uint8_t*)record + record->attrs_offset; + + while (attr < (const uint8_t*)record + record->bytes_used) { + const AttributeHeader* header = (const AttributeHeader*)attr; + + if (header->type == 0xFFFFFFFF || header->length == 0) { + break; + } + + if (header->type == DATA_ATTR && header->name_length == 0) { + if (header->non_resident) { + DataRun runs[256]; + const uint8_t* run_list = attr + header->data.non_resident.mapping_pairs_offset; + int run_count = parse_data_runs(run_list, runs, 256); + + success = extract_data_from_runs(ctx, runs, run_count, + header->data.non_resident.data_size, + out_file); + } + else { + const uint8_t* data = attr + header->data.resident.value_offset; + success = (fwrite(data, 1, header->data.resident.value_length, out_file) == + header->data.resident.value_length); + } + break; + } + + attr += header->length; + } + + fclose(out_file); + if (!success) { + remove(full_path); + } + return success; +} + +static bool process_mft_record(NTFSContext* ctx, const uint8_t* record_data) { + const MFTRecordHeader* record = (const MFTRecordHeader*)record_data; + + if (memcmp(record->magic, "FILE", 4) != 0 || !(record->flags & MFT_RECORD_IN_USE)) { + return true; + } + + char filename[MAX_FILENAME_LENGTH]; + uint64_t parent_ref = 0; + bool got_filename = false; + bool is_directory = (record->flags & MFT_RECORD_IS_DIRECTORY) != 0; + uint64_t record_num = record->record_number & 0xFFFFFFFFFFFF; + + const uint8_t* attr = (const uint8_t*)record + record->attrs_offset; + while (attr < (const uint8_t*)record + record->bytes_used) { + const AttributeHeader* header = (const AttributeHeader*)attr; + + if (header->type == 0xFFFFFFFF || header->length == 0) { + break; + } + + if (header->type == FILE_NAME_ATTR && !header->non_resident) { + const FileNameAttribute* fname = + (const FileNameAttribute*)(attr + header->data.resident.value_offset); + + if (fname->namespace != 2) { + convert_name_to_ascii(fname->name, fname->name_length, filename); + parent_ref = fname->parent_directory & 0xFFFFFFFFFFFF; + got_filename = true; + break; + } + } + + attr += header->length; + } + + if (!got_filename || filename[0] == '$') { + return true; + } + + char full_path[MAX_PATH_LENGTH]; + get_full_path(ctx, parent_ref, filename, full_path, sizeof(full_path)); + + if (is_directory) { + if (!create_directories(full_path)) { + printf("Failed to create directory: %s\n", full_path); + return false; + } + + const char* relative_path = full_path + strlen(ctx->base_path); + while (*relative_path == PATH_SEPARATOR[0]) relative_path++; + + if (!add_directory_to_cache(&ctx->dir_cache, record_num, relative_path)) { + printf("Failed to cache directory: %s\n", filename); + return false; + } + return true; + } + + return extract_file(ctx, record, full_path); +} + +static bool vhd_read(VHDContext* ctx, void* buffer, uint64_t offset, size_t size) { + if (ctx->footer.disk_type == VHD_TYPE_FIXED) { + if (fseeko(ctx->fp, offset, SEEK_SET) != 0) { + return false; + } + return fread(buffer, 1, size, ctx->fp) == size; + } + else if (ctx->footer.disk_type == VHD_TYPE_DYNAMIC) { + uint8_t* buf = (uint8_t*)buffer; + uint64_t block_size = ctx->dyn_header.block_size; + + while (size > 0) { + uint32_t block_idx = (uint32_t)(offset / block_size); + uint32_t block_offset = (uint32_t)(offset % block_size); + + if (block_idx >= ctx->dyn_header.max_bat_entries) { + return false; + } + + uint32_t bat_entry = ctx->bat[block_idx]; + if (bat_entry == VHD_BAT_ENTRY_RESERVED) { + size_t chunk = (size < (block_size - block_offset)) ? + size : (size_t)(block_size - block_offset); + memset(buf, 0, chunk); + buf += chunk; + offset += chunk; + size -= chunk; + } + else { + uint64_t sector_offset = ((uint64_t)bat_entry) * VHD_SECTOR_SIZE; + + if (fseeko(ctx->fp, sector_offset, SEEK_SET) != 0) { + return false; + } + + if (fread(ctx->sector_bitmap, 1, ctx->sector_bitmap_size, ctx->fp) + != ctx->sector_bitmap_size) { + return false; + } + + if (fread(ctx->block_buffer, 1, block_size, ctx->fp) != block_size) { + return false; + } + + size_t chunk = (size < (block_size - block_offset)) ? + size : (size_t)(block_size - block_offset); + memcpy(buf, ctx->block_buffer + block_offset, chunk); + buf += chunk; + offset += chunk; + size -= chunk; + } + } + return true; + } + return false; +} + +static bool vhd_init(VHDContext* ctx, const char* filename) { + memset(ctx, 0, sizeof(VHDContext)); + + ctx->fp = fopen(filename, "rb"); + if (!ctx->fp) { + printf("Failed to open file: %s\n", filename); + return false; + } + + if (fseeko(ctx->fp, -((int64_t)VHD_FOOTER_SIZE), SEEK_END) != 0) { + printf("Failed to seek to VHD footer\n"); + fclose(ctx->fp); + return false; + } + + if (fread(&ctx->footer, 1, sizeof(VHDFooter), ctx->fp) != sizeof(VHDFooter)) { + printf("Failed to read VHD footer\n"); + fclose(ctx->fp); + return false; + } + + if (memcmp(ctx->footer.cookie, VHD_COOKIE, strlen(VHD_COOKIE)) != 0) { + printf("Invalid VHD signature\n"); + fclose(ctx->fp); + return false; + } + + ctx->footer.features = swap32(ctx->footer.features); + ctx->footer.version = swap32(ctx->footer.version); + ctx->footer.data_offset = swap64(ctx->footer.data_offset); + ctx->footer.timestamp = swap32(ctx->footer.timestamp); + ctx->footer.creator_app = swap32(ctx->footer.creator_app); + ctx->footer.creator_ver = swap32(ctx->footer.creator_ver); + ctx->footer.creator_os = swap32(ctx->footer.creator_os); + ctx->footer.original_size = swap64(ctx->footer.original_size); + ctx->footer.current_size = swap64(ctx->footer.current_size); + ctx->footer.cylinder = swap16(ctx->footer.cylinder); + ctx->footer.disk_type = swap32(ctx->footer.disk_type); + ctx->footer.checksum = swap32(ctx->footer.checksum); + + if (ctx->footer.disk_type == VHD_TYPE_DYNAMIC) { + if (fseeko(ctx->fp, ctx->footer.data_offset, SEEK_SET) != 0) { + printf("Failed to seek to dynamic header\n"); + fclose(ctx->fp); + return false; + } + + if (fread(&ctx->dyn_header, 1, sizeof(VHDDynamicHeader), ctx->fp) != sizeof(VHDDynamicHeader)) { + printf("Failed to read dynamic header\n"); + fclose(ctx->fp); + return false; + } + + if (memcmp(ctx->dyn_header.cookie, VHD_DYNAMIC_COOKIE, strlen(VHD_DYNAMIC_COOKIE)) != 0) { + printf("Invalid dynamic disk header signature\n"); + fclose(ctx->fp); + return false; + } + + ctx->dyn_header.data_offset = swap64(ctx->dyn_header.data_offset); + ctx->dyn_header.bat_offset = swap64(ctx->dyn_header.bat_offset); + ctx->dyn_header.head_vers = swap32(ctx->dyn_header.head_vers); + ctx->dyn_header.max_bat_entries = swap32(ctx->dyn_header.max_bat_entries); + ctx->dyn_header.block_size = swap32(ctx->dyn_header.block_size); + + size_t bat_size = (size_t)ctx->dyn_header.max_bat_entries * sizeof(uint32_t); + + if (bat_size == 0 || bat_size > (1ULL << 30)) { + printf("Invalid BAT size\n"); + fclose(ctx->fp); + return false; + } + + ctx->bat = malloc(bat_size); + if (!ctx->bat) { + printf("Failed to allocate BAT memory\n"); + fclose(ctx->fp); + return false; + } + + if (fseeko(ctx->fp, ctx->dyn_header.bat_offset, SEEK_SET) != 0) { + printf("Failed to seek to BAT\n"); + free(ctx->bat); + fclose(ctx->fp); + return false; + } + + if (fread(ctx->bat, 1, bat_size, ctx->fp) != bat_size) { + printf("Failed to read BAT\n"); + free(ctx->bat); + fclose(ctx->fp); + return false; + } + + for (uint32_t i = 0; i < ctx->dyn_header.max_bat_entries; i++) { + ctx->bat[i] = swap32(ctx->bat[i]); + } + + ctx->sector_bitmap_size = (ctx->dyn_header.block_size / VHD_SECTOR_SIZE + 7) / 8; + ctx->sector_bitmap = malloc(ctx->sector_bitmap_size); + ctx->block_buffer = malloc(ctx->dyn_header.block_size); + + if (!ctx->sector_bitmap || !ctx->block_buffer) { + printf("Failed to allocate dynamic disk buffers\n"); + free(ctx->bat); + free(ctx->sector_bitmap); + free(ctx->block_buffer); + fclose(ctx->fp); + return false; + } + } + + return true; +} + +bool ntfs_init(NTFSContext* ctx, const char* path, const char* extract_path) { + memset(ctx, 0, sizeof(NTFSContext)); + strncpy(ctx->base_path, extract_path, sizeof(ctx->base_path) - 1); + + if (!init_directory_cache(&ctx->dir_cache)) { + return false; + } + + FILE* fp = fopen(path, "rb"); + if (!fp) return false; + + if (fseeko(fp, -512, SEEK_END) == 0) { + char signature[9] = { 0 }; + if (fread(signature, 1, 8, fp) == 8 && memcmp(signature, VHD_COOKIE, 8) == 0) { + fclose(fp); + ctx->is_vhd = true; + if (!vhd_init(&ctx->vhd, path)) { + free_directory_cache(&ctx->dir_cache); + return false; + } + } + else { + rewind(fp); + ctx->is_vhd = false; + ctx->raw.fp = fp; + } + } + + uint64_t ntfs_offset = 0; + bool found_ntfs = false; + + if (ctx->is_vhd) { + uint8_t sector[VHD_SECTOR_SIZE]; + if (ntfs_read(ctx, sector, 0, VHD_SECTOR_SIZE)) { + if (sector[0x1FE] == 0x55 && sector[0x1FF] == 0xAA) { + for (int i = 0; i < 4; i++) { + const uint8_t* part = sector + 0x1BE + (i * 16); + if (part[4] == NTFS_PARTITION_TYPE) { + uint32_t start_sector = + (uint32_t)part[8] | + ((uint32_t)part[9] << 8) | + ((uint32_t)part[10] << 16) | + ((uint32_t)part[11] << 24); + ntfs_offset = (uint64_t)start_sector * VHD_SECTOR_SIZE; + + if (ntfs_read(ctx, sector, ntfs_offset, VHD_SECTOR_SIZE) && + memcmp(sector + 3, NTFS_SIGNATURE, 8) == 0) { + found_ntfs = true; + break; + } + } + } + } + } + + if (!found_ntfs) { + const uint64_t offsets[] = { 0, 0x100000, 0x200000, 0x400000, 0x800000, 0 }; + for (int i = 0; offsets[i]; i++) { + if (ntfs_read(ctx, sector, offsets[i], VHD_SECTOR_SIZE) && + memcmp(sector + 3, NTFS_SIGNATURE, 8) == 0) { + ntfs_offset = offsets[i]; + found_ntfs = true; + break; + } + } + } + } + else { + uint8_t boot[512]; + if (ntfs_read(ctx, boot, 0, sizeof(boot)) && + boot[0] == 0xEB && boot[1] == 0x52 && boot[2] == 0x90 && + memcmp(boot + 3, NTFS_SIGNATURE, 8) == 0) { + found_ntfs = true; + } + } + + if (!found_ntfs) { + printf("No NTFS filesystem found\n"); + ntfs_close(ctx); + return false; + } + + ctx->data_start_offset = ntfs_offset; + + if (!ntfs_read(ctx, &ctx->boot, ntfs_offset, sizeof(NTFSBootSector))) { + printf("Failed to read NTFS boot sector\n"); + ntfs_close(ctx); + return false; + } + + ctx->bytes_per_cluster = (uint32_t)ctx->boot.bytes_per_sector * ctx->boot.sectors_per_cluster; + ctx->mft_offset = ntfs_offset + (ctx->boot.mft_cluster_number * ctx->bytes_per_cluster); + + if (ctx->boot.clusters_per_mft_record > 0) { + ctx->mft_record_size = ctx->boot.clusters_per_mft_record * ctx->bytes_per_cluster; + } + else { + ctx->mft_record_size = 1U << (-ctx->boot.clusters_per_mft_record); + } + + uint8_t* mft_record = malloc(ctx->mft_record_size); + if (!mft_record) { + printf("Failed to allocate memory for MFT record\n"); + ntfs_close(ctx); + return false; + } + + if (!ntfs_read(ctx, mft_record, ctx->mft_offset, ctx->mft_record_size)) { + printf("Failed to read MFT record 0\n"); + free(mft_record); + ntfs_close(ctx); + return false; + } + + const MFTRecordHeader* record = (const MFTRecordHeader*)mft_record; + if (memcmp(record->magic, "FILE", 4) != 0) { + printf("Invalid MFT record signature\n"); + free(mft_record); + ntfs_close(ctx); + return false; + } + + const uint8_t* attr = mft_record + record->attrs_offset; + while (attr < mft_record + record->bytes_used) { + const AttributeHeader* header = (const AttributeHeader*)attr; + if (header->type == 0xFFFFFFFF || header->length == 0) break; + + if (header->type == DATA_ATTR && header->name_length == 0) { + if (header->non_resident) { + uint64_t mft_data_size = header->data.non_resident.data_size; + ctx->mft_data_size = mft_data_size; + ctx->total_mft_records = mft_data_size / ctx->mft_record_size; + } + break; + } + attr += header->length; + } + + free(mft_record); + return true; +} + +bool ntfs_extract_all(NTFSContext* ctx) { + if (!create_directories(ctx->base_path)) { + printf("Failed to create output directory\n"); + return false; + } + + uint8_t* record_buffer = malloc(ctx->mft_record_size); + if (!record_buffer) { + printf("Failed to allocate MFT record buffer\n"); + return false; + } + + printf("Extraction in progress...\n"); + uint64_t current_offset = ctx->mft_offset; + uint64_t total_records = ctx->total_mft_records; + uint64_t processed_records = 0; + uint64_t extracted_records = 0; + + time_t last_update_time = time(NULL); + int last_percentage = -1; + + // Process MFT records + for (uint64_t i = 0; i < total_records; i++) { + if (!ntfs_read(ctx, record_buffer, current_offset, ctx->mft_record_size)) { + printf("Failed to read MFT record at offset 0x%llX\n", + (unsigned long long)current_offset); + break; + } + + const MFTRecordHeader* record = (const MFTRecordHeader*)record_buffer; + if (memcmp(record->magic, "FILE", 4) == 0) { + processed_records++; + if (process_mft_record(ctx, record_buffer)) { + extracted_records++; + } + } + + current_offset += ctx->mft_record_size; + + time_t current_time = time(NULL); + if (current_time != last_update_time) { + int percentage = (int)((i + 1) * 100 / total_records); + if (percentage != last_percentage) { + printf("\rProgress: %d%%", percentage); + fflush(stdout); + last_percentage = percentage; + } + last_update_time = current_time; + } + } + + printf("\rProgress: 100%%\n"); + + printf("Extraction completed.\n", + (unsigned long long)processed_records, (unsigned long long)extracted_records); + + free(record_buffer); + return true; +} + +void ntfs_close(NTFSContext* ctx) { + if (ctx->is_vhd) { + if (ctx->vhd.fp) { + fclose(ctx->vhd.fp); + free(ctx->vhd.bat); + free(ctx->vhd.sector_bitmap); + free(ctx->vhd.block_buffer); + } + } + else { + if (ctx->raw.fp) { + fclose(ctx->raw.fp); + } + } + free_directory_cache(&ctx->dir_cache); + memset(ctx, 0, sizeof(NTFSContext)); +} \ No newline at end of file diff --git a/unsegareborn/ntfs.h b/unsegareborn/ntfs.h new file mode 100644 index 0000000..3e350c2 --- /dev/null +++ b/unsegareborn/ntfs.h @@ -0,0 +1,229 @@ +#ifndef NTFS_H +#define NTFS_H + +#include +#include +#include +#include +#include +#include + +#include +#define MKDIR(path) _mkdir(path) +#define PATH_SEPARATOR "\\" +#define fseeko _fseeki64 +#define ftello _ftelli64 + +#define VHD_FOOTER_SIZE 512 +#define VHD_SECTOR_SIZE 512 +#define VHD_BAT_ENTRY_RESERVED 0xFFFFFFFF +#define MAX_PATH_LENGTH 4096 +#define NTFS_RECORD_SIZE 1024 +#define MAX_FILENAME_LENGTH 256 +#define BUFFER_SIZE 65536 +#define MFT_RECORD_MAGIC "FILE" +#define VHD_COOKIE "conectix" +#define VHD_DYNAMIC_COOKIE "cxsparse" +#define VHD_TYPE_FIXED 2 +#define VHD_TYPE_DYNAMIC 3 +#define FILE_NAME_ATTR 0x30 +#define DATA_ATTR 0x80 +#define INDEX_ROOT_ATTR 0x90 +#define INDEX_ALLOCATION_ATTR 0xA0 +#define NTFS_SIGNATURE "NTFS " +#define NTFS_PARTITION_TYPE 0x07 +#define MFT_RECORD_IN_USE 0x0001 +#define MFT_RECORD_IS_DIRECTORY 0x0002 + +typedef struct { + uint64_t ref_number; + char path[MAX_PATH_LENGTH]; +} DirectoryInfo; + +typedef struct { + DirectoryInfo* directories; + size_t capacity; + size_t count; +} DirectoryCache; + +#pragma pack(push, 1) + +typedef struct { + char cookie[8]; + uint32_t features; + uint32_t version; + uint64_t data_offset; + uint32_t timestamp; + uint32_t creator_app; + uint32_t creator_ver; + uint32_t creator_os; + uint64_t original_size; + uint64_t current_size; + uint16_t cylinder; + uint8_t heads; + uint8_t sectors; + uint32_t disk_type; + uint32_t checksum; + uint8_t unique_id[16]; + uint8_t saved_state; + uint8_t reserved[427]; +} VHDFooter; + +typedef struct { + char cookie[8]; + uint64_t data_offset; + uint64_t bat_offset; + uint32_t head_vers; + uint32_t max_bat_entries; + uint32_t block_size; + uint32_t checksum; + uint8_t parent_id[16]; + uint32_t parent_timestamp; + uint32_t reserved1; + uint16_t parent_name[256]; + uint8_t parent_loc[8][512]; + uint8_t reserved2[256]; +} VHDDynamicHeader; + +typedef struct { + uint8_t jump[3]; + uint8_t signature[8]; + uint16_t bytes_per_sector; + uint8_t sectors_per_cluster; + uint16_t reserved_sectors; + uint8_t always_zero1[3]; + uint16_t not_used1; + uint8_t media_descriptor; + uint16_t always_zero2; + uint16_t sectors_per_track; + uint16_t number_of_heads; + uint32_t hidden_sectors; + uint32_t not_used2; + uint32_t not_used3; + uint64_t total_sectors; + uint64_t mft_cluster_number; + uint64_t mft_mirror_cluster_number; + int8_t clusters_per_mft_record; + uint8_t not_used4[3]; + int8_t clusters_per_index_record; + uint8_t not_used5[3]; + uint64_t volume_serial_number; + uint32_t checksum; +} NTFSBootSector; + +typedef struct { + char magic[4]; + uint16_t usa_offset; + uint16_t usa_count; + uint64_t lsn; + uint16_t sequence_number; + uint16_t link_count; + uint16_t attrs_offset; + uint16_t flags; + uint32_t bytes_used; + uint32_t bytes_allocated; + uint64_t base_ref; + uint16_t next_attr_id; + uint16_t record_number; + uint16_t usa_value; +} MFTRecordHeader; + +typedef struct { + uint32_t type; + uint32_t length; + uint8_t non_resident; + uint8_t name_length; + uint16_t name_offset; + uint16_t flags; + uint16_t attribute_id; + union { + struct { + uint32_t value_length; + uint16_t value_offset; + uint16_t flags; + } resident; + struct { + uint64_t lowest_vcn; + uint64_t highest_vcn; + uint16_t mapping_pairs_offset; + uint16_t compression_unit; + uint32_t padding; + uint64_t allocated_size; + uint64_t data_size; + uint64_t initialized_size; + uint64_t compressed_size; + } non_resident; + } data; +} AttributeHeader; + +typedef struct { + uint64_t parent_directory; + uint64_t creation_time; + uint64_t modification_time; + uint64_t mft_modification_time; + uint64_t access_time; + uint64_t allocated_size; + uint64_t real_size; + uint32_t flags; + uint32_t reparse_value; + uint8_t name_length; + uint8_t namespace; + uint16_t name[256]; +} FileNameAttribute; + +typedef struct { + uint64_t offset; + uint64_t length; +} DataRun; + +#pragma pack(pop) + +typedef struct { + FILE* fp; + VHDFooter footer; + VHDDynamicHeader dyn_header; + uint32_t* bat; + uint32_t sector_bitmap_size; + uint8_t* sector_bitmap; + uint8_t* block_buffer; +} VHDContext; + +typedef struct { + char name[MAX_FILENAME_LENGTH]; + uint64_t parent_ref; + bool is_directory; + bool valid; +} FileInfo; + +typedef struct { + FILE* fp; +} RawNTFSContext; + +typedef struct { + union { + VHDContext vhd; + RawNTFSContext raw; + }; + bool is_vhd; + NTFSBootSector boot; + uint32_t bytes_per_cluster; + uint64_t mft_offset; + uint32_t mft_record_size; + uint64_t mft_data_size; + uint64_t total_mft_records; + char base_path[MAX_PATH_LENGTH]; + DirectoryCache dir_cache; + uint64_t data_start_offset; +} NTFSContext; + +bool ntfs_init(NTFSContext* ctx, const char* vhd_path, const char* extract_path); +bool ntfs_extract_all(NTFSContext* ctx); +void ntfs_close(NTFSContext* ctx); +static bool vhd_read(VHDContext* ctx, void* buffer, uint64_t offset, size_t size); +static bool create_directories(const char* path); +static void convert_name_to_ascii(const uint16_t* utf16_name, int name_length, char* ascii_name); +static bool add_directory_to_cache(DirectoryCache* cache, uint64_t ref_number, const char* path); +static bool vhd_init(VHDContext* ctx, const char* filename); +static bool ntfs_read(NTFSContext* ctx, void* buffer, uint64_t offset, size_t size); + +#endif // NTFS_H \ No newline at end of file diff --git a/unsegareborn/unsegareborn.vcxproj b/unsegareborn/unsegareborn.vcxproj new file mode 100644 index 0000000..c3cdfbe --- /dev/null +++ b/unsegareborn/unsegareborn.vcxproj @@ -0,0 +1,150 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {4b2c8a0e-0be1-4ed1-8108-6124c58f3a46} + unsegareturn + 10.0 + unsegareborn + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + "D:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\um\x64\WS2_32.Lib";"D:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\um\x64\User32.Lib";"D:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\um\x64\Crypt32.Lib";"C:\Users\User\vcpkg\buildtrees\openssl\x64-windows-rel\libssl_static.lib";"C:\Users\User\vcpkg\buildtrees\openssl\x64-windows-rel\libcrypto_static.lib";%(AdditionalDependencies) + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + true + true + "D:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\um\x64\WS2_32.Lib";"D:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\um\x64\User32.Lib";"D:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\um\x64\Crypt32.Lib";"C:\Users\User\vcpkg\buildtrees\openssl\x64-windows-rel\libssl_static.lib";"C:\Users\User\vcpkg\buildtrees\openssl\x64-windows-rel\libcrypto_static.lib";%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/unsegareborn/unsegareborn.vcxproj.filters b/unsegareborn/unsegareborn.vcxproj.filters new file mode 100644 index 0000000..009fe1f --- /dev/null +++ b/unsegareborn/unsegareborn.vcxproj.filters @@ -0,0 +1,48 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/unsegareborn/unsegareborn.vcxproj.user b/unsegareborn/unsegareborn.vcxproj.user new file mode 100644 index 0000000..0f14913 --- /dev/null +++ b/unsegareborn/unsegareborn.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file