mirror of
https://github.com/Elpisdev/unsegaREBORN.git
synced 2026-09-22 22:28:11 +03:00
major rework
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
# build outputs
|
||||
build/
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# keys (use keys.inc.example as template)
|
||||
include/keys.inc
|
||||
@@ -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
|
||||
@@ -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/>
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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, ×) == 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, ×) == 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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
static const GameKeyEntry embedded_keys[] = {
|
||||
#if __has_include("keys.inc")
|
||||
#include "keys.inc"
|
||||
#endif
|
||||
{NULL, {0}, {0}, false}
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
+1188
File diff suppressed because it is too large
Load Diff
+1669
File diff suppressed because it is too large
Load Diff
+158
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user