This commit is contained in:
Eren
2025-06-24 13:24:45 +03:00
parent 9735cb2cf5
commit 27aa9e2b54
16 changed files with 2759 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
cmake_minimum_required(VERSION 3.16)
project(unsegaREBORN C)
set(CMAKE_C_STANDARD 11)
option(BUILD_STATIC "Build a static executable" OFF)
if(BUILD_STATIC)
set(OPENSSL_USE_STATIC_LIBS TRUE)
set(OPENSSL_MSVC_STATIC_RT TRUE)
if(WIN32)
set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a)
else()
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
endif()
endif()
find_package(OpenSSL REQUIRED)
add_library(unsega STATIC
src/crypto.c
src/keys.c
include/crypto.h
src/exfat.c
include/exfat.h
src/ntfs.c
include/ntfs.h
src/bootid.c
include/bootid.h
include/common.h
)
target_include_directories(unsega PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(unsega PUBLIC OpenSSL::Crypto)
if (MSVC)
target_compile_definitions(unsega PUBLIC _CRT_SECURE_NO_WARNINGS)
if (MSVC_VERSION LESS 1900)
target_compile_definitions(unsega PUBLIC snprintf=_snprintf)
endif()
elseif(UNIX)
target_compile_definitions(unsega PUBLIC _FILE_OFFSET_BITS=64)
endif()
add_executable(unsegareborn src/main.c)
target_link_libraries(unsegareborn PRIVATE unsega)
if(BUILD_STATIC)
if(MSVC)
set_property(TARGET unsegareborn PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set_property(TARGET unsega PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
target_link_libraries(unsegareborn PRIVATE
ws2_32
crypt32
bcrypt
Advapi32
)
target_link_options(unsegareborn PRIVATE /INCREMENTAL:NO)
else()
target_link_options(unsegareborn PRIVATE -static)
if(UNIX AND NOT APPLE)
target_link_libraries(unsegareborn PRIVATE dl pthread)
endif()
endif()
endif()
install(TARGETS unsegareborn RUNTIME DESTINATION bin)
+60
View File
@@ -0,0 +1,60 @@
# unsegaREBORN
An all-in-one tool that unpacks (and optionally extracts) arcade SEGA images.
---
## Features
* Decrypts update/app containers
* Extracts the embedded
* NTFS archives (with nested VHDs)
* exFAT archives (cluster-by-cluster walker)
* Cross-platform (Windows / Linux / macOS)
Uses plain C11 + OpenSSL, no fancy dependencies.
* Can be built fully static (`--static`) if you need a drop-in binary.
---
## Building
### Unix-like (Linux / macOS)
```bash
./build.sh # release build
./build.sh --debug # debug symbols, no optimisation
./build.sh --static # link everything statically
### Windows (MSVC or MinGW)
build.bat :: release build
build.bat --debug :: debug build
build.bat --static :: static CRT + OpenSSL
## Usage
unsegareborn [-no] <image1> [image2 …]
-no just decrypt, do NOT auto-extract the embedded file system
The program writes a decrypted .ntfs or .exfat file next to the input
and, unless -no is given, immediately unpacks its contents into a folder
with the same stem.
You can also just drag and drop the image(s) on the program. ("-no" flag is disabled by default)
## Where do the keys come from?
They were all collected from leaks over time.
If you have additional keys and want to contribute, feel free to open a PR.
If you are a gatekeeper, you can just use the built in custom key function.
## License
UNLICENSE
## Caveats / TODO
No APM3 support. Maybe will be added when I feel like it.
Child VHD won't be processed for app files. If the output VHD number is not zero (e.g. internal_1, internal_2) you are on your own.
+24
View File
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>
+36
View File
@@ -0,0 +1,36 @@
@echo off
REM Build script for unsegaREBORN on Windows
set BUILD_TYPE=Release
set STATIC_BUILD=OFF
:parse_args
if "%1"=="" goto build
if "%1"=="--static" (
set STATIC_BUILD=ON
echo Building static executable...
)
if "%1"=="--debug" (
set BUILD_TYPE=Debug
echo Building debug version...
)
shift
goto parse_args
:build
if not exist build mkdir build
cd build
echo Configuring with CMake...
cmake -DBUILD_STATIC=%STATIC_BUILD% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% ..
echo Building...
cmake --build . --config %BUILD_TYPE%
echo.
echo Build complete!
echo Executable: build\%BUILD_TYPE%\unsegareborn.exe
if "%STATIC_BUILD%"=="ON" (
echo Built as static executable - no external DLLs required
)
cd ..
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
# Build script for unsegaREBORN on Linux/macOS
BUILD_TYPE=Release
STATIC_BUILD=OFF
while [[ $# -gt 0 ]]; do
case $1 in
--static)
STATIC_BUILD=ON
echo "Building static executable..."
shift
;;
--debug)
BUILD_TYPE=Debug
echo "Building debug version..."
shift
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 [--static] [--debug]"
exit 1
;;
esac
done
mkdir -p build
cd build
echo "Configuring with CMake..."
cmake -DBUILD_STATIC=$STATIC_BUILD -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
echo "Building..."
cmake --build . --config $BUILD_TYPE
cd ..
echo ""
echo "Build complete!"
echo "Executable: build/unsegareborn"
if [ "$STATIC_BUILD" = "ON" ]; then
echo "Built as static executable - no external dependencies required"
fi
+74
View File
@@ -0,0 +1,74 @@
#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
+50
View File
@@ -0,0 +1,50 @@
#ifndef COMMON_H
#define COMMON_H
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <direct.h>
#define MKDIR(path) _mkdir(path)
#define PATH_SEPARATOR "\\"
#define STRDUP _strdup
#define FSEEKO _fseeki64
#define FTELLO _ftelli64
#else
#include <sys/stat.h>
#include <sys/types.h>
#define MKDIR(path) mkdir((path), 0755)
#define PATH_SEPARATOR "/"
#define STRDUP strdup
#define FSEEKO fseeko
#define FTELLO ftello
#endif
#define MAX_PATH_LENGTH 256
#define MAX_FILENAME_LENGTH 256
#ifndef min
#define min(a,b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif
#define SNPRINTF snprintf
#ifdef _WIN32
#ifdef _MSC_VER
#define STRCPY_S(dst, size, src) strcpy_s(dst, size, src)
#define STRCAT_S(dst, size, src) strcat_s(dst, size, src)
#else
#define STRCPY_S(dst, size, src) do { strncpy(dst, src, (size)-1); (dst)[(size)-1] = '\0'; } while(0)
#define STRCAT_S(dst, size, src) strncat(dst, src, (size) - strlen(dst) - 1)
#endif
#else
#define STRCPY_S(dst, size, src) do { strncpy(dst, src, (size)-1); (dst)[(size)-1] = '\0'; } while(0)
#define STRCAT_S(dst, size, src) strncat(dst, src, (size) - strlen(dst) - 1)
#endif
#endif /* COMMON_H */
+54
View File
@@ -0,0 +1,54 @@
#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;
typedef struct {
const char* game_id;
uint8_t key[16];
uint8_t iv[16];
bool has_iv;
} GameKeyEntry;
extern const GameKeyEntry game_keys[];
extern const size_t game_keys_count;
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
+103
View File
@@ -0,0 +1,103 @@
#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];
} 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
+215
View File
@@ -0,0 +1,215 @@
#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 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);
#endif // NTFS_H
+7
View File
@@ -0,0 +1,7 @@
#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);
}
+116
View File
@@ -0,0 +1,116 @@
#include "crypto.h"
#include "common.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;
}
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];
SNPRINTF(filename, sizeof(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;
}
}
+300
View File
@@ -0,0 +1,300 @@
#include "exfat.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <locale.h>
#include <wchar.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 bool is_safe_path(const char* path) {
if (!path) return false;
if (strstr(path, "..") != NULL) return false;
if (strstr(path, "./") != NULL) return false;
if (strstr(path, ".\\") != NULL) return false;
#ifndef _WIN32
if (path[0] == '/') return false;
#endif
return true;
}
static bool combine_path(char* dest, size_t dest_size, const char* dir, const char* name) {
if (!dest || dest_size == 0 || !dir || !name) {
return false;
}
if (!is_safe_path(name)) {
return false;
}
size_t dir_len = strlen(dir);
size_t name_len = strlen(name);
size_t sep_len = (dir_len > 0 && dir[dir_len - 1] != '/' && dir[dir_len - 1] != '\\') ? 1 : 0;
if (dir_len + sep_len + name_len + 1 > dest_size) {
return false;
}
STRCPY_S(dest, dest_size, dir);
if (sep_len) {
STRCAT_S(dest, dest_size, PATH_SEPARATOR);
}
STRCAT_S(dest, dest_size, name);
return true;
}
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) {
// shouldn't cut off the wide char
full_name_unicode[pos++] = (wchar_t)name_entry->file_name[j];
}
}
}
full_name_unicode[pos] = L'\0';
// convert UTF-16 filename to multibyte
#ifdef _WIN32
_locale_t locale = _create_locale(LC_ALL, "");
_wcstombs_l(full_name, full_name_unicode, MAX_FILENAME_LENGTH, locale);
_free_locale(locale);
#else
wcstombs(full_name, full_name_unicode, MAX_FILENAME_LENGTH);
#endif
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];
if (!combine_path(full_path, sizeof(full_path), output_dir, file_info.name)) {
fprintf(stderr, "Warning: Invalid or too long path, skipping: %s/%s\n", output_dir, file_info.name);
continue;
}
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;
}
}
+224
View File
@@ -0,0 +1,224 @@
#include "crypto.h"
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 }
};
const size_t game_keys_count = sizeof(game_keys) / sizeof(game_keys[0]);
+500
View File
@@ -0,0 +1,500 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.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* decrypted_buffer = malloc(BUFFER_SIZE);
uint8_t iv[16];
uint8_t page_iv[16];
uint8_t key[16];
if (!bootid_bytes || !read_buffer || !decrypted_buffer) {
printf("Memory allocation failed\n");
free(bootid_bytes);
free(read_buffer);
free(decrypted_buffer);
return 1;
}
FILE* file = fopen(path, "rb");
if (!file) {
perror(path);
free(bootid_bytes);
free(read_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(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(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(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(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(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(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) {
perror("fseek");
fclose(file);
free(bootid_bytes);
free(read_buffer);
free(decrypted_buffer);
return 1;
}
size_t read_size = fread(read_buffer, 1, PAGE_SIZE, file);
if (read_size != PAGE_SIZE) {
perror("fread");
fclose(file);
free(bootid_bytes);
free(read_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(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(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(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) {
perror(output_filename);
free(output_filename);
fclose(file);
free(bootid_bytes);
free(read_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) {
perror("fseek");
fclose(file);
fclose(output_file);
free(bootid_bytes);
free(read_buffer);
free(decrypted_buffer);
free(output_filename);
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(decrypted_buffer);
free(output_filename);
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) {
if (feof(file)) {
printf("\nUnexpected end of file\n");
} else {
perror("\nread_chunk");
}
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 = total_bytes_read + offset;
calculate_page_iv(file_offset, iv, page_iv); // Generate unique IV for each 4KB page
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) {
perror("\nfwrite");
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); // Extra spaces to clear line
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);
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(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;
}
+884
View File
@@ -0,0 +1,884 @@
#include "ntfs.h"
#include <time.h>
#include <locale.h>
#include <wchar.h>
#define BUFFER_SIZE 65536
static bool vhd_read(VHDContext* ctx, void* buffer, uint64_t offset, size_t size);
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 is_safe_path(const char* path) {
if (!path) return false;
if (strstr(path, "..") != NULL) return false;
if (strstr(path, "./") != NULL) return false;
if (strstr(path, ".\\") != NULL) return false;
#ifndef _WIN32
if (path[0] == '/') return false;
#endif
return true;
}
static bool create_directories(const char* path) {
if (!is_safe_path(path)) {
return false;
}
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, size_t ascii_buffer_size) {
if (!ascii_name || ascii_buffer_size == 0) return;
int real_name_length = min(name_length, (int)(ascii_buffer_size - 1));
real_name_length = min(real_name_length, MAX_FILENAME_LENGTH - 1);
#ifdef _WIN32
_locale_t locale = _create_locale(LC_ALL, "");
size_t converted = _wcstombs_l(ascii_name, (const wchar_t*)utf16_name, real_name_length, locale);
_free_locale(locale);
if (converted == (size_t)-1) {
ascii_name[0] = '\0';
return;
}
#else
wchar_t temp[MAX_FILENAME_LENGTH];
for (int i = 0; i < real_name_length && i < MAX_FILENAME_LENGTH - 1; i++) {
temp[i] = (wchar_t)utf16_name[i];
}
temp[real_name_length] = L'\0';
size_t converted = wcstombs(ascii_name, temp, ascii_buffer_size - 1);
if (converted == (size_t)-1) {
ascii_name[0] = '\0';
return;
}
#endif
ascii_name[min(converted, ascii_buffer_size - 1)] = '\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, sizeof(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(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, sizeof(filename));
parent_ref = fname->parent_directory & 0xFFFFFFFFFFFF;
got_filename = true;
break;
}
}
attr += header->length;
}
if (!got_filename || filename[0] == '$') {
return true;
}
if (!is_safe_path(filename)) {
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) {
free_directory_cache(&ctx->dir_cache);
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;
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));
}