major rework

This commit is contained in:
Elpisdev
2026-02-06 01:07:35 +03:00
commit 7099589b02
26 changed files with 7034 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
* text=auto eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
+66
View File
@@ -0,0 +1,66 @@
name: build
on:
push:
tags: ['[0-9]*']
jobs:
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
install: mingw-w64-x86_64-clang mingw-w64-x86_64-lld
- name: inject keys
shell: bash
env:
KEYS: ${{ secrets.KEYS_INC }}
run: printf '%s\n' "$KEYS" > include/keys.inc
- name: build
shell: msys2 {0}
run: sh build.cmd
- uses: actions/upload-artifact@v4
with:
name: win-x64
path: build/unsegareborn-win-x64.exe
linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: deps
run: |
sudo apt-get update
sudo apt-get install -y clang lld
- name: inject keys
env:
KEYS: ${{ secrets.KEYS_INC }}
run: printf '%s\n' "$KEYS" > include/keys.inc
- run: sed 's/\r$//' build.cmd | sh
- uses: actions/upload-artifact@v4
with:
name: linux-x64
path: build/unsegareborn-linux-x64
release:
needs: [windows, linux]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
name: win-x64
path: win
- uses: actions/download-artifact@v4
with:
name: linux-x64
path: linux
- uses: softprops/action-gh-release@v2
with:
files: |
win/unsegareborn-win-x64.exe
linux/unsegareborn-linux-x64
+7
View File
@@ -0,0 +1,7 @@
# build outputs
build/
*.o
*.obj
# keys (use keys.inc.example as template)
include/keys.inc
+75
View File
@@ -0,0 +1,75 @@
# unsegaREBORN
SEGA arcade image toolkit
## features
- APP/OPT/APM3 decryption
- NTFS/exFAT support
- VHD support (fixed, dynamic, differencing)
- stream directly from encrypted image (no temp files)
- preserved timestamps
- AES-NI accelerated with software fallback
## build
```
build.cmd # windows
sh build.cmd # linux
```
output: `build/unsegareborn-{platform}-x64[.exe]`
## usage
```
unsegareborn [flags] <files>
```
flags:
- `-o dir` output directory
- `-n` decrypt only, skip extraction
- `-w` write intermediate .ntfs/.exfat files
- `-p file` parent for differencing VHD
- `-s` silent
- `-v` verbose
- `-vn` version
drag and drop works on windows
## keys
prebuilt releases include keys. source does not.
to build from source:
1. copy `include/keys.inc.example` to `include/keys.inc`
2. add your keys in the format shown
format:
```c
{"SDEZ", {0xd1,0x36,...}, {0xc4,0x84,...}, true},
```
## platforms
| platform | method |
|-------------|-------------------------|
| win x64 | native (ntdll only) |
| win arm64 | x64 emulation |
| linux x64 | native (static no libc) |
| linux arm64 | box64/qemu |
| macos | wine (untested) |
## release
push version tag:
```
git tag 2026020501
git push origin 2026020501
```
ci builds both platforms and creates a github release with binaries
## license
UNLICENSE
+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/>
+76
View File
@@ -0,0 +1,76 @@
:<<"::BATCH_SECTION"
@echo off
goto :WINDOWS
::BATCH_SECTION
#!/bin/sh
set -e
mkdir -p build
SRC="src/lib.c src/main.c src/crypto.c src/keys.c src/exfat.c src/ntfs.c src/stream.c src/aes.c"
# Check for clang
command -v clang >/dev/null 2>&1 || { echo "error: clang not found"; exit 1; }
# Detect OS
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*)
OUT="build/unsegareborn-win-x64.exe"
CFLAGS="-target x86_64-pc-windows-gnu -Oz -maes -msse4.1 -I include -fno-asynchronous-unwind-tables -fno-ident -ffunction-sections -fdata-sections -flto -ffreestanding -fno-builtin -fno-stack-protector -nostdlib -fno-unwind-tables -fno-exceptions -fmerge-all-constants -fno-addrsig"
LDFLAGS="-fuse-ld=lld -Wl,--gc-sections -Wl,--icf=all -Wl,-e,_start -Wl,--subsystem,console -Wl,-s -Wl,--lto-Oz -L/mingw64/lib"
LIBS="-lntdll"
;;
*)
OUT="build/unsegareborn-linux-x64"
CFLAGS="-Oz -maes -msse4.1 -I include -fno-asynchronous-unwind-tables -fno-ident -ffunction-sections -fdata-sections -flto -ffreestanding -fno-builtin -fno-stack-protector -nostdlib -fno-unwind-tables -fno-exceptions -fmerge-all-constants -fno-addrsig"
LDFLAGS="-fuse-ld=lld -Wl,--gc-sections -Wl,--icf=all -Wl,-e,_start -Wl,-s -Wl,--lto-O2 -static"
LIBS=""
;;
esac
echo "building..."
clang $CFLAGS $LDFLAGS -o $OUT $SRC $LIBS
echo "done: $OUT ($(stat -c%s "$OUT" 2>/dev/null || stat -f%z "$OUT") bytes)"
exit 0
:WINDOWS
setlocal
if not exist build mkdir build
set SRC=src\lib.c src\main.c src\crypto.c src\keys.c src\exfat.c src\ntfs.c src\stream.c src\aes.c
:: Find Clang
where clang >nul 2>&1 || (
for %%P in (
"C:\Program Files\LLVM\bin"
"C:\LLVM\bin"
"%LOCALAPPDATA%\LLVM\bin"
"C:\msys64\clang64\bin"
"C:\msys64\mingw64\bin"
) do if exist "%%~P\clang.exe" set "PATH=%%~P;%PATH%"& goto :clang_found
echo error: clang not found - install LLVM or add to PATH
exit /b 1
)
:clang_found
:: Find libraries
set LIBPATH=
for %%L in (
"C:\msys64\mingw64\lib"
"C:\msys64\clang64\lib"
"C:\msys64\ucrt64\lib"
) do if exist "%%~L\libntdll.a" set "LIBPATH=-L%%~L"& goto :lib_found
:lib_found
set CFLAGS=-target x86_64-pc-windows-gnu -Oz -maes -msse4.1 -I include -fno-asynchronous-unwind-tables -fno-ident -ffunction-sections -fdata-sections -flto -ffreestanding -fno-builtin -fno-stack-protector -nostdlib -fno-unwind-tables -fno-exceptions -fmerge-all-constants -fno-addrsig
set LDFLAGS=-fuse-ld=lld -Wl,--gc-sections -Wl,--icf=all -Wl,-e,_start -Wl,--subsystem,console -Wl,-s -Wl,--lto-Oz %LIBPATH%
echo building...
clang %CFLAGS% %LDFLAGS% -o build\unsegareborn-win-x64.exe %SRC% -lntdll
if exist build\unsegareborn-win-x64.exe (
for %%F in (build\unsegareborn-win-x64.exe) do echo done: %%~nxF ^(%%~zF bytes^)
) else (
echo build failed
exit /b 1
)
exit /b 0
+20
View File
@@ -0,0 +1,20 @@
#ifndef AES_H
#define AES_H
#include "lib.h"
#define AES_BLOCKLEN 16
typedef struct {
uint8_t round_keys[176] __attribute__((aligned(16)));
uint8_t dec_keys[160] __attribute__((aligned(16)));
uint8_t iv[16] __attribute__((aligned(16)));
} AES_ctx;
void AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv);
void AES_ctx_set_iv(AES_ctx* ctx, const uint8_t* iv);
void AES_CBC_decrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t len);
void AES_CBC_encrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t len);
int aes_hw_supported(void);
#endif
+71
View File
@@ -0,0 +1,71 @@
#ifndef BOOTID_H
#define BOOTID_H
#include "lib.h"
extern const uint8_t BOOTID_KEY[16];
extern const uint8_t BOOTID_IV[16];
enum ContainerType {
CONTAINER_TYPE_OS = 0x00,
CONTAINER_TYPE_APP = 0x01,
CONTAINER_TYPE_OPTION = 0x02
};
#define IS_APM3_OPTION(game_id) (memcmp(game_id, "SDEM", 4) == 0)
#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)
static inline 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);
}
#endif
+254
View File
@@ -0,0 +1,254 @@
#ifndef COMMON_H
#define COMMON_H
#include "lib.h"
#define FSEEKO fseeko
#define FTELLO ftello
#ifdef PLATFORM_WINDOWS
static inline int utf8_to_wide(const char* utf8, WCHAR* wide, int wide_len) {
if (!utf8 || !wide || wide_len <= 0) return 0;
int out = 0;
const unsigned char* s = (const unsigned char*)utf8;
while (*s && out < wide_len - 1) {
uint32_t cp;
if (s[0] < 0x80) { cp = s[0]; s += 1; }
else if ((s[0] & 0xE0) == 0xC0 && (s[1] & 0xC0) == 0x80) { cp = ((s[0] & 0x1F) << 6) | (s[1] & 0x3F); s += 2; }
else if ((s[0] & 0xF0) == 0xE0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) { cp = ((s[0] & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F); s += 3; }
else if ((s[0] & 0xF8) == 0xF0 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) { cp = ((s[0] & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F); s += 4; }
else { cp = '?'; s += 1; }
if (cp <= 0xFFFF) wide[out++] = (WCHAR)cp;
else if (cp <= 0x10FFFF && out < wide_len - 2) { cp -= 0x10000; wide[out++] = (WCHAR)(0xD800 | (cp >> 10)); wide[out++] = (WCHAR)(0xDC00 | (cp & 0x3FF)); }
}
wide[out] = 0;
return out;
}
static inline FILE* fopen_utf8(const char* path, const char* mode) {
return fopen(path, mode);
}
static inline FILE* fopen_prealloc_utf8(const char* path, uint64_t size) {
WCHAR wpath[1024];
if (!utf8_to_wide(path, wpath, 1024)) return NULL;
return lib_wfopen_prealloc(wpath, size);
}
static inline int mkdir_utf8(const char* path) {
WCHAR wpath[1024];
if (!utf8_to_wide(path, wpath, 1024)) return -1;
return _wmkdir(wpath);
}
static inline int remove_utf8(const char* path) {
WCHAR wpath[1024];
if (!utf8_to_wide(path, wpath, 1024)) return -1;
return _wremove(wpath);
}
#define FOPEN fopen_utf8
#define FOPEN_PREALLOC fopen_prealloc_utf8
#define FWRITE_DIRECT lib_fwrite_direct
#define MKDIR(path) mkdir_utf8(path)
#define REMOVE remove_utf8
#define RMDIR(path) _rmdir(path)
static inline bool set_file_times(const char* path, uint64_t modified_time, uint64_t access_time) {
WCHAR wpath[1024];
if (!utf8_to_wide(path, wpath, 1024)) return false;
struct { int64_t actime; int64_t modtime; } times;
times.modtime = (int64_t)((modified_time / 10000000ULL) - 11644473600ULL);
times.actime = (int64_t)((access_time / 10000000ULL) - 11644473600ULL);
return _wutime(wpath, &times) == 0;
}
static inline bool set_dir_times(const char* path, uint64_t modified_time, uint64_t access_time) {
WCHAR wpath[1024];
if (!utf8_to_wide(path, wpath, 1024)) return false;
struct { int64_t actime; int64_t modtime; } times;
times.modtime = (int64_t)((modified_time / 10000000ULL) - 11644473600ULL);
times.actime = (int64_t)((access_time / 10000000ULL) - 11644473600ULL);
return lib_wutime_dir(wpath, &times) == 0;
}
static inline bool set_file_times_handle(FILE* f, uint64_t modified_time, uint64_t access_time) {
return lib_set_file_times_ntfs(f, (int64_t)modified_time, (int64_t)access_time);
}
#else
#define FOPEN fopen
#define FOPEN_PREALLOC(path, size) fopen(path, "wb")
#define FWRITE_DIRECT(f, buf, size) fwrite(buf, 1, size, f)
#define MKDIR(path) mkdir(path)
#define REMOVE remove
#define RMDIR(path) rmdir(path)
static inline bool set_file_times(const char* path, uint64_t modified_time, uint64_t access_time) {
struct linux_timespec times[2];
int64_t unix_mtime = (int64_t)((modified_time / 10000000ULL) - 11644473600ULL);
int64_t unix_atime = (int64_t)((access_time / 10000000ULL) - 11644473600ULL);
times[0].tv_sec = unix_atime;
times[0].tv_nsec = (access_time % 10000000ULL) * 100;
times[1].tv_sec = unix_mtime;
times[1].tv_nsec = (modified_time % 10000000ULL) * 100;
return syscall4(SYS_utimensat, AT_FDCWD, (long)path, (long)times, 0) == 0;
}
static inline bool set_dir_times(const char* path, uint64_t modified_time, uint64_t access_time) {
return set_file_times(path, modified_time, access_time);
}
static inline bool set_file_times_handle(FILE* f, uint64_t modified_time, uint64_t access_time) {
(void)f; (void)modified_time; (void)access_time;
return true;
}
#endif
static inline uint64_t exfat_timestamp_to_ntfs(uint32_t exfat_ts, uint8_t centiseconds, int8_t utc_offset) {
uint32_t second = (exfat_ts & 0x1F) * 2;
uint32_t minute = (exfat_ts >> 5) & 0x3F;
uint32_t hour = (exfat_ts >> 11) & 0x1F;
uint32_t day = (exfat_ts >> 16) & 0x1F;
uint32_t month = (exfat_ts >> 21) & 0x0F;
uint32_t year = ((exfat_ts >> 25) & 0x7F) + 1980;
uint64_t days = 0;
for (uint32_t y = 1601; y < year; y++)
days += (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? 366 : 365;
static const uint16_t month_days[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
if (month >= 1 && month <= 12) {
days += month_days[month - 1];
if (month > 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) days += 1;
}
days += day - 1;
uint64_t intervals = days * 24ULL * 60 * 60 * 10000000ULL;
intervals += hour * 60ULL * 60 * 10000000ULL;
intervals += minute * 60ULL * 10000000ULL;
intervals += second * 10000000ULL;
intervals += centiseconds * 100000ULL;
int64_t offset_seconds = (int64_t)utc_offset * 15 * 60;
intervals -= offset_seconds * 10000000LL;
return intervals;
}
#define MAX_PATH_LENGTH 256
#define MAX_FILENAME_LENGTH 256
#ifndef min
#define min(a,b) ((a) < (b) ? (a) : (b))
#endif
static inline size_t utf16_to_utf8_common(const uint16_t* utf16, int utf16_len, char* utf8, size_t utf8_size) {
size_t out_pos = 0;
for (int i = 0; i < utf16_len && out_pos < utf8_size - 1; i++) {
uint16_t c = utf16[i];
if (c < 0x80) utf8[out_pos++] = (char)c;
else if (c < 0x800) {
if (out_pos + 2 > utf8_size - 1) break;
utf8[out_pos++] = (char)(0xC0 | (c >> 6));
utf8[out_pos++] = (char)(0x80 | (c & 0x3F));
} else {
if (out_pos + 3 > utf8_size - 1) break;
utf8[out_pos++] = (char)(0xE0 | (c >> 12));
utf8[out_pos++] = (char)(0x80 | ((c >> 6) & 0x3F));
utf8[out_pos++] = (char)(0x80 | (c & 0x3F));
}
}
utf8[out_pos] = '\0';
return out_pos;
}
#define utf16_to_utf8 utf16_to_utf8_common
static inline void sanitize_filename(char* name) {
for (char* p = name; *p; p++) {
unsigned char c = (unsigned char)*p;
if (c < 0x20 || c == '<' || c == '>' || c == ':' || c == '"' || c == '|' || c == '?' || c == '*' || c == '\\' || c == '/')
*p = '_';
}
}
static inline void fs_name_to_utf8(const uint16_t* utf16, int len, char* utf8, size_t utf8_size) {
if (!utf8 || utf8_size == 0) return;
utf16_to_utf8(utf16, min(len, MAX_FILENAME_LENGTH - 1), utf8, utf8_size);
sanitize_filename(utf8);
}
#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)
static inline bool is_safe_path(const char* name) {
if (!name || name[0] == '\0') return false;
if (name[0] == '\\' || name[0] == '/') return false;
if (name[0] == '.' && name[1] == '.') return false;
if (strstr(name, "/..") || strstr(name, "\\..")) return false;
if (strchr(name, ':') != NULL) return false;
return true;
}
static inline uint32_t dir_hash(const char* s, size_t len) {
uint32_t h = 5381;
for (size_t i = 0; i < len; i++) h = ((h << 5) + h) ^ s[i];
return h;
}
#define DIR_CACHE_BITS 10
#define DIR_CACHE_SIZE (1 << DIR_CACHE_BITS)
extern uint32_t g_dir_cache[DIR_CACHE_SIZE];
extern bool g_dir_cache_init;
static inline bool create_directories(const char* path) {
if (!path || path[0] == '\0' || strstr(path, "..") != NULL) return false;
if (!g_dir_cache_init) { memset(g_dir_cache, 0, sizeof(g_dir_cache)); g_dir_cache_init = true; }
size_t path_len = strlen(path);
uint32_t h = dir_hash(path, path_len);
uint32_t idx = h & (DIR_CACHE_SIZE - 1);
if (g_dir_cache[idx] == h) return true;
if (path_len >= MAX_PATH_LENGTH) return false;
char tmp[MAX_PATH_LENGTH];
memcpy(tmp, path, path_len + 1);
bool success = true;
char* p = tmp;
#ifdef PLATFORM_WINDOWS
if (path_len > 2 && p[1] == ':') {
p += 2;
if (*p == '\\' || *p == '/') p++;
}
#else
if (*p == '/') p++;
#endif
while ((p = strchr(p, PATH_SEP_CHAR)) != NULL) {
*p = '\0';
int result = MKDIR(tmp);
if (result != 0 && errno != EEXIST) { success = false; break; }
*p = PATH_SEP_CHAR;
p++;
}
if (success && tmp[0] != '\0') {
int result = MKDIR(tmp);
if (result != 0 && errno != EEXIST) success = false;
}
if (success) g_dir_cache[idx] = h;
return success;
}
#endif
+35
View File
@@ -0,0 +1,35 @@
#ifndef CRYPTO_H
#define CRYPTO_H
#include "lib.h"
extern const uint8_t NTFS_HEADER[16];
extern const uint8_t EXFAT_HEADER[16];
extern const uint8_t OPTION_KEY[16];
extern const uint8_t OPTION_IV[16];
extern const uint8_t APM3_SEED[96];
extern const uint8_t APM3_KEY[16];
extern const uint8_t APM3_IV[16];
typedef struct {
uint8_t key[16];
uint8_t iv[16];
bool has_iv;
bool external;
} GameKeys;
typedef struct {
const char* game_id;
uint8_t key[16];
uint8_t iv[16];
bool has_iv;
} GameKeyEntry;
bool key_lookup(const char* id, uint8_t key[16], uint8_t iv[16], bool* from_external);
bool key_any(void);
void iv_page(uint64_t off, const uint8_t* base, uint8_t* out);
bool iv_file(const uint8_t key[16], const uint8_t* hdr, const uint8_t* page, uint8_t out[16]);
bool key_game(const char* id, GameKeys* out);
bool key_derive(const char* id, uint8_t key[16], uint8_t iv[16]);
#endif
+44
View File
@@ -0,0 +1,44 @@
#ifndef ERROR_H
#define ERROR_H
#include "lib.h"
typedef enum {
ERR_OK = 0,
ERR_MEMORY,
ERR_FILE_OPEN,
ERR_FILE_READ,
ERR_FILE_WRITE,
ERR_FILE_SEEK,
ERR_INVALID_BOOTID,
ERR_UNKNOWN_CONTAINER,
ERR_KEY_NOT_FOUND,
ERR_INVALID_KEY_FILE,
ERR_AES_ALIGNMENT,
ERR_IV_CALCULATION,
ERR_INVALID_NTFS,
ERR_INVALID_EXFAT,
ERR_INVALID_VHD,
ERR_MFT_CORRUPT,
ERR_PATH_UNSAFE,
ERR_DIR_CREATE,
ERR_EXTRACTION_FAILED
} ErrorCode;
static inline const char* error_string(ErrorCode code) {
static const char* const errs[] = {
"ok", "mem", "open", "read", "write", "seek", "bootid", "container",
"key", "keyfile", "align", "iv", "ntfs", "exfat", "vhd", "mft", "path", "mkdir", "extract"
};
return (code < sizeof(errs)/sizeof(errs[0])) ? errs[code] : "?";
}
#define FAIL(code) do { \
fprintf(stderr, "error: %s\n", error_string(code)); \
} while(0)
#define FAIL_MSG(code, msg) do { \
fprintf(stderr, "error: %s - %s\n", error_string(code), (msg)); \
} while(0)
#endif
+128
View File
@@ -0,0 +1,128 @@
#ifndef EXFAT_H
#define EXFAT_H
#include "lib.h"
#include "common.h"
#include "stream.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;
bool no_fat_chain;
uint32_t modify_timestamp;
uint32_t access_timestamp;
uint8_t modify_10ms;
int8_t modify_utc_offset;
int8_t access_utc_offset;
} ExfatFileInfo;
typedef struct {
char path[MAX_PATH_LENGTH];
uint64_t mtime;
uint64_t atime;
} DeferredDirTime;
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;
uint8_t* cluster_buf;
uint8_t* io_buf;
uint64_t total_bytes;
uint64_t extracted_bytes;
uint64_t files_extracted;
void* progress;
bool silent;
bool verbose;
DecryptStream* stream;
char last_dir[MAX_PATH_LENGTH];
DeferredDirTime* deferred_dirs;
uint32_t deferred_count;
uint32_t deferred_capacity;
} ExfatContext;
bool exfat_init(ExfatContext* ctx, const char* filename);
bool exfat_init_stream(ExfatContext* ctx, DecryptStream* stream);
bool exfat_extract_all(ExfatContext* ctx, const char* output_dir);
void exfat_close(ExfatContext* ctx);
#endif
+6
View File
@@ -0,0 +1,6 @@
static const GameKeyEntry embedded_keys[] = {
#if __has_include("keys.inc")
#include "keys.inc"
#endif
{NULL, {0}, {0}, false}
};
+3
View File
@@ -0,0 +1,3 @@
// copy to keys.inc and add your keys
// format: {"SXXX", {key[16]}, {iv[16]}, has_iv},
// {"XXXX", {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, true},
+638
View File
@@ -0,0 +1,638 @@
#ifndef LIB_H
#define LIB_H
#if defined(_WIN32) || defined(_WIN64)
#define PLATFORM_WINDOWS 1
#elif defined(__linux__) && defined(__x86_64__)
#define PLATFORM_LINUX 1
#else
#error "unsupported platform"
#endif
typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef signed short int16_t;
typedef unsigned short uint16_t;
typedef signed int int32_t;
typedef unsigned int uint32_t;
typedef signed long long int64_t;
typedef unsigned long long uint64_t;
#ifdef PLATFORM_WINDOWS
typedef uint64_t size_t;
typedef int64_t ssize_t;
typedef uint64_t uintptr_t;
typedef int64_t intptr_t;
#else
typedef unsigned long size_t;
typedef long ssize_t;
typedef unsigned long uintptr_t;
typedef long intptr_t;
#endif
typedef int64_t off_t;
typedef int64_t time_t;
typedef int64_t __time64_t;
#ifndef __bool_true_false_are_defined
#if __STDC_VERSION__ < 202311L
typedef _Bool bool;
#define true 1
#define false 0
#endif
#define __bool_true_false_are_defined 1
#endif
#define NULL ((void*)0)
typedef __builtin_va_list va_list;
#define va_start(ap, last) __builtin_va_start(ap, last)
#define va_end(ap) __builtin_va_end(ap)
#define va_arg(ap, type) __builtin_va_arg(ap, type)
#ifdef __cplusplus
extern "C" {
#endif
#define EPERM 1
#define ENOENT 2
#define ESRCH 3
#define EINTR 4
#define EIO 5
#define ENXIO 6
#define EBADF 9
#define ENOMEM 12
#define EACCES 13
#define EFAULT 14
#define EEXIST 17
#define ENOTDIR 20
#define EISDIR 21
#define EINVAL 22
#define EMFILE 24
#define ENOSPC 28
#define EROFS 30
#define ERANGE 34
#ifdef PLATFORM_WINDOWS
typedef long NTSTATUS;
typedef void* HANDLE;
typedef uint16_t WCHAR;
typedef uint32_t ULONG;
typedef int32_t LONG;
typedef uint64_t ULONGLONG;
typedef int64_t LONGLONG;
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_END_OF_FILE ((NTSTATUS)0xC0000011L)
#define STATUS_NO_MORE_FILES ((NTSTATUS)0x80000006L)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define INVALID_HANDLE_VALUE ((HANDLE)(intptr_t)-1)
#define FILE_READ_DATA 0x0001
#define FILE_WRITE_DATA 0x0002
#define FILE_APPEND_DATA 0x0004
#define FILE_READ_ATTRIBUTES 0x0080
#define FILE_WRITE_ATTRIBUTES 0x0100
#define FILE_LIST_DIRECTORY 0x0001
#define DELETE 0x00010000L
#define SYNCHRONIZE 0x00100000L
#define FILE_SHARE_READ 0x00000001
#define FILE_SHARE_WRITE 0x00000002
#define FILE_SHARE_DELETE 0x00000004
#define FILE_OPEN 0x00000001
#define FILE_CREATE 0x00000002
#define FILE_OVERWRITE_IF 0x00000005
#define FILE_OPEN_IF 0x00000003
#define FILE_DIRECTORY_FILE 0x00000001
#define FILE_SEQUENTIAL_ONLY 0x00000004
#define FILE_NON_DIRECTORY_FILE 0x00000040
#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020
#define FILE_DELETE_ON_CLOSE 0x00001000
#define FILE_ATTRIBUTE_NORMAL 0x00000080
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
#define OBJ_CASE_INSENSITIVE 0x00000040
typedef struct _UNICODE_STRING {
uint16_t Length;
uint16_t MaximumLength;
WCHAR* Buffer;
} UNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
UNICODE_STRING* ObjectName;
ULONG Attributes;
void* SecurityDescriptor;
void* SecurityQualityOfService;
} OBJECT_ATTRIBUTES;
typedef struct _IO_STATUS_BLOCK {
union { NTSTATUS Status; void* Pointer; };
uintptr_t Information;
} IO_STATUS_BLOCK;
typedef struct _LARGE_INTEGER {
LONGLONG QuadPart;
} LARGE_INTEGER;
typedef struct _FILE_POSITION_INFORMATION {
LARGE_INTEGER CurrentByteOffset;
} FILE_POSITION_INFORMATION;
typedef struct _FILE_STANDARD_INFORMATION {
LARGE_INTEGER AllocationSize;
LARGE_INTEGER EndOfFile;
ULONG NumberOfLinks;
uint8_t DeletePending;
uint8_t Directory;
} FILE_STANDARD_INFORMATION;
typedef struct _FILE_BASIC_INFORMATION {
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
ULONG FileAttributes;
} FILE_BASIC_INFORMATION;
typedef struct _FILE_BOTH_DIR_INFORMATION {
ULONG NextEntryOffset;
ULONG FileIndex;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaSize;
uint8_t ShortNameLength;
WCHAR ShortName[12];
WCHAR FileName[1];
} FILE_BOTH_DIR_INFORMATION;
typedef enum _FILE_INFORMATION_CLASS {
FileBasicInformation = 4,
FileStandardInformation = 5,
FilePositionInformation = 14,
FileBothDirectoryInformation = 3,
} FILE_INFORMATION_CLASS;
typedef struct _RTL_USER_PROCESS_PARAMETERS {
uint8_t _Reserved0[0x20];
HANDLE StandardInput;
HANDLE StandardOutput;
HANDLE StandardError;
uint8_t _Reserved1[0x38];
UNICODE_STRING CommandLine;
WCHAR* Environment;
} RTL_USER_PROCESS_PARAMETERS;
typedef struct _PEB {
uint8_t _Reserved0[0x20];
RTL_USER_PROCESS_PARAMETERS* ProcessParameters;
void* SubSystemData;
HANDLE ProcessHeap;
} PEB;
typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
void* PebBaseAddress;
uintptr_t AffinityMask;
int32_t BasePriority;
uintptr_t UniqueProcessId;
uintptr_t InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
uint8_t Reserved1[48];
UNICODE_STRING ImageName;
int32_t BasePriority;
uintptr_t UniqueProcessId;
} SYSTEM_PROCESS_INFORMATION;
#define ProcessBasicInformation 0
#define SystemProcessInformation 5
#define CRT_OFFSETOF(type, member) ((size_t)&(((type*)0)->member))
__declspec(dllimport) NTSTATUS __stdcall NtCreateFile(HANDLE*, ULONG, OBJECT_ATTRIBUTES*, IO_STATUS_BLOCK*, LARGE_INTEGER*, ULONG, ULONG, ULONG, ULONG, void*, ULONG);
__declspec(dllimport) NTSTATUS __stdcall NtReadFile(HANDLE, HANDLE, void*, void*, IO_STATUS_BLOCK*, void*, ULONG, LARGE_INTEGER*, ULONG*);
__declspec(dllimport) NTSTATUS __stdcall NtWriteFile(HANDLE, HANDLE, void*, void*, IO_STATUS_BLOCK*, const void*, ULONG, LARGE_INTEGER*, ULONG*);
__declspec(dllimport) NTSTATUS __stdcall NtClose(HANDLE);
__declspec(dllimport) NTSTATUS __stdcall NtQueryInformationFile(HANDLE, IO_STATUS_BLOCK*, void*, ULONG, FILE_INFORMATION_CLASS);
__declspec(dllimport) NTSTATUS __stdcall NtSetInformationFile(HANDLE, IO_STATUS_BLOCK*, void*, ULONG, FILE_INFORMATION_CLASS);
__declspec(dllimport) NTSTATUS __stdcall NtQueryDirectoryFile(HANDLE, HANDLE, void*, void*, IO_STATUS_BLOCK*, void*, ULONG, FILE_INFORMATION_CLASS, uint8_t, UNICODE_STRING*, uint8_t);
__declspec(dllimport) NTSTATUS __stdcall NtQuerySystemTime(LARGE_INTEGER*);
__declspec(dllimport) NTSTATUS __stdcall NtQueryInformationProcess(HANDLE, ULONG, void*, ULONG, ULONG*);
__declspec(dllimport) NTSTATUS __stdcall NtQuerySystemInformation(ULONG, void*, ULONG, ULONG*);
__declspec(dllimport) NTSTATUS __stdcall NtDelayExecution(uint8_t, LARGE_INTEGER*);
__declspec(dllimport) void* __stdcall RtlAllocateHeap(HANDLE, ULONG, size_t);
__declspec(dllimport) void* __stdcall RtlReAllocateHeap(HANDLE, ULONG, void*, size_t);
__declspec(dllimport) uint8_t __stdcall RtlFreeHeap(HANDLE, ULONG, void*);
__declspec(dllimport) void __stdcall RtlExitUserProcess(NTSTATUS);
__declspec(dllimport) NTSTATUS __stdcall RtlDosPathNameToNtPathName_U_WithStatus(const WCHAR*, UNICODE_STRING*, WCHAR**, void*);
__declspec(dllimport) void __stdcall RtlFreeUnicodeString(UNICODE_STRING*);
static inline PEB* lib_get_peb(void) {
PEB* peb;
__asm__ volatile ("mov %%gs:0x60, %0" : "=r"(peb));
return peb;
}
#else
#define SYS_read 0
#define SYS_write 1
#define SYS_open 2
#define SYS_close 3
#define SYS_fstat 5
#define SYS_lseek 8
#define SYS_mmap 9
#define SYS_mprotect 10
#define SYS_munmap 11
#define SYS_brk 12
#define SYS_ioctl 16
#define SYS_access 21
#define SYS_dup2 33
#define SYS_getpid 39
#define SYS_exit 60
#define SYS_uname 63
#define SYS_fcntl 72
#define SYS_fsync 74
#define SYS_ftruncate 77
#define SYS_getdents64 217
#define SYS_fadvise64 221
#define SYS_exit_group 231
#define SYS_openat 257
#define SYS_mkdirat 258
#define SYS_newfstatat 262
#define SYS_unlinkat 263
#define SYS_utimensat 280
#define O_RDONLY 0x0000
#define O_WRONLY 0x0001
#define O_RDWR 0x0002
#define O_CREAT 0x0040
#define O_EXCL 0x0080
#define O_TRUNC 0x0200
#define O_APPEND 0x0400
#define O_DIRECTORY 0x10000
#define O_CLOEXEC 0x80000
#define S_IRWXU 0700
#define S_IRUSR 0400
#define S_IWUSR 0200
#define S_IXUSR 0100
#define S_IRWXG 0070
#define S_IRGRP 0040
#define S_IXGRP 0010
#define S_IRWXO 0007
#define S_IROTH 0004
#define S_IXOTH 0001
#define S_IFMT 0170000
#define S_IFDIR 0040000
#define S_IFREG 0100000
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#define PROT_READ 0x1
#define PROT_WRITE 0x2
#define MAP_PRIVATE 0x02
#define MAP_ANONYMOUS 0x20
#define MAP_FAILED ((void*)-1)
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
#define AT_FDCWD (-100)
#define DT_DIR 4
#define DT_REG 8
struct linux_dirent64 {
uint64_t d_ino;
int64_t d_off;
uint16_t d_reclen;
uint8_t d_type;
char d_name[];
};
struct linux_stat {
uint64_t st_dev;
uint64_t st_ino;
uint64_t st_nlink;
uint32_t st_mode;
uint32_t st_uid;
uint32_t st_gid;
uint32_t __pad0;
uint64_t st_rdev;
int64_t st_size;
int64_t st_blksize;
int64_t st_blocks;
int64_t st_atime_sec;
int64_t st_atime_nsec;
int64_t st_mtime_sec;
int64_t st_mtime_nsec;
int64_t st_ctime_sec;
int64_t st_ctime_nsec;
int64_t __unused[3];
};
struct linux_timespec {
int64_t tv_sec;
int64_t tv_nsec;
};
static inline long syscall0(long n) {
long ret;
__asm__ volatile ("syscall" : "=a"(ret) : "a"(n) : "rcx", "r11", "memory");
return ret;
}
static inline long syscall1(long n, long a1) {
long ret;
__asm__ volatile ("syscall" : "=a"(ret) : "a"(n), "D"(a1) : "rcx", "r11", "memory");
return ret;
}
static inline long syscall2(long n, long a1, long a2) {
long ret;
__asm__ volatile ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2) : "rcx", "r11", "memory");
return ret;
}
static inline long syscall3(long n, long a1, long a2, long a3) {
long ret;
__asm__ volatile ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3) : "rcx", "r11", "memory");
return ret;
}
static inline long syscall4(long n, long a1, long a2, long a3, long a4) {
long ret;
register long r10 __asm__("r10") = a4;
__asm__ volatile ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10) : "rcx", "r11", "memory");
return ret;
}
static inline long syscall5(long n, long a1, long a2, long a3, long a4, long a5) {
long ret;
register long r10 __asm__("r10") = a4;
register long r8 __asm__("r8") = a5;
__asm__ volatile ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8) : "rcx", "r11", "memory");
return ret;
}
static inline long syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6) {
long ret;
register long r10 __asm__("r10") = a4;
register long r8 __asm__("r8") = a5;
register long r9 __asm__("r9") = a6;
__asm__ volatile ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10), "r"(r8), "r"(r9) : "rcx", "r11", "memory");
return ret;
}
#endif
#define _IOFBF 0
#define _IOLBF 1
#define _IONBF 2
#define _A_SUBDIR 0x10
#ifndef SEEK_SET
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
#endif
#define EOF (-1)
#ifdef PLATFORM_WINDOWS
extern HANDLE lib_heap;
extern HANDLE lib_stdout_handle;
extern HANDLE lib_stderr_handle;
extern HANDLE lib_stdin_handle;
#endif
extern int lib_errno_val;
void* lib_malloc(size_t size);
void* lib_calloc(size_t count, size_t size);
void* lib_realloc(void* ptr, size_t size);
void lib_free(void* ptr);
#define malloc lib_malloc
#define calloc lib_calloc
#define realloc lib_realloc
#define free lib_free
size_t lib_strlen(const char* s);
char* lib_strcpy(char* dst, const char* src);
char* lib_strncpy(char* dst, const char* src, size_t n);
int lib_strcmp(const char* s1, const char* s2);
int lib_strncmp(const char* s1, const char* s2, size_t n);
char* lib_strchr(const char* s, int c);
char* lib_strrchr(const char* s, int c);
char* lib_strstr(const char* haystack, const char* needle);
char* lib_strncat(char* dst, const char* src, size_t n);
void* lib_memcpy(void* dst, const void* src, size_t n);
void* lib_memset(void* dst, int c, size_t n);
int lib_memcmp(const void* s1, const void* s2, size_t n);
#define strlen lib_strlen
#define strcpy lib_strcpy
#define strncpy lib_strncpy
#define strcmp lib_strcmp
#define strncmp lib_strncmp
#define strchr lib_strchr
#define strrchr lib_strrchr
#define strstr lib_strstr
#define strncat lib_strncat
#define memcpy lib_memcpy
#define memset lib_memset
#define memcmp lib_memcmp
#define LIB_FILE_READ 0x01
#define LIB_FILE_WRITE 0x02
#define LIB_FILE_EOF 0x04
#define LIB_FILE_ERROR 0x08
#define LIB_FILE_APPEND 0x10
#define LIB_IOBUF_SIZE (64 * 1024)
typedef struct {
#ifdef PLATFORM_WINDOWS
HANDLE handle;
#else
int fd;
#endif
uint8_t* buffer;
uint32_t buf_cap;
uint32_t buf_pos;
uint32_t buf_fill;
int64_t file_pos;
uint32_t flags;
} FILE;
extern FILE* lib_stdout_file;
extern FILE* lib_stderr_file;
extern FILE* lib_stdin_file;
#define stdin lib_stdin_file
#define stdout lib_stdout_file
#define stderr lib_stderr_file
FILE* lib_fopen(const char* path, const char* mode);
size_t lib_fread(void* buf, size_t size, size_t count, FILE* f);
size_t lib_fwrite(const void* buf, size_t size, size_t count, FILE* f);
int lib_fseek(FILE* f, long offset, int whence);
int lib_fseeki64(FILE* f, int64_t offset, int whence);
long lib_ftell(FILE* f);
int64_t lib_ftelli64(FILE* f);
int lib_fclose(FILE* f);
int lib_fflush(FILE* f);
int lib_feof(FILE* f);
char* lib_fgets(char* buf, int n, FILE* f);
void lib_rewind(FILE* f);
void lib_setvbuf(FILE* f, char* buf, int mode, size_t size);
#define fopen lib_fopen
#define fread lib_fread
#define fwrite lib_fwrite
#define fseek lib_fseek
#define _fseeki64 lib_fseeki64
#define fseeko lib_fseeki64
#define ftell lib_ftell
#define _ftelli64 lib_ftelli64
#define ftello lib_ftelli64
#define fclose lib_fclose
#define fflush lib_fflush
#define feof lib_feof
#define fgets lib_fgets
#define rewind lib_rewind
#define setvbuf lib_setvbuf
#ifdef PLATFORM_WINDOWS
FILE* lib_wfopen(const WCHAR* path, const WCHAR* mode);
FILE* lib_wfopen_prealloc(const WCHAR* path, uint64_t size);
bool lib_set_file_times_ntfs(FILE* f, int64_t modified_time, int64_t access_time);
int lib_wutime(const WCHAR* path, const void* times);
int lib_wutime_dir(const WCHAR* path, const void* times);
#define _wfopen lib_wfopen
#define _wutime lib_wutime
#endif
size_t lib_fwrite_direct(FILE* f, const void* buf, size_t size);
int lib_printf(const char* fmt, ...);
int lib_fprintf(FILE* f, const char* fmt, ...);
int lib_snprintf(char* buf, size_t n, const char* fmt, ...);
int lib_vprintf(const char* fmt, va_list ap);
int lib_vfprintf(FILE* f, const char* fmt, va_list ap);
int lib_vsnprintf(char* buf, size_t n, const char* fmt, va_list ap);
int lib_puts(const char* s);
#define printf lib_printf
#define fprintf lib_fprintf
#define snprintf lib_snprintf
#define vprintf lib_vprintf
#define vfprintf lib_vfprintf
#define vsnprintf lib_vsnprintf
#define puts lib_puts
int lib_mkdir(const char* path);
int lib_rmdir(const char* path);
int lib_remove(const char* path);
#define mkdir lib_mkdir
#define _mkdir lib_mkdir
#define rmdir lib_rmdir
#define _rmdir lib_rmdir
#define remove lib_remove
#ifdef PLATFORM_WINDOWS
int lib_wmkdir(const WCHAR* path);
int lib_wremove(const WCHAR* path);
#define _wmkdir lib_wmkdir
#define _wremove lib_wremove
#endif
typedef struct {
uint32_t attrib;
int64_t time_create;
int64_t time_access;
int64_t time_write;
int64_t size;
char name[260];
} lib_finddata_t;
intptr_t lib_findfirst(const char* pattern, lib_finddata_t* data);
int lib_findnext(intptr_t handle, lib_finddata_t* data);
int lib_findclose(intptr_t handle);
#define _finddata_t lib_finddata_t
#define _finddata64_t lib_finddata_t
#define _findfirst lib_findfirst
#define _findfirst64 lib_findfirst
#define _findnext lib_findnext
#define _findnext64 lib_findnext
#define _findclose lib_findclose
int lib_isatty(int fd);
#define isatty lib_isatty
#define _isatty lib_isatty
time_t lib_time(time_t* t);
double lib_difftime(time_t t1, time_t t0);
#define time lib_time
#define _time64 lib_time
#define difftime lib_difftime
int lib_atoi(const char* s);
int lib_isxdigit(int c);
#define atoi lib_atoi
#define isxdigit lib_isxdigit
int* lib_errno_func(void);
#define errno (*lib_errno_func())
void lib_exit(int status);
#define exit lib_exit
#define _exit lib_exit
#ifdef PLATFORM_WINDOWS
#define PATH_SEPARATOR "\\"
#define PATH_SEP_CHAR '\\'
#else
#define PATH_SEPARATOR "/"
#define PATH_SEP_CHAR '/'
#endif
void lib_init(void);
int lib_main(int argc, char** argv);
#ifdef __cplusplus
}
#endif
#endif
+280
View File
@@ -0,0 +1,280 @@
#ifndef NTFS_H
#define NTFS_H
#include "lib.h"
#include "common.h"
#include "stream.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 VHD_TYPE_DIFFERENCING 4
#define VHD_MAX_CHAIN_DEPTH 8
#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
#define DIR_CACHE_INITIAL_SIZE 2048
#define NTFS_MAX_RECURSION_DEPTH 64
typedef struct {
uint64_t ref_number;
char path[MAX_PATH_LENGTH];
bool occupied;
} DirectoryEntry;
typedef struct {
DirectoryEntry* entries;
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 {
uint32_t platform_code;
uint32_t platform_data_space;
uint32_t platform_data_length;
uint32_t reserved;
uint64_t platform_data_offset;
} VHDParentLocator;
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];
VHDParentLocator parent_loc[8];
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;
#pragma pack(pop)
typedef struct VHDRunSource {
void* ntfs_ctx;
DataRun runs[MAX_DATA_RUNS];
int run_count;
uint64_t file_size;
uint64_t data_start_offset;
uint32_t bytes_per_cluster;
} VHDRunSource;
typedef struct VHDContext {
FILE* fp;
VHDRunSource* run_source;
VHDFooter footer;
VHDDynamicHeader dyn_header;
uint32_t* bat;
uint32_t sector_bitmap_size;
uint8_t* sector_bitmap;
uint8_t* block_buffer;
uint32_t cached_block_idx;
bool block_cached;
struct VHDContext* parent;
uint32_t depth;
char base_dir[MAX_PATH_LENGTH];
uint64_t file_pos;
} VHDContext;
typedef struct {
char name[MAX_FILENAME_LENGTH];
uint64_t parent_ref;
bool is_directory;
bool valid;
} FileInfo;
typedef struct {
FILE* fp;
} RawNTFSContext;
#define MAX_PENDING_VHDS 16
#define MAX_PENDING_OPTS 16
typedef struct {
DataRun runs[MAX_DATA_RUNS];
int run_count;
uint64_t file_size;
int vhd_number;
} PendingVHD;
typedef struct {
DataRun runs[MAX_DATA_RUNS];
int run_count;
uint64_t file_size;
uint64_t data_offset;
char filename[MAX_FILENAME_LENGTH];
} PendingOpt;
typedef struct {
union {
VHDContext vhd;
RawNTFSContext raw;
};
bool is_vhd;
NTFSBootSector boot;
uint16_t bytes_per_sector;
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;
bool silent;
bool verbose;
uint64_t total_bytes;
uint64_t extracted_bytes;
uint64_t files_extracted;
void* progress;
uint64_t raw_file_pos;
DecryptStream* stream;
PendingVHD pending_vhds[MAX_PENDING_VHDS];
int pending_vhd_count;
PendingOpt pending_opts[MAX_PENDING_OPTS];
int pending_opt_count;
uint8_t* lookup_buffer;
bool apm3_decrypt;
uint8_t apm3_key[16];
uint8_t apm3_iv[16];
uint8_t* file_buffer;
char last_dir[MAX_PATH_LENGTH];
void* deferred_dirs;
uint32_t deferred_count;
uint32_t deferred_capacity;
} NTFSContext;
bool ntfs_init(NTFSContext* ctx, const char* vhd_path, const char* extract_path);
bool ntfs_init_stream(NTFSContext* ctx, DecryptStream* stream, const char* extract_path);
int ntfs_detect_vhd_type(NTFSContext* ctx);
bool ntfs_extract_all(NTFSContext* ctx);
bool ntfs_extract_pending_vhds(NTFSContext* ctx, bool silent, bool verbose, bool* is_orphan);
int ntfs_get_pending_opt_count(NTFSContext* ctx);
const PendingOpt* ntfs_get_pending_opt(NTFSContext* ctx, int index);
void ntfs_close(NTFSContext* ctx);
#endif
+12
View File
@@ -0,0 +1,12 @@
#ifndef PROGRESS_H
#define PROGRESS_H
#include "lib.h"
typedef struct { uint64_t total; } Progress;
static inline void progress_init(Progress* p, uint64_t total) { p->total = total; }
static inline void progress_update(Progress* p, uint64_t current) { (void)p; (void)current; }
static inline void progress_finish(Progress* p) { (void)p; }
#endif
+46
View File
@@ -0,0 +1,46 @@
#ifndef STREAM_H
#define STREAM_H
#include "lib.h"
#include "aes.h"
#define DECRYPT_PAGE_SIZE 4096
#define MAX_DATA_RUNS 256
typedef struct {
uint64_t offset;
uint64_t length;
} DataRun;
typedef struct {
void* ntfs_ctx;
DataRun runs[MAX_DATA_RUNS];
int run_count;
uint64_t file_size;
} RunSource;
typedef struct DecryptStream DecryptStream;
struct DecryptStream {
FILE* fp;
DecryptStream* parent_stream;
RunSource* run_source;
uint64_t data_offset;
uint64_t data_size;
uint8_t key[16];
uint8_t file_iv[16];
uint8_t page_buffer[DECRYPT_PAGE_SIZE];
uint64_t cached_page_offset;
uint64_t file_pos;
AES_ctx aes_ctx;
};
bool stream_init(DecryptStream* ds, FILE* fp, uint64_t data_offset,
uint64_t data_size, const uint8_t key[16], const uint8_t iv[16]);
bool stream_init_from_runs(DecryptStream* ds, RunSource* source,
const uint8_t key[16], const uint8_t iv[16]);
bool stream_read(DecryptStream* ds, void* buffer, uint64_t offset, size_t size);
bool stream_read_raw(void* ntfs_ctx, const DataRun* runs, int run_count,
uint64_t file_size, uint64_t offset, void* buffer, size_t size);
#endif
+319
View File
@@ -0,0 +1,319 @@
#include "aes.h"
const uint8_t aes_sbox[256] = {
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
};
const uint8_t aes_rcon[11] = {0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36};
static uint8_t aes_rsbox[256];
static int aes_rsbox_init;
static void aes_init_rsbox(void) {
if (aes_rsbox_init) return;
for (int i = 0; i < 256; i++) aes_rsbox[aes_sbox[i]] = (uint8_t)i;
aes_rsbox_init = 1;
}
#define xtime(x) ((((x) << 1) ^ ((((x) >> 7) & 1) * 0x1b)) & 0xff)
#define mul(a,b) ((((b)&1)*a) ^ (((b>>1)&1)*xtime(a)) ^ (((b>>2)&1)*xtime(xtime(a))) ^ (((b>>3)&1)*xtime(xtime(xtime(a)))))
#if defined(__x86_64__) || defined(_M_X64)
#define AES_HW_AVAILABLE 1
#else
#define AES_HW_AVAILABLE 0
int aes_hw_supported(void) { return 0; }
#endif
#if AES_HW_AVAILABLE
static int aes_hw_checked = 0;
static int aes_hw_available = 0;
__attribute__((noinline))
int aes_hw_supported(void) {
if (!aes_hw_checked) {
unsigned int eax, ecx;
__asm__ volatile ("cpuid" : "=a"(eax), "=c"(ecx) : "a"(1), "c"(0) : "ebx", "edx");
aes_hw_available = (ecx & (1 << 25)) != 0;
aes_hw_checked = 1;
}
return aes_hw_available;
}
__attribute__((noinline))
static void aes_hw_key_expand(AES_ctx* ctx, const uint8_t* key) {
__asm__ volatile (
"movdqu (%[key]), %%xmm0\n\t"
"movdqa %%xmm0, (%[rk])\n\t"
#define KEYGEN(r, rc) \
"aeskeygenassist $" #rc ", %%xmm0, %%xmm1\n\t" \
"pshufd $0xff, %%xmm1, %%xmm1\n\t" \
"movdqa %%xmm0, %%xmm2\n\t" \
"pslldq $4, %%xmm2\n\t" "pxor %%xmm2, %%xmm0\n\t" \
"pslldq $4, %%xmm2\n\t" "pxor %%xmm2, %%xmm0\n\t" \
"pslldq $4, %%xmm2\n\t" "pxor %%xmm2, %%xmm0\n\t" \
"pxor %%xmm1, %%xmm0\n\t" \
"movdqa %%xmm0, " #r "*16(%[rk])\n\t"
KEYGEN(1,0x01) KEYGEN(2,0x02) KEYGEN(3,0x04) KEYGEN(4,0x08)
KEYGEN(5,0x10) KEYGEN(6,0x20) KEYGEN(7,0x40) KEYGEN(8,0x80)
KEYGEN(9,0x1b) KEYGEN(10,0x36)
#undef KEYGEN
: : [key] "r" (key), [rk] "r" (ctx->round_keys)
: "xmm0", "xmm1", "xmm2", "memory"
);
__asm__ volatile (
#define INVKEY(s, d) \
"movdqa " #s "*16(%[rk]), %%xmm0\n\t" \
"aesimc %%xmm0, %%xmm0\n\t" \
"movdqa %%xmm0, " #d "*16(%[dk])\n\t"
INVKEY(1,0) INVKEY(2,1) INVKEY(3,2) INVKEY(4,3) INVKEY(5,4)
INVKEY(6,5) INVKEY(7,6) INVKEY(8,7) INVKEY(9,8)
#undef INVKEY
: : [rk] "r" (ctx->round_keys), [dk] "r" (ctx->dec_keys)
: "xmm0", "memory"
);
}
__attribute__((noinline))
static void aes_hw_cbc_decrypt(AES_ctx* ctx, uint8_t* buf, size_t len) {
size_t blocks = len >> 4;
if (!blocks) return;
__asm__ volatile (
"movdqa (%[rk]), %%xmm15\n\t"
"movdqa 160(%[rk]), %%xmm14\n\t"
"movdqa (%[dk]), %%xmm13\n\t"
"movdqu (%[iv]), %%xmm12\n\t"
"cmpq $8, %[n]\n\t"
"jb 2f\n\t"
".p2align 4\n"
"1:\n\t"
"movdqu (%[buf]), %%xmm0\n\t"
"movdqu 16(%[buf]), %%xmm1\n\t"
"movdqu 32(%[buf]), %%xmm2\n\t"
"movdqu 48(%[buf]), %%xmm3\n\t"
"movdqu 64(%[buf]), %%xmm4\n\t"
"movdqu 80(%[buf]), %%xmm5\n\t"
"movdqu 96(%[buf]), %%xmm6\n\t"
"movdqu 112(%[buf]), %%xmm7\n\t"
"movdqa %%xmm7, %%xmm11\n\t"
"pxor %%xmm14, %%xmm0\n\t"
"pxor %%xmm14, %%xmm1\n\t"
"pxor %%xmm14, %%xmm2\n\t"
"pxor %%xmm14, %%xmm3\n\t"
"pxor %%xmm14, %%xmm4\n\t"
"pxor %%xmm14, %%xmm5\n\t"
"pxor %%xmm14, %%xmm6\n\t"
"pxor %%xmm14, %%xmm7\n\t"
#define DR(off) \
"movdqa " #off "(%[dk]), %%xmm10\n\t" \
"aesdec %%xmm10, %%xmm0\n\t" \
"aesdec %%xmm10, %%xmm1\n\t" \
"aesdec %%xmm10, %%xmm2\n\t" \
"aesdec %%xmm10, %%xmm3\n\t" \
"aesdec %%xmm10, %%xmm4\n\t" \
"aesdec %%xmm10, %%xmm5\n\t" \
"aesdec %%xmm10, %%xmm6\n\t" \
"aesdec %%xmm10, %%xmm7\n\t"
DR(128) DR(112) DR(96) DR(80) DR(64) DR(48) DR(32) DR(16)
#undef DR
"aesdec %%xmm13, %%xmm0\n\t"
"aesdec %%xmm13, %%xmm1\n\t"
"aesdec %%xmm13, %%xmm2\n\t"
"aesdec %%xmm13, %%xmm3\n\t"
"aesdec %%xmm13, %%xmm4\n\t"
"aesdec %%xmm13, %%xmm5\n\t"
"aesdec %%xmm13, %%xmm6\n\t"
"aesdec %%xmm13, %%xmm7\n\t"
"aesdeclast %%xmm15, %%xmm0\n\t"
"aesdeclast %%xmm15, %%xmm1\n\t"
"aesdeclast %%xmm15, %%xmm2\n\t"
"aesdeclast %%xmm15, %%xmm3\n\t"
"aesdeclast %%xmm15, %%xmm4\n\t"
"aesdeclast %%xmm15, %%xmm5\n\t"
"aesdeclast %%xmm15, %%xmm6\n\t"
"aesdeclast %%xmm15, %%xmm7\n\t"
"pxor %%xmm12, %%xmm0\n\t"
"movdqu (%[buf]), %%xmm12\n\t"
"pxor %%xmm12, %%xmm1\n\t"
"movdqu 16(%[buf]), %%xmm12\n\t"
"pxor %%xmm12, %%xmm2\n\t"
"movdqu 32(%[buf]), %%xmm12\n\t"
"pxor %%xmm12, %%xmm3\n\t"
"movdqu 48(%[buf]), %%xmm12\n\t"
"pxor %%xmm12, %%xmm4\n\t"
"movdqu 64(%[buf]), %%xmm12\n\t"
"pxor %%xmm12, %%xmm5\n\t"
"movdqu 80(%[buf]), %%xmm12\n\t"
"pxor %%xmm12, %%xmm6\n\t"
"movdqu 96(%[buf]), %%xmm12\n\t"
"pxor %%xmm12, %%xmm7\n\t"
"movdqu %%xmm0, (%[buf])\n\t"
"movdqu %%xmm1, 16(%[buf])\n\t"
"movdqu %%xmm2, 32(%[buf])\n\t"
"movdqu %%xmm3, 48(%[buf])\n\t"
"movdqu %%xmm4, 64(%[buf])\n\t"
"movdqu %%xmm5, 80(%[buf])\n\t"
"movdqu %%xmm6, 96(%[buf])\n\t"
"movdqu %%xmm7, 112(%[buf])\n\t"
"movdqa %%xmm11, %%xmm12\n\t"
"addq $128, %[buf]\n\t"
"subq $8, %[n]\n\t"
"cmpq $8, %[n]\n\t"
"jae 1b\n\t"
"2:\n\t"
"testq %[n], %[n]\n\t"
"jz 4f\n\t"
"3:\n\t"
"movdqu (%[buf]), %%xmm0\n\t"
"movdqa %%xmm0, %%xmm1\n\t"
"pxor %%xmm14, %%xmm0\n\t"
"aesdec 128(%[dk]), %%xmm0\n\t"
"aesdec 112(%[dk]), %%xmm0\n\t"
"aesdec 96(%[dk]), %%xmm0\n\t"
"aesdec 80(%[dk]), %%xmm0\n\t"
"aesdec 64(%[dk]), %%xmm0\n\t"
"aesdec 48(%[dk]), %%xmm0\n\t"
"aesdec 32(%[dk]), %%xmm0\n\t"
"aesdec 16(%[dk]), %%xmm0\n\t"
"aesdec %%xmm13, %%xmm0\n\t"
"aesdeclast %%xmm15, %%xmm0\n\t"
"pxor %%xmm12, %%xmm0\n\t"
"movdqa %%xmm1, %%xmm12\n\t"
"movdqu %%xmm0, (%[buf])\n\t"
"addq $16, %[buf]\n\t"
"decq %[n]\n\t"
"jnz 3b\n\t"
"4:\n\t"
"movdqu %%xmm12, (%[iv])\n\t"
: [buf] "+r" (buf), [n] "+r" (blocks)
: [rk] "r" (ctx->round_keys), [dk] "r" (ctx->dec_keys), [iv] "r" (ctx->iv)
: "xmm0","xmm1","xmm2","xmm3","xmm4","xmm5","xmm6","xmm7",
"xmm10","xmm11","xmm12","xmm13","xmm14","xmm15","memory","cc"
);
}
#endif
static void aes_sw_key_expand(uint8_t* rk, const uint8_t* key) {
uint8_t t[4];
for (int i = 0; i < 16; ++i) rk[i] = key[i];
for (int i = 4; i < 44; ++i) {
int k = (i - 1) << 2;
t[0] = rk[k]; t[1] = rk[k+1]; t[2] = rk[k+2]; t[3] = rk[k+3];
if ((i & 3) == 0) {
uint8_t tmp = t[0];
t[0] = aes_sbox[t[1]] ^ aes_rcon[i >> 2];
t[1] = aes_sbox[t[2]];
t[2] = aes_sbox[t[3]];
t[3] = aes_sbox[tmp];
}
int j = i << 2; k = (i - 4) << 2;
rk[j] = rk[k] ^ t[0]; rk[j+1] = rk[k+1] ^ t[1];
rk[j+2] = rk[k+2] ^ t[2]; rk[j+3] = rk[k+3] ^ t[3];
}
}
static void aes_sw_decrypt_block(uint8_t* s, const uint8_t* rk) {
uint8_t t;
for (int i = 0; i < 16; ++i) s[i] ^= rk[160+i];
for (int r = 9; r >= 0; --r) {
t = s[13]; s[13] = s[9]; s[9] = s[5]; s[5] = s[1]; s[1] = t;
t = s[2]; s[2] = s[10]; s[10] = t;
t = s[6]; s[6] = s[14]; s[14] = t;
t = s[3]; s[3] = s[7]; s[7] = s[11]; s[11] = s[15]; s[15] = t;
for (int i = 0; i < 16; ++i) s[i] = aes_rsbox[s[i]];
for (int i = 0; i < 16; ++i) s[i] ^= rk[(r<<4)+i];
if (r == 0) break;
for (int i = 0; i < 4; ++i) {
int j = i << 2;
uint8_t a = s[j], b = s[j+1], c = s[j+2], d = s[j+3];
s[j] = mul(a,0x0e) ^ mul(b,0x0b) ^ mul(c,0x0d) ^ mul(d,0x09);
s[j+1] = mul(a,0x09) ^ mul(b,0x0e) ^ mul(c,0x0b) ^ mul(d,0x0d);
s[j+2] = mul(a,0x0d) ^ mul(b,0x09) ^ mul(c,0x0e) ^ mul(d,0x0b);
s[j+3] = mul(a,0x0b) ^ mul(b,0x0d) ^ mul(c,0x09) ^ mul(d,0x0e);
}
}
}
static void aes_sw_cbc_decrypt(AES_ctx* ctx, uint8_t* buf, size_t len) {
uint8_t tmp[16], niv[16];
for (size_t i = 0; i < len; i += 16) {
for (int j = 0; j < 16; ++j) { niv[j] = buf[i+j]; tmp[j] = buf[i+j]; }
aes_sw_decrypt_block(tmp, ctx->round_keys);
for (int j = 0; j < 16; ++j) buf[i+j] = tmp[j] ^ ctx->iv[j];
for (int j = 0; j < 16; ++j) ctx->iv[j] = niv[j];
}
}
static void aes_sw_encrypt_block(uint8_t* s, const uint8_t* rk) {
uint8_t t;
for (int i = 0; i < 16; ++i) s[i] ^= rk[i];
for (int r = 1; ; ++r) {
for (int i = 0; i < 16; ++i) s[i] = aes_sbox[s[i]];
t = s[1]; s[1] = s[5]; s[5] = s[9]; s[9] = s[13]; s[13] = t;
t = s[2]; s[2] = s[10]; s[10] = t;
t = s[6]; s[6] = s[14]; s[14] = t;
t = s[3]; s[3] = s[15]; s[15] = s[11]; s[11] = s[7]; s[7] = t;
if (r == 10) { for (int i = 0; i < 16; ++i) s[i] ^= rk[(r<<4)+i]; break; }
for (int i = 0; i < 4; ++i) {
int j = i << 2;
uint8_t a = s[j], b = s[j+1], c = s[j+2], d = s[j+3];
s[j] = xtime(a) ^ xtime(b) ^ b ^ c ^ d;
s[j+1] = a ^ xtime(b) ^ xtime(c) ^ c ^ d;
s[j+2] = a ^ b ^ xtime(c) ^ xtime(d) ^ d;
s[j+3] = xtime(a) ^ a ^ b ^ c ^ xtime(d);
}
for (int i = 0; i < 16; ++i) s[i] ^= rk[(r<<4)+i];
}
}
static void aes_sw_cbc_encrypt(AES_ctx* ctx, uint8_t* buf, size_t len) {
for (size_t i = 0; i < len; i += 16) {
for (int j = 0; j < 16; ++j) buf[i+j] ^= ctx->iv[j];
aes_sw_encrypt_block(buf + i, ctx->round_keys);
for (int j = 0; j < 16; ++j) ctx->iv[j] = buf[i+j];
}
}
void AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) {
aes_init_rsbox();
#if AES_HW_AVAILABLE
if (aes_hw_supported()) {
aes_hw_key_expand(ctx, key);
} else
#endif
{
aes_sw_key_expand(ctx->round_keys, key);
}
for (int i = 0; i < 16; ++i) ctx->iv[i] = iv[i];
}
void AES_ctx_set_iv(AES_ctx* ctx, const uint8_t* iv) {
for (int i = 0; i < 16; ++i) ctx->iv[i] = iv[i];
}
void AES_CBC_decrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t len) {
#if AES_HW_AVAILABLE
if (aes_hw_supported()) { aes_hw_cbc_decrypt(ctx, buf, len); return; }
#endif
aes_sw_cbc_decrypt(ctx, buf, len);
}
void AES_CBC_encrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t len) {
aes_sw_cbc_encrypt(ctx, buf, len);
}
+114
View File
@@ -0,0 +1,114 @@
#include "crypto.h"
#include "aes.h"
#include "common.h"
const uint8_t NTFS_HEADER[16] = {
0xeb, 0x52, 0x90, 0x4e, 0x54, 0x46, 0x53, 0x20,
0x20, 0x20, 0x20, 0x00, 0x10, 0x01, 0x00, 0x00
};
const uint8_t EXFAT_HEADER[16] = {
0xeb, 0x76, 0x90, 0x45, 0x58, 0x46, 0x41, 0x54,
0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00
};
const uint8_t OPTION_KEY[16] = {
0x5c, 0x84, 0xa9, 0xe7, 0x26, 0xea, 0xa5, 0xdd,
0x35, 0x1f, 0x2b, 0x07, 0x50, 0xc2, 0x36, 0x97
};
const uint8_t OPTION_IV[16] = {
0xc0, 0x63, 0xbf, 0x6f, 0x56, 0x2d, 0x08, 0x4d,
0x79, 0x63, 0xc9, 0x87, 0xf5, 0x28, 0x17, 0x61
};
const uint8_t APM3_SEED[96] = {
0xC7, 0x3C, 0xDD, 0xBF, 0x7A, 0xFB, 0x0E, 0xBC, 0xE6, 0xDE, 0xD4, 0xD9, 0xB3, 0xDF, 0x3B, 0x03,
0x3F, 0xE1, 0x40, 0xE4, 0xF4, 0xFF, 0x96, 0xC5, 0x79, 0x90, 0x8B, 0x5B, 0x69, 0x6A, 0xBE, 0xEE,
0x32, 0x6C, 0x5E, 0xEA, 0x47, 0xC0, 0xA3, 0x40, 0x51, 0xDC, 0x55, 0xBF, 0x8C, 0x2A, 0x80, 0x7B,
0xE4, 0xC6, 0xE3, 0xEF, 0x2F, 0x15, 0x30, 0x84, 0x69, 0x3C, 0xE2, 0xD2, 0x1E, 0xF1, 0xBB, 0x13,
0xDC, 0xC9, 0x6D, 0x31, 0x7C, 0x3F, 0xCC, 0x7A, 0xB9, 0x44, 0x63, 0x6D, 0x65, 0xC2, 0x8B, 0xB8,
0xE2, 0xF7, 0x74, 0x8D, 0xC6, 0x42, 0x08, 0xA8, 0x73, 0x41, 0x4B, 0x78, 0x7E, 0x3F, 0x18, 0x66
};
const uint8_t APM3_KEY[16] = {
0x87, 0x3d, 0xf6, 0x32, 0xb9, 0x88, 0xae, 0x14,
0xaa, 0x9f, 0x73, 0x6b, 0x03, 0xa5, 0x1c, 0x4f
};
const uint8_t APM3_IV[16] = {
0x35, 0x7d, 0xc1, 0x90, 0x30, 0xd8, 0xe8, 0xd4,
0x94, 0x1a, 0x7e, 0x6a, 0xce, 0xb9, 0x4e, 0x4c
};
const uint8_t BOOTID_KEY[16] = {
0x09, 0xCA, 0x5E, 0xFD, 0x30, 0xC9, 0xAA, 0xEF,
0x38, 0x04, 0xD0, 0xA7, 0xE3, 0xFA, 0x71, 0x20
};
const uint8_t BOOTID_IV[16] = {
0xB1, 0x55, 0xC2, 0x2C, 0x2E, 0x7F, 0x04, 0x91,
0xFA, 0x7F, 0x0F, 0xDC, 0x21, 0x7A, 0xFF, 0x90
};
bool key_derive(const char* id, uint8_t out_key[16], uint8_t out_iv[16]) {
if (!id || strlen(id) < 4) {
return false;
}
uint8_t data[96];
memcpy(data, APM3_SEED, 96);
AES_ctx ctx;
AES_init_ctx_iv(&ctx, APM3_KEY, APM3_IV);
AES_CBC_decrypt_buffer(&ctx, data, 96);
uint8_t intermediate[32];
AES_init_ctx_iv(&ctx, data, data + 16);
memcpy(intermediate, data + 64, 32);
AES_CBC_encrypt_buffer(&ctx, intermediate, 32);
uint8_t pKey[16];
uint8_t pIv[16];
memcpy(pKey, intermediate, 16);
memcpy(pIv, intermediate + 16, 16);
for (int i = 0; i < 16; i++) {
pKey[i] ^= (uint8_t)id[i % 4];
pIv[i] ^= (uint8_t)id[i % 4];
}
memcpy(out_key, pKey, 16);
memcpy(out_iv, pIv, 16);
return true;
}
void iv_page(uint64_t off, const uint8_t* base, uint8_t* out) {
__asm__ volatile (
"movq %[off], %%xmm0\n\t"
"punpcklqdq %%xmm0, %%xmm0\n\t"
"movdqu (%[base]), %%xmm1\n\t"
"pxor %%xmm0, %%xmm1\n\t"
"movdqu %%xmm1, (%[out])\n\t"
:
: [off] "r" (off), [base] "r" (base), [out] "r" (out)
: "xmm0", "xmm1", "memory"
);
}
bool iv_file(const uint8_t key[16], const uint8_t* hdr, const uint8_t* page, uint8_t out[16]) {
uint8_t iv[16];
uint8_t header[16];
memcpy(header, page, 16);
iv_page(0, hdr, iv);
AES_ctx ctx;
AES_init_ctx_iv(&ctx, key, iv);
AES_CBC_decrypt_buffer(&ctx, header, 16);
memcpy(out, header, 16);
return true;
}
bool key_game(const char* id, GameKeys* out) {
if (key_lookup(id, out->key, out->iv, &out->external)) {
out->has_iv = true;
return true;
}
out->external = false;
return false;
}
+420
View File
@@ -0,0 +1,420 @@
#include "exfat.h"
#include "progress.h"
static void count_directory_size(ExfatContext* ctx, uint32_t start_cluster);
static uint64_t get_cluster_offset(ExfatContext* ctx, uint32_t cluster) {
return ctx->cluster_heap_offset_bytes + ((uint64_t)(cluster - 2) * ctx->bytes_per_cluster);
}
static bool exfat_read(ExfatContext* ctx, void* buffer, uint64_t offset, size_t size) {
if (ctx->stream) {
return stream_read(ctx->stream, buffer, offset, size);
}
if (FSEEKO(ctx->fp, offset, SEEK_SET) != 0) {
return false;
}
return fread(buffer, 1, size, ctx->fp) == size;
}
static bool read_cluster(ExfatContext* ctx, uint32_t cluster, void* buffer) {
uint32_t offset = get_cluster_offset(ctx, cluster);
return exfat_read(ctx, buffer, offset, ctx->bytes_per_cluster);
}
static uint32_t get_next_cluster(ExfatContext* ctx, uint32_t cluster) {
uint32_t max_cluster = ctx->fat_length_bytes / sizeof(uint32_t);
if (cluster >= max_cluster) {
return 0;
}
uint32_t next = ctx->fat[cluster];
if (next >= 0xFFFFFFF8) {
return 0;
}
if (next == 0)
{
return cluster + 1;
}
if (next >= max_cluster) {
return 0;
}
return next;
}
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;
}
setvbuf(out, NULL, _IOFBF, ctx->bytes_per_cluster);
uint32_t current_cluster = file->first_cluster;
uint64_t remaining = file->data_length;
bool success = true;
while (remaining > 0 && current_cluster != 0 && success) {
if (!read_cluster(ctx, current_cluster, ctx->io_buf)) {
success = false;
break;
}
size_t write_size = (remaining > ctx->bytes_per_cluster) ? ctx->bytes_per_cluster : (size_t)remaining;
if (fwrite(ctx->io_buf, 1, write_size, out) != write_size) {
success = false;
break;
}
remaining -= write_size;
if (file->no_fat_chain) {
current_cluster++;
} else {
current_cluster = get_next_cluster(ctx, current_cluster);
}
ctx->extracted_bytes += write_size;
if (ctx->progress) {
progress_update((Progress*)ctx->progress, ctx->extracted_bytes);
}
}
fclose(out);
if (success) {
ctx->files_extracted++;
if (file->modify_timestamp != 0) {
uint64_t mtime = exfat_timestamp_to_ntfs(file->modify_timestamp, file->modify_10ms, file->modify_utc_offset);
uint64_t atime = exfat_timestamp_to_ntfs(file->access_timestamp, 0, file->access_utc_offset);
set_file_times(output_path, mtime, atime);
}
}
return success;
}
#define EXFAT_MAX_RECURSION_DEPTH 128
static bool process_directory_recursive(ExfatContext* ctx, uint32_t start_cluster, const char* output_dir, int depth) {
if (depth > EXFAT_MAX_RECURSION_DEPTH) {
fprintf(stderr, "depth\n");
return true;
}
uint32_t current_cluster = start_cluster;
bool finished = false;
while (!finished && current_cluster != 0) {
if (!read_cluster(ctx, current_cluster, ctx->cluster_buf)) {
return false;
}
uint32_t entries_per_cluster = ctx->bytes_per_cluster / EXFAT_ENTRY_SIZE;
uint32_t entry_offset = 0;
for (uint32_t i = 0; i < entries_per_cluster; ) {
uint8_t* entry_ptr = ctx->cluster_buf + entry_offset;
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_offset += 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];
uint16_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) {
full_name_unicode[pos++] = name_entry->file_name[j];
}
}
}
full_name_unicode[pos] = 0;
fs_name_to_utf8(full_name_unicode, pos, full_name, sizeof(full_name));
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);
file_info.no_fat_chain = ((stream_entry->flags & 0x02) != 0);
file_info.modify_timestamp = file_entry->last_modified_timestamp;
file_info.access_timestamp = file_entry->last_access_timestamp;
file_info.modify_10ms = file_entry->last_modified_10ms;
file_info.modify_utc_offset = (int8_t)file_entry->last_modified_utc_offset;
file_info.access_utc_offset = (int8_t)file_entry->last_access_utc_offset;
char full_path[MAX_PATH_LENGTH];
if (!combine_path(full_path, sizeof(full_path), output_dir, file_info.name)) {
fprintf(stderr, "path:%s\n", file_info.name);
continue;
}
int total_entries = 2 + num_name_entries;
i += total_entries;
entry_offset += EXFAT_ENTRY_SIZE * total_entries;
if (file_info.is_directory) {
if (create_directories(full_path)) {
process_directory_recursive(ctx, file_info.first_cluster, full_path, depth + 1);
if (file_info.modify_timestamp != 0) {
if (ctx->deferred_count >= ctx->deferred_capacity) {
uint32_t new_cap = ctx->deferred_capacity ? ctx->deferred_capacity * 2 : 256;
DeferredDirTime* new_buf = realloc(ctx->deferred_dirs, new_cap * sizeof(DeferredDirTime));
if (new_buf) {
ctx->deferred_dirs = new_buf;
ctx->deferred_capacity = new_cap;
}
}
if (ctx->deferred_count < ctx->deferred_capacity) {
DeferredDirTime* d = &ctx->deferred_dirs[ctx->deferred_count++];
STRCPY_S(d->path, sizeof(d->path), full_path);
d->mtime = exfat_timestamp_to_ntfs(file_info.modify_timestamp, file_info.modify_10ms, file_info.modify_utc_offset);
d->atime = exfat_timestamp_to_ntfs(file_info.access_timestamp, 0, file_info.access_utc_offset);
}
}
if (!read_cluster(ctx, current_cluster, ctx->cluster_buf)) {
return false;
}
}
}
else {
extract_file(ctx, &file_info, full_path);
}
continue;
}
else {
i++;
entry_offset += EXFAT_ENTRY_SIZE;
}
}
if (!finished) {
current_cluster = get_next_cluster(ctx, current_cluster);
}
}
return true;
}
static bool process_directory(ExfatContext* ctx, uint32_t start_cluster, const char* output_dir) {
return process_directory_recursive(ctx, start_cluster, output_dir, 0);
}
static bool exfat_setup_fields(ExfatContext* ctx) {
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);
ctx->cluster_buf = malloc(ctx->bytes_per_cluster);
ctx->io_buf = malloc(ctx->bytes_per_cluster);
if (!ctx->fat || !ctx->cluster_buf || !ctx->io_buf) {
free(ctx->fat);
free(ctx->cluster_buf);
free(ctx->io_buf);
return false;
}
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;
}
if (!exfat_setup_fields(ctx)) {
fclose(ctx->fp);
return false;
}
if (fseek(ctx->fp, (long)ctx->fat_offset_bytes, SEEK_SET) != 0 ||
fread(ctx->fat, 1, ctx->fat_length_bytes, ctx->fp) != ctx->fat_length_bytes) {
exfat_close(ctx);
return false;
}
return true;
}
bool exfat_init_stream(ExfatContext* ctx, DecryptStream* stream) {
memset(ctx, 0, sizeof(ExfatContext));
ctx->stream = stream;
if (!exfat_read(ctx, &ctx->boot_sector, 0, sizeof(ExfatBootSector))) {
return false;
}
if (!exfat_setup_fields(ctx)) return false;
if (!exfat_read(ctx, ctx->fat, ctx->fat_offset_bytes, ctx->fat_length_bytes)) {
exfat_close(ctx);
return false;
}
return true;
}
static void count_directory_size_recursive(ExfatContext* ctx, uint32_t start_cluster, int depth) {
if (depth > EXFAT_MAX_RECURSION_DEPTH) return;
uint32_t current_cluster = start_cluster;
bool finished = false;
while (!finished && current_cluster != 0) {
if (!read_cluster(ctx, current_cluster, ctx->cluster_buf)) {
break;
}
uint32_t entries_per_cluster = ctx->bytes_per_cluster / EXFAT_ENTRY_SIZE;
uint32_t entry_offset = 0;
for (uint32_t i = 0; i < entries_per_cluster; ) {
uint8_t* entry_ptr = ctx->cluster_buf + entry_offset;
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_offset += EXFAT_ENTRY_SIZE;
continue;
}
bool is_directory = ((file_entry->file_attributes & 0x10) != 0);
uint32_t first_cluster = stream_entry->first_cluster;
uint64_t data_length = stream_entry->data_length;
int num_name_entries = (stream_entry->name_length + 14) / 15;
int total_entries = 2 + num_name_entries;
i += total_entries;
entry_offset += EXFAT_ENTRY_SIZE * total_entries;
if (is_directory) {
count_directory_size_recursive(ctx, first_cluster, depth + 1);
if (!read_cluster(ctx, current_cluster, ctx->cluster_buf)) {
return;
}
} else {
ctx->total_bytes += data_length;
}
continue;
}
i++;
entry_offset += EXFAT_ENTRY_SIZE;
}
if (!finished) {
current_cluster = get_next_cluster(ctx, current_cluster);
}
}
}
static void count_directory_size(ExfatContext* ctx, uint32_t start_cluster) {
count_directory_size_recursive(ctx, start_cluster, 0);
}
bool exfat_extract_all(ExfatContext* ctx, const char* output_dir) {
if (!create_directories(output_dir)) {
return false;
}
ctx->total_bytes = 0;
ctx->extracted_bytes = 0;
count_directory_size(ctx, ctx->boot_sector.first_cluster_of_root_dir);
if (ctx->verbose && !ctx->silent) {
printf("%llu B\n", (unsigned long long)ctx->total_bytes);
}
Progress progress;
if (!ctx->silent) {
progress_init(&progress, ctx->total_bytes > 0 ? ctx->total_bytes : 1);
ctx->progress = &progress;
}
bool result = process_directory(ctx, ctx->boot_sector.first_cluster_of_root_dir, output_dir);
if (!ctx->silent && ctx->progress) {
progress_finish(&progress);
}
ctx->progress = NULL;
for (uint32_t i = ctx->deferred_count; i > 0; i--) {
DeferredDirTime* d = &ctx->deferred_dirs[i - 1];
set_dir_times(d->path, d->mtime, d->atime);
}
free(ctx->deferred_dirs);
ctx->deferred_dirs = NULL;
ctx->deferred_count = 0;
ctx->deferred_capacity = 0;
return result;
}
void exfat_close(ExfatContext* ctx) {
if (!ctx->stream && ctx->fp) {
fclose(ctx->fp);
ctx->fp = NULL;
}
if (ctx->fat) {
free(ctx->fat);
ctx->fat = NULL;
}
free(ctx->cluster_buf);
ctx->cluster_buf = NULL;
free(ctx->io_buf);
ctx->io_buf = NULL;
}
+129
View File
@@ -0,0 +1,129 @@
#include "crypto.h"
#include "common.h"
typedef struct {
char game_id[8];
uint8_t key[16];
uint8_t iv[16];
} ExternalKey;
static ExternalKey* external_keys;
static size_t external_keys_count;
static size_t external_keys_cap;
static bool external_keys_loaded;
#include "keys.h"
static int hex_val(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
static bool parse_hex_bytes(const char* start, const char* end, uint8_t* out, size_t count) {
size_t idx = 0;
const char* p = start;
while (p < end && idx < count) {
while (p < end && !isxdigit(*p)) p++;
if (p >= end) break;
int hi = hex_val(*p++);
while (p < end && !isxdigit(*p)) p++;
if (p >= end) break;
int lo = hex_val(*p++);
if (hi < 0 || lo < 0) return false;
out[idx++] = (hi << 4) | lo;
}
return idx == count;
}
static void load_keys_from_file(const char* path) {
FILE* f = FOPEN(path, "r");
if (!f) return;
char line[512];
while (fgets(line, sizeof(line), f)) {
char* start = strchr(line, '{');
if (!start) continue;
char* quote1 = strchr(start, '"');
if (!quote1) continue;
char* quote2 = strchr(quote1 + 1, '"');
if (!quote2) continue;
size_t id_len = quote2 - quote1 - 1;
if (id_len == 0 || id_len > 7) continue;
char* brace1 = strchr(quote2, '{');
if (!brace1) continue;
char* brace1_end = strchr(brace1, '}');
if (!brace1_end) continue;
char* brace2 = strchr(brace1_end, '{');
if (!brace2) continue;
char* brace2_end = strchr(brace2, '}');
if (!brace2_end) continue;
if (external_keys_count >= external_keys_cap) {
size_t new_cap = external_keys_cap ? external_keys_cap * 2 : 8;
ExternalKey* new_keys = realloc(external_keys, new_cap * sizeof(ExternalKey));
if (!new_keys) break;
external_keys = new_keys;
external_keys_cap = new_cap;
}
ExternalKey* k = &external_keys[external_keys_count];
memset(k, 0, sizeof(*k));
memcpy(k->game_id, quote1 + 1, id_len);
if (!parse_hex_bytes(brace1, brace1_end, k->key, 16)) continue;
if (!parse_hex_bytes(brace2, brace2_end, k->iv, 16)) continue;
external_keys_count++;
}
fclose(f);
}
static void try_load_external_keys(void) {
if (external_keys_loaded) return;
external_keys_loaded = true;
load_keys_from_file("keys.inc");
}
static size_t count_embedded_keys(void) {
size_t count = 0;
const GameKeyEntry* entry = embedded_keys;
while (entry->game_id != NULL) { count++; entry++; }
return count;
}
bool key_any(void) {
try_load_external_keys();
return count_embedded_keys() > 0 || external_keys_count > 0;
}
bool key_lookup(const char* id, uint8_t out_key[16], uint8_t out_iv[16], bool* from_external) {
try_load_external_keys();
if (from_external) *from_external = false;
for (size_t i = 0; i < external_keys_count; i++) {
if (strcmp(external_keys[i].game_id, id) == 0) {
memcpy(out_key, external_keys[i].key, 16);
memcpy(out_iv, external_keys[i].iv, 16);
if (from_external) *from_external = true;
return true;
}
}
const GameKeyEntry* entry = embedded_keys;
while (entry->game_id != NULL) {
if (strcmp(entry->game_id, id) == 0) {
memcpy(out_key, entry->key, 16);
memcpy(out_iv, entry->iv, 16);
return true;
}
entry++;
}
return false;
}
+1249
View File
File diff suppressed because it is too large Load Diff
+1188
View File
File diff suppressed because it is too large Load Diff
+1669
View File
File diff suppressed because it is too large Load Diff
+158
View File
@@ -0,0 +1,158 @@
#include "stream.h"
#include "crypto.h"
#include "common.h"
#include "ntfs.h"
static bool read_from_runs_internal(void* ntfs_ctx, const DataRun* runs, int run_count,
uint64_t file_size, uint64_t read_offset, void* buffer, size_t read_size);
bool stream_init(DecryptStream* ds, FILE* fp, uint64_t data_offset,
uint64_t data_size, const uint8_t key[16], const uint8_t iv[16]) {
if (!ds || !fp) return false;
memset(ds, 0, sizeof(DecryptStream));
ds->fp = fp;
ds->parent_stream = NULL;
ds->run_source = NULL;
ds->data_offset = data_offset;
ds->data_size = data_size;
memcpy(ds->key, key, 16);
memcpy(ds->file_iv, iv, 16);
ds->cached_page_offset = (uint64_t)-1;
ds->file_pos = (uint64_t)-1;
AES_init_ctx_iv(&ds->aes_ctx, key, iv);
return true;
}
bool stream_init_from_runs(DecryptStream* ds, RunSource* source,
const uint8_t key[16], const uint8_t iv[16]) {
if (!ds || !source) return false;
memset(ds, 0, sizeof(DecryptStream));
ds->fp = NULL;
ds->parent_stream = NULL;
ds->run_source = source;
ds->data_offset = 0;
ds->data_size = source->file_size;
memcpy(ds->key, key, 16);
memcpy(ds->file_iv, iv, 16);
ds->cached_page_offset = (uint64_t)-1;
ds->file_pos = (uint64_t)-1;
AES_init_ctx_iv(&ds->aes_ctx, key, iv);
return true;
}
static bool read_from_runs_internal(void* ntfs_ctx, const DataRun* runs, int run_count,
uint64_t file_size, uint64_t read_offset, void* buffer, size_t read_size) {
if (read_offset + read_size > file_size) {
return false;
}
NTFSContext* ctx = (NTFSContext*)ntfs_ctx;
uint64_t current_file_pos = 0;
uint8_t* out = (uint8_t*)buffer;
size_t bytes_remaining = read_size;
for (int i = 0; i < run_count && bytes_remaining > 0; i++) {
uint64_t run_bytes = runs[i].length * ctx->bytes_per_cluster;
uint64_t run_start = current_file_pos;
uint64_t run_end = run_start + run_bytes;
if (read_offset < run_end && read_offset + read_size > run_start) {
uint64_t start_in_run = (read_offset > run_start) ? (read_offset - run_start) : 0;
uint64_t end_in_run = ((read_offset + read_size) < run_end) ?
(read_offset + read_size - run_start) : run_bytes;
size_t chunk_size = (size_t)(end_in_run - start_in_run);
uint64_t disk_offset = ctx->data_start_offset +
(runs[i].offset * ctx->bytes_per_cluster) + start_in_run;
if (!stream_read(ctx->stream, out, disk_offset, chunk_size)) {
return false;
}
out += chunk_size;
bytes_remaining -= chunk_size;
}
current_file_pos = run_end;
}
return bytes_remaining == 0;
}
bool stream_read_raw(void* ntfs_ctx, const DataRun* runs, int run_count,
uint64_t file_size, uint64_t offset, void* buffer, size_t size) {
return read_from_runs_internal(ntfs_ctx, runs, run_count, file_size, offset, buffer, size);
}
bool stream_read(DecryptStream* ds, void* buffer, uint64_t offset, size_t size) {
if (!ds || !buffer || size == 0) return false;
if (offset > ds->data_size) return false;
if ((uint64_t)size > ds->data_size - offset) return false;
uint8_t* out = (uint8_t*)buffer;
size_t bytes_read = 0;
while (bytes_read < size) {
uint64_t current_offset = offset + bytes_read;
uint64_t page_offset = (current_offset / DECRYPT_PAGE_SIZE) * DECRYPT_PAGE_SIZE;
size_t offset_in_page = (size_t)(current_offset % DECRYPT_PAGE_SIZE);
if (ds->cached_page_offset != page_offset) {
size_t page_bytes_available = (size_t)(ds->data_size - page_offset);
size_t read_size = (page_bytes_available > DECRYPT_PAGE_SIZE)
? DECRYPT_PAGE_SIZE : page_bytes_available;
if (ds->parent_stream) {
if (!stream_read(ds->parent_stream, ds->page_buffer, page_offset, read_size)) {
return false;
}
} else if (ds->run_source) {
uint64_t run_offset = ds->data_offset + page_offset;
if (!read_from_runs_internal(ds->run_source->ntfs_ctx, ds->run_source->runs,
ds->run_source->run_count, ds->run_source->file_size,
run_offset, ds->page_buffer, read_size)) {
return false;
}
} else {
uint64_t file_pos_wanted = ds->data_offset + page_offset;
if (ds->file_pos != file_pos_wanted) {
if (FSEEKO(ds->fp, file_pos_wanted, SEEK_SET) != 0) return false;
}
if (fread(ds->page_buffer, 1, read_size, ds->fp) != read_size) return false;
ds->file_pos = file_pos_wanted + read_size;
}
if (read_size < DECRYPT_PAGE_SIZE) {
memset(ds->page_buffer + read_size, 0, DECRYPT_PAGE_SIZE - read_size);
}
uint8_t page_iv[16];
iv_page(page_offset, ds->file_iv, page_iv);
AES_ctx_set_iv(&ds->aes_ctx, page_iv);
AES_CBC_decrypt_buffer(&ds->aes_ctx, ds->page_buffer, DECRYPT_PAGE_SIZE);
ds->cached_page_offset = page_offset;
}
size_t bytes_in_page = DECRYPT_PAGE_SIZE - offset_in_page;
size_t bytes_remaining = size - bytes_read;
size_t copy_size = (bytes_in_page < bytes_remaining) ? bytes_in_page : bytes_remaining;
uint64_t max_valid = ds->data_size - current_offset;
if (copy_size > max_valid) copy_size = (size_t)max_valid;
memcpy(out + bytes_read, ds->page_buffer + offset_in_page, copy_size);
bytes_read += copy_size;
if (copy_size == 0) break;
}
return bytes_read == size;
}