mirror of
https://github.com/Elpisdev/unsegaREBORN.git
synced 2026-09-27 17:28:06 +03:00
Delete unsegareborn directory
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
#include "bootid.h"
|
||||
#include <stdio.h>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
#ifndef BOOTID_H
|
||||
#define BOOTID_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
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
|
||||
@@ -1,11 +0,0 @@
|
||||
#ifndef COMMON_H
|
||||
|
||||
#include <direct.h>
|
||||
|
||||
#define PATH_SEPARATOR "\\"
|
||||
#define MAX_PATH_LENGTH 256
|
||||
#define MAX_FILENAME_LENGTH 256
|
||||
#define MKDIR(path) _mkdir(path)
|
||||
|
||||
#endif // !COMMON_H
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
#include "crypto.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/aes.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#ifndef CRYPTO_H
|
||||
#define CRYPTO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
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
|
||||
@@ -1,268 +0,0 @@
|
||||
#include "exfat.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <locale.h>
|
||||
|
||||
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 uint32_t get_next_cluster(ExfatContext* ctx, uint32_t cluster) {
|
||||
uint32_t next = ctx->fat[cluster];
|
||||
if (next >= 0xFFFFFFF8) {
|
||||
return 0; // end-of-chain
|
||||
}
|
||||
if (next == 0)
|
||||
{
|
||||
return cluster + 1; // if cluster in FAT is 0, it means the cluster heap is continous (exfat only)
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
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 && current_cluster != 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 = get_next_cluster(ctx, current_cluster);
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
fclose(out);
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool process_directory(ExfatContext* ctx, uint32_t start_cluster, const char* output_dir) {
|
||||
uint8_t* cluster_buffer = malloc(ctx->bytes_per_cluster);
|
||||
if (!cluster_buffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t current_cluster = start_cluster;
|
||||
bool finished = false;
|
||||
|
||||
while (!finished && current_cluster != 0) {
|
||||
if (!read_cluster(ctx, current_cluster, cluster_buffer)) {
|
||||
free(cluster_buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t entries_per_cluster = ctx->bytes_per_cluster / EXFAT_ENTRY_SIZE;
|
||||
uint8_t* entry_ptr = cluster_buffer;
|
||||
for (uint32_t i = 0; i < entries_per_cluster; ) {
|
||||
uint8_t entry_type = *entry_ptr;
|
||||
if (entry_type == EXFAT_ENTRY_EOD) {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (entry_type == EXFAT_ENTRY_FILE) {
|
||||
ExfatFileEntry* file_entry = (ExfatFileEntry*)entry_ptr;
|
||||
ExfatStreamEntry* stream_entry = (ExfatStreamEntry*)(entry_ptr + EXFAT_ENTRY_SIZE);
|
||||
if (stream_entry->entry_type != EXFAT_ENTRY_STREAM) {
|
||||
i++;
|
||||
entry_ptr += EXFAT_ENTRY_SIZE;
|
||||
continue;
|
||||
}
|
||||
int total_name_chars = stream_entry->name_length;
|
||||
int num_name_entries = (total_name_chars + 14) / 15;
|
||||
|
||||
char full_name[MAX_FILENAME_LENGTH];
|
||||
wchar_t full_name_unicode[MAX_FILENAME_LENGTH];
|
||||
int pos = 0;
|
||||
uint8_t* name_entry_ptr = entry_ptr + EXFAT_ENTRY_SIZE * 2;
|
||||
for (int k = 0; k < num_name_entries; k++) {
|
||||
ExfatFileNameEntry* name_entry = (ExfatFileNameEntry*)(name_entry_ptr + k * EXFAT_ENTRY_SIZE);
|
||||
int chars_in_this_entry = (total_name_chars - k * 15 < 15) ? (total_name_chars - k * 15) : 15;
|
||||
for (int j = 0; j < chars_in_this_entry; j++) {
|
||||
if (pos < MAX_FILENAME_LENGTH - 1) {
|
||||
// you shouldn't cut of the wide char
|
||||
full_name_unicode[pos++] = (wchar_t)name_entry->file_name[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
full_name_unicode[pos] = L'\0';
|
||||
|
||||
// just convert it
|
||||
_locale_t locale = _create_locale(LC_ALL, "");
|
||||
_wcstombs_l(full_name, full_name_unicode, MAX_FILENAME_LENGTH, locale);
|
||||
|
||||
ExfatFileInfo file_info;
|
||||
memset(&file_info, 0, sizeof(file_info));
|
||||
strncpy(file_info.name, full_name, MAX_PATH_LENGTH - 1);
|
||||
file_info.name[MAX_PATH_LENGTH - 1] = '\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);
|
||||
}
|
||||
|
||||
int total_entries = 2 + num_name_entries;
|
||||
i += total_entries;
|
||||
entry_ptr += EXFAT_ENTRY_SIZE * total_entries;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
i++;
|
||||
entry_ptr += EXFAT_ENTRY_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
if (!finished) {
|
||||
current_cluster = get_next_cluster(ctx, current_cluster);
|
||||
}
|
||||
}
|
||||
|
||||
free(cluster_buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
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_sector = (1 << ctx->boot_sector.bytes_per_sector_shift);
|
||||
ctx->bytes_per_cluster = ctx->bytes_per_sector * (1 << ctx->boot_sector.sectors_per_cluster_shift);
|
||||
ctx->cluster_heap_offset_bytes = ctx->boot_sector.cluster_heap_offset * ctx->bytes_per_sector;
|
||||
|
||||
ctx->fat_offset_bytes = ctx->boot_sector.fat_offset * ctx->bytes_per_sector;
|
||||
ctx->fat_length_bytes = ctx->boot_sector.fat_length * ctx->bytes_per_sector;
|
||||
|
||||
ctx->fat = malloc(ctx->fat_length_bytes);
|
||||
if (!ctx->fat) {
|
||||
fclose(ctx->fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(ctx->fp, ctx->fat_offset_bytes, SEEK_SET) != 0) {
|
||||
free(ctx->fat);
|
||||
fclose(ctx->fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fread(ctx->fat, 1, ctx->fat_length_bytes, ctx->fp) != ctx->fat_length_bytes) {
|
||||
free(ctx->fat);
|
||||
fclose(ctx->fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (ctx->fat) {
|
||||
free(ctx->fat);
|
||||
ctx->fat = NULL;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
#ifndef EXFAT_H
|
||||
#define EXFAT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "common.h"
|
||||
|
||||
#define EXFAT_ENTRY_SIZE 32
|
||||
|
||||
#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]; // please work
|
||||
} 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_sector;
|
||||
uint32_t bytes_per_cluster;
|
||||
uint32_t cluster_heap_offset_bytes;
|
||||
uint32_t fat_offset_bytes;
|
||||
uint32_t fat_length_bytes;
|
||||
uint32_t* fat;
|
||||
} 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
|
||||
@@ -1,517 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "bootid.h"
|
||||
#include "crypto.h"
|
||||
#include "exfat.h"
|
||||
#include "ntfs.h"
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/aes.h>
|
||||
|
||||
#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] <input_file1> [<input_file2> ...]\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;
|
||||
}
|
||||
@@ -1,837 +0,0 @@
|
||||
#include "ntfs.h"
|
||||
#include <time.h>
|
||||
#include <locale.h>
|
||||
#define BUFFER_SIZE 65536
|
||||
|
||||
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) {
|
||||
// just convert it
|
||||
_locale_t locale = _create_locale(LC_ALL, "");
|
||||
int real_name_length = min(name_length, MAX_FILENAME_LENGTH - 1);
|
||||
_wcstombs_l(ascii_name, utf16_name, real_name_length, locale);
|
||||
ascii_name[real_name_length] = '\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");
|
||||
|
||||
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));
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
#ifndef NTFS_H
|
||||
#define NTFS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "common.h"
|
||||
|
||||
#define fseeko _fseeki64
|
||||
#define ftello _ftelli64
|
||||
|
||||
#define VHD_FOOTER_SIZE 512
|
||||
#define VHD_SECTOR_SIZE 512
|
||||
#define VHD_BAT_ENTRY_RESERVED 0xFFFFFFFF
|
||||
#define NTFS_RECORD_SIZE 1024
|
||||
#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
|
||||
@@ -1,150 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{4b2c8a0e-0be1-4ed1-8108-6124c58f3a46}</ProjectGuid>
|
||||
<RootNamespace>unsegareturn</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>unsegareborn</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>"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)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>"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)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="bootid.c" />
|
||||
<ClCompile Include="crypto.c" />
|
||||
<ClCompile Include="exfat.c" />
|
||||
<ClCompile Include="main.c" />
|
||||
<ClCompile Include="ntfs.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="bootid.h" />
|
||||
<ClInclude Include="crypto.h" />
|
||||
<ClInclude Include="exfat.h" />
|
||||
<ClInclude Include="ntfs.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="bootid.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="crypto.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="main.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="exfat.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ntfs.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="crypto.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="bootid.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="exfat.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ntfs.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
Reference in New Issue
Block a user