Lots of Updates (expand for more):

- Started implementing new parser for chunked data
- Started documenting data formats
- Started dissector for network protocol
- Added AI-Graph renderer (converts .pth files to python data you can import into Blender)
- Added Script to convert savefile to JSON
- Added (old) parser for chunked data format
- Added basic parser for LFVF data section (Vertex Data)
- Added script to analyze and filter read trace generated with frida script
- Added various Frida scripts
This commit is contained in:
Daniel S. 2020-08-04 18:05:34 +02:00
parent aabacafd9c
commit 8d92f25b8c
47 changed files with 2744 additions and 411 deletions

2
.gitignore vendored
View file

@ -265,3 +265,5 @@ __pycache__/
ScrapHacks/build/*
ScrapHacks/src/D3D8_VMT.hpp
.vscode/c_cpp_properties.json
tools/*.log
frida/*.mp

108
NOTES.md
View file

@ -1,6 +1,6 @@
# Infos
- Engine: ScrapEngine
- Engine: ScrapEngine/Mercury Engine
- Ingame Scripting Language: Python 1.5.2
- Interesting memory locations and functions are noted in `config.yml`
@ -8,6 +8,8 @@
- `-console`: open external console window on start
- `-wideWindow`: start game in widescreen mode
- `-dedicated`: start in mutliplayer dedicated server mode (needs to be used with `-server`)
- `-server`: start in multiplayer server mode
# Functions identified:
@ -23,11 +25,11 @@
## External Console (Scenegraph Debugging?) (Handler @ `0x5f9520`):
* `listar luces`
* `listar`
* `arbol` (Patch Scrap.exe@offset 0x314bc9 replace 0x20 with 0x00 (or just type `arbol ` with a space at the end))
* `mem`
* `ver uniones`
* `listar luces` List lights in scene
* `listar` list models in scene
* `arbol <model_name>` show details for model
* `mem` (doesn't do anything?)
* `ver uniones`
* Easter Eggs:
* `imbecil`
* `idiota`
@ -67,12 +69,11 @@ unsigned long hash(const unsigned char *s)
## Other Functions:
Check `r2_analyze.py` for full list
Check `config.yml` for full list
## File Index struct @ `0x7fcbec`
```cpp
struct FileEntry {
uint32_t offset;
uint32_t size;
@ -87,7 +88,7 @@ struct FileIDX {
};
```
## Packed Index struct (array @ `0x7fc1b0`)
## Packed Index struct (array of 0x80 entries @ `0x7fc1b0`)
```cpp
struct PackedIDX {
void** VMT;
@ -111,7 +112,10 @@ struct CPP_Callback {
}
```
## Game engine Variables Pointer @ `0x7FBE4C`
## Game engine Variables Hashtable @ `0x7fbe50`
## Game engine Variables @ `0x7fbe4c`
Structure:
@ -130,20 +134,21 @@ struct GameVar {
Types
| Value | Type |
| ------ | ----------------------- |
| `0x10` | const char* |
| `0x20` | int32_t |
| `0x30` | User Control Definition |
| `0x40` | float |
| `0x60` | Callback function |
| Value | Type |
|-------|-----------------|
| `0x1` | const char* |
| `0x2` | int32_t |
| `0x3` | List of Defines |
| `0x4` | float |
| `0x5` | function |
| `0x6` | Script function |
## Game World/State Pointer @ `0x7fe944`
Points to World struct
| Offset | Type | Description |
| ------ | ------------------------ | -------------------------------------- |
|--------|--------------------------|----------------------------------------|
| 0x0000 | `void**` | Virtual Method Table |
| 0x0004 | `uint32_t` | Size of Entity Hashtable |
| 0x0008 | `void**` | Pointer to Entity Hashtable |
@ -172,6 +177,15 @@ Points to World struct
| 0x2238 | `???` | Used in `World_Init` |
| 0x2254 | `float` | Used in `World_Init` |
## cPyEntity structure
| Offset | Type | Description |
|--------|----------|----------------------|
| 0x0000 | `void**` | Virtual Method Table |
| 0x0004 | `char*` | Name |
| 0x0008 | `void*` | ??? |
## Entity Hash Table
Hash-function used: [PJW](https://en.wikipedia.org/wiki/PJW_hash_function) (Same parameters as the example implementation)
@ -189,7 +203,7 @@ struct HT_Entry {
Data format:
| Offset | Type | Description |
| ------ | ------------- | ------------------------ |
|--------|---------------|--------------------------|
| 0x0 | `void**` | Virtual Method Table (?) |
| 0x4 | `const char*` | name as string |
| 0x14 | `void*` | pointer to self (why?) |
@ -204,21 +218,57 @@ Attributes:
- `OnDeath`
- `OnDamage`
# File Formats
# Netplay protocol (WIP)
## .packed File Format:
Game Info Packet
```
Header:
"BFPK\0\0\0\0"
Int32ul: number of files
for each file:
Int32ul: path length
String: path
Int32ul: size
Int32ul: offset in file
Server 'B':FZ (0/10) Ver 1.0 at 192.168.99.1:28086
[0-3] header/ID?
[4-5] port (16-bit)
[6-7] max_players (16-bit)
[8-9] curr_player (16-bit)
[10-x] server name (char*)
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0019fdc0 ba ce 00 01 b6 6d 0a 00 00 00 42 00 30 fe 19 00 .....m....B.0...
0019fdd0 ff ff ff ff 27 2b b3 9b c7 3e bb 00 9c af 29 00 ....'+...>....).
0019fde0 db 69 00 00 00 00 00 00 00 00 44 65 61 74 68 4d .i........DeathM
0019fdf0 61 74 63 68 00 00 00 00 ff ff 46 5a 00 4a 91 f0 atch......FZ.J..
0019fe00 92 8b 57 4e 7f 00 00 00 10 21 fe 38 0d ae 00 00 ..WN.....!.8....
0019fe10 f0 ce f3 36 a0 e8 0b 77 a0 e8 ...6...w..
```
Player Join Packet
```
[0-3] header/ID?
[6-x] Player name
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
09c9dfe8 7f 47 00 00 00 0e 55 6e 6e 61 6d 65 64 20 50 6c .G....Unnamed Pl
09c9dff8 61 79 65 72 06 53 42 6f 73 73 31 b9 00 07 50 5f ayer.SBoss1...P_
09c9e008 42 65 74 74 79 06 4d 42 4f 53 53 31 06 4d 42 4f Betty.MBOSS1.MBO
09c9e018 53 53 31 00 00 10 30 2c 31 35 2c 30 2c 30 2c 31 SS1...0,15,0,0,1
09c9e028 35 2c 31 35 2c 31 02 00 00 00 5,15,1....
```
| Message | Description |
|------------------------------------------|-------------------------------------------------------------------|
| `5c68625c32383230395c73637261706c616e64` | "Scrapland Server" announcement broadcast (`\hb\28209\scrapland`) |
| `7f01000007` | Retrieve Game info |
| `48423d35323932322c3235363a323830383600` | Connection Information (`HB=52922,256:28086`) |
# [Notes](NOTES.md)
## File Formats
- [Chunked](file_formats/chunked.md)
- [Packed](file_formats/packed.md)
- [AI Pathfinding Graph](file_formats/ai_path.md)
# Virtual Method Tables:
check `r2_analyze.py` for full list

View file

@ -1,13 +1,24 @@
# Scrapland Reverse Engineering notes and tools
## Scripts:
* `parse_save.py`: Dumps information extracted from Save file
## Note!
All memory addresses are only valid for an unprotected `Scrap.exe` v1.0 with a SHA1 checksum of `d2dde960e8eca69d60c2e39a439088b75f0c89fa` , other version will crash if the memory offsets don't match and you try to inject ScrapHacks
[Computer Bild Spiele Issue 2006/08](https://archive.org/download/cbs-2006-08-coverdisc/) Contains a full version of the game which was used as the basis for this project
## Scripts
* `tools/rbingrep.py`: Search for pattern in all files and generate radare2 script to find all references (currently configured to search for chunked file section headers)
* `frida/`: Scripts for use with Frida
* `parse_chunked.py`: WIP Parser for the game's chunked data format (Models, Animations, Maps)
* `save_to_json.py`: Convert game save to JSON
* `scrapper.py`: Extractor and Repacker for *.packed files, needs the `construct` and `tqdm` python modules and python 3.x
- Run `scrapper.py -h` for help
* `r2_analyze.py`: uses radare2 to parse and label a lot of interesting stuff in the `Scrap.exe` binary
* `lib/dbg.py`: general Script for poking around inside the game's scripting system
- Run `import dbg` inside the Game's Console,
this will load all builtin modules and enable godmode
- Run `import dbg;dbg.init()` inside the Game's Console,
this will load all builtin modules, ScrapHacks and enable godmode
- The dbg module also enables writing to the ingame console using `print <var>`
and defines two global functions s_write() and e_write() for writing to the Ingame Console's Stdout and Stderr Stream
- `dbg.menu()` Displays the Game's built in Debug Menu (doesn't work properly)
@ -24,9 +35,15 @@ WIP Memory hacking library
# Tools used:
- [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/)
- [IDA](https://www.hex-rays.com/products/ida/index.shtml) and [x32dbg](https://x64dbg.com/)
- [Reclass.NET](https://github.com/ReClassNET/ReClass.NET)
- [HxD](https://mh-nexus.de/en/hxd/)
- [Kaitai Struct](http://kaitai.io/)
- [Radare2](https://www.radare.org/) + [Cutter](https://cutter.re/)
- Binary parsing:
- [HxD](https://mh-nexus.de/en/hxd/) for initial file analysis
- [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/) for binary parsing
- [Kaitai Struct](http://kaitai.io/) for binary parsing
- Static analysis:
- [IDA](https://www.hex-rays.com/products/ida/index.shtml) initialy used, later replaced by radare2 and Cutter
- [radare2](https://www.radare.org/)
- [Cutter](https://cutter.re/)
- Dynamic analysis:
- [x64dbg](https://x64dbg.com/) for dynamic analysis
- [Reclass.NET](https://github.com/ReClassNET/ReClass.NET) to analyze structures and classes in memory
- [Frida](https://frida.re/) for tracing and instrumenting functions

View file

@ -17,8 +17,7 @@
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "msvc-x86",
"configurationProvider": "vector-of-bool.cmake-tools",
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
"configurationProvider": "vector-of-bool.cmake-tools"
}
],
"version": 4

View file

@ -78,5 +78,11 @@
"xtr1common": "cpp",
"xtree": "cpp",
"xutility": "cpp"
}
},
"cmake.generator": "NMake Makefiles",
"cmake.preferredGenerators": [
"NMake Makefiles",
"Ninja",
"Unix Makefiles"
]
}

21
ScrapHacks/.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,21 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "${workspaceFolder}\\build.bat",
"problemMatcher": [
"$msCompile"
]
},
{
"label": "clean",
"type": "shell",
"command": "rm -Force -Recurse ${workspaceFolder}\\build",
"problemMatcher": []
}
]
}

View file

@ -1,6 +1,5 @@
cmake_minimum_required(VERSION 3.1)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
project(ScrapHacks
VERSION 1.0
DESCRIPTION "Scrapland memory hacking library"
@ -13,6 +12,8 @@ if(NOT IS_ABSOLUTE "${SCRAPLAND_DIR}" OR NOT EXISTS "${SCRAPLAND_DIR}")
message(FATAL_ERROR "Scrapland installation folder not found!")
endif()
message(STATUS "Scrapland found at ${SCRAPLAND_DIR}")
message(STATUS "Checking Scrap.exe hash")
file(SHA1 "${SCRAPLAND_DIR}/Bin/Scrap.exe" SCRAP_EXE_HASH)
@ -20,14 +21,26 @@ if(NOT ${SCRAP_EXE_HASH} STREQUAL "d2dde960e8eca69d60c2e39a439088b75f0c89fa")
message(FATAL_ERROR "Scrap.exe hash miss match!")
endif()
# ==============================
# "${SCRAPLAND_DIR}/Bin/Scrap.exe"
add_custom_target(
run
COMMAND "Scrap.exe"
WORKING_DIRECTORY "${SCRAPLAND_DIR}/Bin"
VERBATIM
)
add_dependencies(run install)
# ==============================
set(COMPONENT "ScrapHacks")
set(FETCHCONTENT_QUIET 0)
set(CMAKE_BUILD_TYPE "RelMinSize")
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}")
set(ASMJIT_EMBED true)
set(ASMTK_EMBED true)
set(ZYDIS_BUILD_TOOLS false)
set(ZYDIS_BUILD_EXAMPLES false)
set(ASMJIT_EMBED ON CACHE INTERNAL "")
set(ASMTK_EMBED ON CACHE INTERNAL "")
set(ZYDIS_BUILD_TOOLS OFF CACHE INTERNAL "")
set(ZYDIS_BUILD_EXAMPLES OFF CACHE INTERNAL "")
set(toml11_BUILD_TEST OFF CACHE INTERNAL "")
if(WIN32)
if(MSVC)
@ -44,54 +57,67 @@ endif(WIN32)
include(FetchContent)
FetchContent_Declare(
Python
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
URL https://www.python.org/ftp/python/src/py152.tgz
URL_HASH SHA1=2d648d07b1aa1aab32a3a24851c33715141779b9
)
FetchContent_Declare(
DirectX
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
URL
https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip
URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312)
FetchContent_MakeAvailable(DirectX)
URL https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip
URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312
)
FetchContent_Declare(
ASM_JIT
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:asmjit/asmjit.git
GIT_SHALLOW true
GIT_PROGRESS true
INSTALL_COMMAND ""
CONFIGURE_COMMAND ""
)
FetchContent_MakeAvailable(ASM_JIT)
FetchContent_Declare(
ASM_TK
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:asmjit/asmtk.git
GIT_SHALLOW true
GIT_PROGRESS true
INSTALL_COMMAND ""
)
FetchContent_MakeAvailable(ASM_TK)
set(ASMJIT_DIR ${asm_jit_SOURCE_DIR})
include(${asm_tk_SOURCE_DIR}/CMakeLists.txt)
FetchContent_Declare(
Zydis
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:zyantific/zydis.git
GIT_SHALLOW true
GIT_PROGRESS true
INSTALL_COMMAND ""
)
# FetchContent_MakeAvailable(Zydis)
FetchContent_MakeAvailable(Python DirectX ASM_JIT ASM_TK Zydis)
include_directories(AFTER ${directx_SOURCE_DIR}/8.0/include/ ${ASMTK_INCLUDE_DIRS} ${ASMJIT_INCLUDE_DIRS})
set(ASMJIT_DIR ${asm_jit_SOURCE_DIR})
include(${asm_tk_SOURCE_DIR}/CMakeLists.txt)
message(STATUS "Python 1.5.2: ${python_SOURCE_DIR}")
message(STATUS "DX8: ${directx_SOURCE_DIR}")
message(STATUS "Zydis: ${zydis_SOURCE_DIR}")
include_directories(AFTER
${directx_SOURCE_DIR}/8.0/include/
${python_SOURCE_DIR}/Include/
${ASMTK_INCLUDE_DIRS}
${ASMJIT_INCLUDE_DIRS}
${toml_SOURCE_DIR}
)
link_directories(AFTER ${directx_SOURCE_DIR}/8.0/lib/)
find_package(Python3 3.6 REQUIRED COMPONENTS Interpreter)
find_package(Python3 REQUIRED COMPONENTS Interpreter)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/D3D8_VMT.hpp
@ -111,7 +137,10 @@ add_library(ScrapHack SHARED
${ASMJIT_SRC}
)
set_target_properties(ScrapHack PROPERTIES SUFFIX ".pyd")
set_target_properties(ScrapHack PROPERTIES PREFIX "_")
add_dependencies(ScrapHack D3D8_VMT)
# add_dependencies(ScrapHack Python152)
# add_dependencies(ScrapHack Python152_Bin)
@ -119,10 +148,13 @@ target_link_libraries(ScrapHack
d3d8
d3dx8
dxerr8
gdiplus
# gdiplus
# PYTHON15
# Zydis
Zydis
legacy_stdio_definitions)
install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib)
target_compile_features(ScrapHack PUBLIC cxx_std_11)
install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/python/ScrapHack.py DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/python/dbg.py DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks)
target_compile_features(ScrapHack PUBLIC cxx_std_17)

View file

@ -1,10 +1,11 @@
## Features
- read and write memory
- disassemble memory (using zydis)
- change DirectX state
- Draw DirectX overlay (still need to make a useful overlay)
- Dump various data structures to the console
- Assemble and execute code on the fly
- Assemble and execute code on the fly (using asmtk)
- Can be controlled via keyboard shortcuts (TODO: allow defining own shortcuts for commands)
## Prerequisites
@ -30,7 +31,34 @@ This will find the Games's installation folder, verify that the version you have
- type `import ScrapHack`
- type `$help`
## Notes
## Config file keys
(this has only been tested with a (cracked/unpacked/de-obfuscated) `Scrap.exe` v1.0 with a SHA1 checksum of `d2dde960e8eca69d60c2e39a439088b75f0c89fa` , other version will crash if the memory offsets don't match)
- patches.asm: map of address->list of assembly instructions
- patches.hex: map of address->hex bytes
Example:
```json
{
"patches": {
"hex": {
"0xDEADBEEF": "BADFOODDEADFEED"
},
"asm": {
"0xBADF00D": [
"pushad",
"call 0xf00dbabe",
"popad",
"mov eax, 0x42",
"ret"
]
},
}
}
```
## Third-Party components used
- Zydis disassembler
- asmJIT/asmTK assembler
- nlohmann/json JSON-parser

View file

@ -5,6 +5,15 @@ if "%VSINSTALLDIR%"=="" (
call "%%i" x86
)
)
if not exist build cmake -G"NMake Makefiles" -B build
cmake --build build --target install
if "%VSINSTALLDIR%"=="" (
echo "VSINSTALLDIR" not set something is wrong!
) else (
if not exist build cmake -G"NMake Makefiles" -B build
if "%1"=="--run" (
cmake --build build --target run
) else (
cmake --build build --target install
)
)
endlocal

View file

@ -147,6 +147,13 @@ void unhook_d3d8() {
hooked=false;
}
map<size_t,void*> *dx_hooks = new map<size_t,void*>({
{VMT_IDirect3DDevice8::m_EndScene,H_EndScene},
{VMT_IDirect3DDevice8::m_BeginScene,H_BeginScene},
{VMT_IDirect3DDevice8::m_DrawIndexedPrimitive,H_DrawIndexedPrimitive},
{VMT_IDirect3DDevice8::m_SetLight,H_SetLight},
});
void hook_d3d8() {
if (hooked) {
return;
@ -162,11 +169,9 @@ void hook_d3d8() {
hFont = CreateFontA(15, 0, 0, 0, FW_EXTRABOLD, 0, 0, 0, ANSI_CHARSET, 0, 0,
0, 0, "Lucida Console");
hBrush = CreateSolidBrush(D3DCOLOR_ARGB(25, 0, 0, 0));
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_EndScene], H_EndScene);
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_BeginScene], H_BeginScene);
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_DrawIndexedPrimitive],
H_DrawIndexedPrimitive);
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_SetLight], H_SetLight);
for (auto h: *dx_hooks) {
Hook::addr(GetVTable(dev)[h.first], h.second);
}
hooked=true;
return;
}

View file

@ -9,9 +9,10 @@
using namespace std;
/*
vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) {
using namespace asmjit;
vector<uint8_t> ret;
JitRuntime rt;
CodeHolder code;
CodeInfo ci=rt.codeInfo();
@ -23,9 +24,15 @@ vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) {
code.resolveUnresolvedLinks();
code.relocateToBase(orig);
size_t code_size=code.sectionById(0)->buffer().size();
code.copyFlattenedData((void*)orig, code_size, CodeHolder::kCopyWithPadding);
uint8_t* buffer=new uint8_t[code_size];
code.copyFlattenedData((void*)buffer, code_size, CodeHolder::kCopyWithPadding);
for (size_t i=0;i<code_size;++i) {
ret.push_back(buffer[i]);
}
delete buffer;
return ret;
}
*/
class Hook {
private:
@ -33,29 +40,32 @@ class Hook {
void *orig;
void *detour;
bool enabled;
uint8_t orig_bytes[6];
uint8_t jmp_bytes[6];
uint8_t *orig_bytes;
uint8_t *jmp_bytes;
size_t size;
static map<uintptr_t, shared_ptr<Hook>> hooks;
public:
Hook(void *func, void *detour) {
// TODO: build jmp_bytes using asmjit
uintptr_t dest = reinterpret_cast<uintptr_t>(detour);
uintptr_t src = reinterpret_cast<uintptr_t>(func);
this->orig = func;
this->detour = detour;
this->jmp_bytes[0] = 0x68; // push
this->jmp_bytes[1] = (dest >> 0) & 0xff;
this->jmp_bytes[2] = (dest >> 8) & 0xff;
this->jmp_bytes[3] = (dest >> 16) & 0xff;
this->jmp_bytes[4] = (dest >> 24) & 0xff;
this->jmp_bytes[5] = 0xC3; // ret
VirtualQuery(func, &mbi, sizeof(mbi));
vector<uint8_t> code = make_trampoline(src,dest);
this->orig_bytes = new uint8_t[code.size()];
this->jmp_bytes = new uint8_t[code.size()];
this->size = code.size();
this->enabled = false;
uint8_t* func_b = reinterpret_cast<uint8_t*>(this->orig);
for (size_t i=0;i<this->size;++i) {
this->orig_bytes[i]=func_b[i];
this->jmp_bytes[i]=code[i];
}
VirtualQuery(this->orig, &mbi, sizeof(mbi));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE,
&mbi.Protect);
memcpy(this->orig_bytes, this->orig, 1 + 4 + 1);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
this->enabled = false;
cout<<"Constructed hook from "<<func<<" to "<<detour<<", size: " << this->size<<endl;
}
~Hook() {
@ -64,6 +74,11 @@ class Hook {
this->disable();
}
static void addr(uintptr_t _addr, void *detour) {
Hook::addr(reinterpret_cast<void*>(_addr),detour);
}
static void addr(void *addr, void *detour) {
cout << "Hooking: [" << addr << " -> " << detour << "]" << endl;
uintptr_t key = reinterpret_cast<uintptr_t>(detour);
@ -105,23 +120,23 @@ class Hook {
void disable() {
if (this->enabled) {
// cout << "Disabling: [" << this->orig << " <- " << this->detour <<
// "]"
// << endl;
cout << "Disabling: [" << this->orig << " <- " << this->detour <<
"]"
<< endl;
VirtualProtect(mbi.BaseAddress, mbi.RegionSize,
PAGE_EXECUTE_READWRITE, NULL);
memcpy(this->orig, this->orig_bytes, 1 + 4 + 1);
memcpy(this->orig, this->orig_bytes, this->size);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
this->enabled = false;
}
}
void enable() {
if (!this->enabled) {
// cout << "Enabling: [" << this->orig << " -> " << this->detour <<
// "]" << endl;
cout << "Enabling: [" << this->orig << " -> " << this->detour <<
"]" << endl;
VirtualProtect(mbi.BaseAddress, mbi.RegionSize,
PAGE_EXECUTE_READWRITE, NULL);
memcpy(this->orig, this->jmp_bytes, 1 + 4 + 1);
memcpy(this->orig, this->jmp_bytes, this->size);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
this->enabled = true;
}

74
ScrapHacks/src/Py_Mod.hpp Normal file
View file

@ -0,0 +1,74 @@
#pragma once
#include "Scrapland.hpp"
using namespace std;
static void *py_asm(void *self, void *args)
{
void* ret;
void *addr = nullptr;
char *asm_code;
if (!PyArg_ParseTuple(args, "s|i", &asm_code, &addr))
{
return nullptr;
}
string code(asm_code);
size_t data_size=asm_size(split(code,';'));
if (addr==nullptr) {
addr = malloc(data_size);
if (addr==nullptr) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, "malloc() failed");
scrap_log(ERR_COLOR, "\n");
return Py_BuildValue("s",nullptr);
}
char ptr[255];
snprintf(ptr,255,"Buffer @ %p\n",(void*)addr);
scrap_log(INFO_COLOR,ptr);
ret=Py_BuildValue("(l,l)", data_size, addr);
} else {
ret=Py_BuildValue("(l,l)", data_size, addr);
}
assemble(split(code,';'),reinterpret_cast<uint64_t>(addr));
return ret;
}
static void *py_dasm(void *self, void *args) {
// TODO: return list of (addr,asm)
return nullptr;
}
static void *py_read_mem(void *self, void *args)
{
//TODO: implement reading memory and return data as hex string
void* ret = nullptr;
void *addr = nullptr;
size_t size = 256;
char *data;
if (!PyArg_ParseTuple(args, "ii", &addr, &size))
{
return nullptr;
}
char* buffer=new char[size*2];
for (size_t i=0;i<size;++i) {
}
return ret;
}
static void *py_write_mem(void *self, void *args) {
//TODO: implement (return None or error string)
return nullptr;
}
static PyMethodDef SH_Methods[] = {
// {"asm", py_asm, 1},
// {"read", py_read_mem, 1},
// {"write", py_write_mem, 1},
{NULL, NULL}
};
void InitPyMod()
{
Py_InitModule("_ScrapHack", SH_Methods, nullptr, nullptr, 1007);
}

View file

@ -2,7 +2,7 @@
#include <iostream>
#include <map>
#include <string>
// #include <Python.h>
#include "Structures.hpp"
using namespace std;

View file

@ -10,6 +10,7 @@
#include <string>
#include <asmtk/asmtk.h>
#include <Zydis/Zydis.h>
#include "Scrapland.hpp"
#include "Util.hpp"
@ -40,17 +41,20 @@ size_t assemble(vector<string> assembly,uint64_t base) {
for (string line:assembly) {
if (err = p.parse((line+"\n").c_str())) {
snprintf(err_msg,1024,"PARSE ERROR: [%s] %08x (%s)\n",line.c_str(), err, DebugUtils::errorAsString(err));
cerr<<err_msg<<endl;
scrap_log(ERR_COLOR,err_msg);
return 0;
}
}
if (err=code.flatten()) {
snprintf(err_msg,1024,"FLATTEN ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err));
cerr<<err_msg<<endl;
scrap_log(ERR_COLOR,err_msg);
return 0;
}
if (err=code.resolveUnresolvedLinks()) {
snprintf(err_msg,1024,"RESOLVE ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err));
cerr<<err_msg<<endl;
scrap_log(ERR_COLOR,err_msg);
return 0;
}
@ -63,6 +67,7 @@ size_t assemble(vector<string> assembly,uint64_t base) {
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery((void*)base, &mbi, sizeof(mbi)) == 0) {
cerr<<"ERROR: "<< GetLastErrorAsString() <<endl;
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
@ -70,11 +75,13 @@ size_t assemble(vector<string> assembly,uint64_t base) {
};
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect))
{
cerr<<"ERROR: "<< GetLastErrorAsString() <<endl;
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return 0;
};
cout<<"CODE: "<< hexdump_s((void*)base,buffer.size()) << endl;
memcpy((void*)base,buffer.data(),buffer.size());
scrap_log(INFO_COLOR,"Code:"+hexdump_s((void*)base,buffer.size()));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
@ -85,7 +92,38 @@ size_t asm_size(vector<string> assembly) {
return assemble(assembly,0);
}
string disassemble(void* addr, size_t num,bool compact) {
stringstream ret;
ZyanU64 z_addr = reinterpret_cast<uintptr_t>(addr);
ZydisDecoder decoder;
ZydisFormatter formatter;
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_ADDRESS_WIDTH_32);
ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);
ZydisDecodedInstruction instruction;
while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(&decoder, addr, -1, &instruction)))
{
char buffer[256];
ZydisFormatterFormatInstruction(&formatter, &instruction, buffer, sizeof(buffer), z_addr);
if (!compact) {
ret<< "[" << std::hex << setfill('0') << setw(8) << z_addr << "]: ";
}
ret << buffer;
if (compact) {
ret<<"; ";
} else {
ret<<endl;
}
addr = reinterpret_cast<void*>(z_addr += instruction.length);
num--;
if (num==0) {
break;
}
}
return ret.str();
}
struct Command {
t_cmd_func func;
@ -217,6 +255,46 @@ get_protection(void *addr) {
return mbi.Protect;
}
void cmd_disassemble(Command* cmd, vector<string> args) {
MEMORY_BASIC_INFORMATION mbi;
if (args.size()<1) {
scrap_log(ERR_COLOR, cmd->usage);
scrap_log(ERR_COLOR, "\n");
return;
}
uintptr_t addr = UINTPTR_MAX;
size_t size = 0xff;
try {
addr = stoull(args[0], 0, 16);
if (args.size()>1) {
size = stoull(args[1]);
}
} catch (exception e) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, e.what());
scrap_log(ERR_COLOR, "\n");
return;
}
void *mptr = reinterpret_cast<void *>(addr);
if (VirtualQuery(mptr, &mbi, sizeof(mbi)) == 0) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return;
};
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE,
&mbi.Protect)) {
scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n");
return;
};
string dasm = disassemble(mptr, size, false);
scrap_log(INFO_COLOR, dasm);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
return;
}
void cmd_exec(Command* cmd, vector<string> args) {
void *addr;
MEMORY_BASIC_INFORMATION mbi;
@ -339,7 +417,7 @@ void cmd_read(Command* cmd,vector<string> args) {
return;
};
string hxd = hexdump_s(mptr, size);
scrap_log(INFO_COLOR, hxd.c_str());
scrap_log(INFO_COLOR, hxd);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
if (buffer) {
free(buffer);
@ -399,22 +477,26 @@ void cmd_dx8(Command* cmd,vector<string> args) {
}
void cmd_dump_stack(Command* cmd, vector<string> args) {
stringstream ret;
void** stack=(void**)_AddressOfReturnAddress();
cout<<"ESP: "<<stack<<endl;
for (size_t n=0;n<0x100;++n) {
if (!addr_exists(stack[n])) {
continue;
}
char R=can_read(stack[n]) ? 'R' : ' ';
char W=can_write(stack[n]) ? 'W' : ' ';
char X=can_execute(stack[n]) ? 'X' : ' ';
cout<<"ESP[" << std::hex << setfill('0') << setw(2) << n <<"]: "<<stack[n]<<" "<<R<<W<<X;
if (can_read(stack[n])) {
cout<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]"<<endl;
} else {
cout<<endl;
bool r,w,x;
char R=(r=can_read(stack[n])) ? 'R' : ' ';
char W=(w=can_write(stack[n])) ? 'W' : ' ';
char X=(x=can_execute(stack[n])) ? 'X' : ' ';
ret<< std::hex << setfill('0') << setw(8) << stack+(n*sizeof(void*)) << ": "<<stack[n]<<" "<<R<<W<<X;
if (r && !x) {
ret<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]";
} else if (r && x) {
ret<<" [ "<<disassemble(stack[n],5,true)<<"]";
}
ret<<endl;
}
scrap_log(INFO_COLOR,ret.str());
return;
}
@ -426,7 +508,7 @@ void cmd_dump_py(Command* cmd,vector<string> args) {
<< meth.second->ml_meth << endl;
}
}
scrap_log(INFO_COLOR,out.str().c_str());
scrap_log(INFO_COLOR,out.str());
}
void cmd_dump_vars(Command* cmd, vector<string> args) {
@ -437,7 +519,7 @@ void cmd_dump_vars(Command* cmd, vector<string> args) {
out<<var->name<< "[" <<std::hex <<(uint16_t)var->type <<","<< (uint16_t)var->subtype << std::dec << "]: " << var->desc<<" ("<<var->value<<", "<<var->default<<")"<<endl;
var=var->next;
}
scrap_log(INFO_COLOR,out.str().c_str());
scrap_log(INFO_COLOR,out.str());
}
void cmd_dump_ents(Command* cmd,vector<string> args) {
@ -446,7 +528,7 @@ void cmd_dump_ents(Command* cmd,vector<string> args) {
dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS), &out);
out << "Entity Lists:" << endl;
dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS), &out);
scrap_log(INFO_COLOR,out.str().c_str());
scrap_log(INFO_COLOR,out.str());
return;
}
@ -478,10 +560,14 @@ void cmd_disable_overlay(Command* cmd,vector<string> args) {
void cmd_print_alarm(Command* cmd,vector<string> args) {
stringstream out;
float *alarm = ptr<float>(P_WORLD, O_ALARM);
float *alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW);
out << "Alarm: " << alarm[0] << " + " << alarm_grow[0] << endl;
scrap_log(INFO_COLOR,out.str().c_str());
float alarm = ptr<float>(P_WORLD, O_ALARM)[0];
float alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW)[0];
if (alarm_grow<0) {
out << "Alarm: " << alarm << " - " << alarm_grow << endl;
} else {
out << "Alarm: " << alarm << " + " << alarm_grow << endl;
}
scrap_log(INFO_COLOR,out.str());
return;
}
@ -528,6 +614,7 @@ void cmd_asm(Command* cmd, vector<string> args) {
assemble(split(code,';'),buffer_addr);
}
void cmd_help(Command* cmd,vector<string> args);
static REPL* repl=new REPL(
@ -538,6 +625,7 @@ static REPL* repl=new REPL(
{"exec",new Command(cmd_exec,"Usage: $exec <addr>","Start a new thread at the specified address")},
{"asm",new Command(cmd_asm,"Usage: $asm [addr] <inst1>;<inst2>;...","Assemble instructions at address, if no address is given allocate memory and assemble code into that")},
{"stack",new Command(cmd_dump_stack,"Usage: $mem stack","Dump stack contents")},
{"dasm",new Command(cmd_disassemble,"Usage: $mem dasm <addr> [num_inst]","Disassemble memory at address")},
})},
{"unload",new Command(cmd_unload,"Usage: $unload","Unload ScrapHacks")},
{"dx8",new Command(cmd_dx8,"Usage: $dx8 <subcommand>","Manipulate DirectX 8 functions and state",{

View file

@ -6,7 +6,8 @@
#include <string>
#include <typeinfo>
#include <vector>
#include <fstream>
// #include <Python.h>
#include <Windows.h>
using namespace std;
@ -18,6 +19,7 @@ using namespace std;
#include "Scrapland.hpp"
#include "Structures.hpp"
#include "Util.hpp"
#include "Py_Mod.hpp"
bool initialized = false;
bool running = true;
@ -28,12 +30,14 @@ void DllUnload();
int hooked_console(const char *);
void hook_exit();
void setup_hooks() {
Hook::addr(reinterpret_cast<void *>(P_SCRAP_EXIT), hook_exit);
Hook::addr(reinterpret_cast<void *>(P_CON_HANDLER), hooked_console);
void setup_hooks()
{
Hook::addr(P_SCRAP_EXIT, hook_exit);
Hook::addr(P_CON_HANDLER, hooked_console);
}
void MainLoop() {
void MainLoop()
{
setup_hooks();
overlay = true;
cout << "[*] Starting main Loop" << endl;
@ -49,42 +53,56 @@ void MainLoop() {
cout << "[ F ] \"Handbrake\" (*Will* crash the game after some time!)"
<< endl;
while (running) {
while (running)
{
Sleep(100);
while (key_down('F')) {
while (key_down('F'))
{
scrap_exec("dbg.brake()");
}
if (key_down_norepeat(VK_F3)) {
if (key_down_norepeat(VK_F3))
{
break;
}
if (key_down_norepeat(VK_F7)) {
if (key_down_norepeat(VK_F7))
{
int32_t *money = ptr<int32_t>(P_WORLD, O_MONEY);
money[0] = 0x7fffffff;
}
if (key_down_norepeat(VK_F9)) {
if (key_down_norepeat(VK_F8))
{
cout << "Not yet implemented" << endl;
}
if (key_down_norepeat(VK_F9))
{
cout << "Entities:" << endl;
dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS));
cout << "Entity Lists:" << endl;
dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS));
}
if (key_down_norepeat(VK_F10)) {
if (key_down_norepeat(VK_F10))
{
scrap_exec("dbg.settrace()");
}
}
FreeLibraryAndExitThread(hMod, 0);
}
void InitConsole() {
void InitConsole()
{
char me[1024];
GetModuleFileName(hMod, me, 1024);
GetModuleFileNameA(hMod, me, 1024);
SetupConsole(me);
}
int hooked_console(const char *cmd) {
int hooked_console(const char *cmd)
{
typedef decltype(&hooked_console) t_func;
if (cmd[0] == '$') {
if (cmd[0] == '$')
{
handle_command(++cmd);
return 0;
}
@ -93,7 +111,8 @@ int hooked_console(const char *cmd) {
return ret;
}
void hook_exit() {
void hook_exit()
{
typedef decltype(&hook_exit) t_func;
shared_ptr<Hook> hook = Hook::get(hook_exit);
DllUnload();
@ -102,7 +121,8 @@ void hook_exit() {
return;
}
void DllInit(HMODULE mod) {
void DllInit(HMODULE mod)
{
hMod = mod;
char mfn[1024];
GetModuleFileNameA(0, mfn, 1024);
@ -119,36 +139,23 @@ void DllInit(HMODULE mod) {
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MainLoop, NULL, 0, 0);
cout << "[*] Starting message pump" << endl;
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return;
}
void *H_port_FixupExtension(char *name, char *filename) {
cout<<"FixupExtension: "<<name<<": "<<filename<<endl;
Hook::drop(H_port_FixupExtension);
return NULL;
}
void *H_PyEval_CallObjectWithKeywords(void *func, void *arg, void *kwarg) {
cout<<"PyEval_CallObjectWithKeywords:"<<endl;
cout<<"\t func: "<<func<<endl;
cout<<"\t arg: "<<arg<<endl;
cout<<"\t kwarg: "<<kwarg<<endl;
Hook::drop(H_PyEval_CallObjectWithKeywords);
return NULL;
}
void DllPreInit() {
void DllPreInit()
{
Sleep(100);
InitConsole();
Hook::addr(reinterpret_cast<void *>(0x5a9ca0), H_port_FixupExtension);
Hook::addr(reinterpret_cast<void *>(0x5cdb00),
H_PyEval_CallObjectWithKeywords);
InitPyMod();
}
void DllUnload() {
void DllUnload()
{
SetConsoleCtrlHandler(NULL, false);
unhook_d3d8();
Hook::clear();

View file

@ -1,6 +1,6 @@
#pragma once
#include "Structures.hpp"
#include "Util.hpp"
#include <sstream>
using namespace std;
@ -17,14 +17,21 @@ using namespace std;
#define P_PY_MODS 0x79C698
// FUNCTION ADDRESSES
// ENGINE INTERNALS
#define P_CON_HANDLER 0x402190
#define P_SCRAP_LOG 0x4134C0
#define P_SCRAP_EXEC 0x5a8390
#define P_SCRAP_EXIT 0x4010c0
#define P_D3DCHECK 0x602a70
#define P_D3DDEV 0x852914
// PYTHON FUNCTIONS
#define P_Py_InitModule 0x5A8FB0
#define P_PyArg_ParseTuple 0x5bb9d0
#define P_PyBuildValue 0x5a90f0
#define P_PyList_New 0x5b3120
#define P_PyTuple_New 0x5b91a0
#define P_PyList_SetItem 0x5b3240
#define P_PyTuple_SetItem 0x5b92a0
#define MSG_COLOR scrap_RGB(128,0,255)
@ -41,14 +48,20 @@ uint32_t scrap_RGB(uint8_t r,uint8_t g,uint8_t b) {
typedef int(_cdecl *t_scrap_log)(unsigned int color, const char *message);
typedef int(_cdecl *t_scrap_exec)(const char *code);
typedef int(_cdecl *t_PyArg_ParseTuple)(void *PyObj, char *format, ...);
typedef void*(_cdecl *t_Py_BuildValue)(char *format, ...);
typedef int(_cdecl *t_Py_InitModule)(const char *name, void *methods,
const char *doc, void *passthrough,
int module_api_version);
typedef void*(_cdecl *t_PyList_New)(char *format, ...);
typedef void*(_cdecl *t_PyTuple_New)(char *format, ...);
typedef void*(_cdecl *t_PyList_SetItem)(char *format, ...);
typedef void*(_cdecl *t_PyList_SetItem)(char *format, ...);
// GLOBAL FUNCTIONS
auto scrap_exec = (t_scrap_exec)P_SCRAP_EXEC;
auto pyarg_parsetuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple;
auto py_initmodule = (t_Py_InitModule)P_Py_InitModule;
auto PyArg_ParseTuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple;
auto Py_BuildValue = (t_Py_BuildValue)P_PyBuildValue;
auto Py_InitModule = (t_Py_InitModule)P_Py_InitModule;
int scrap_log(unsigned int color,const char* msg) {
return ((t_scrap_log)P_SCRAP_LOG)(color,msg);
@ -58,7 +71,6 @@ int scrap_log(uint8_t r,uint8_t g,uint8_t b,const char* msg) {
return ((t_scrap_log)P_SCRAP_LOG)(scrap_RGB(r,g,b),msg);
}
int scrap_log(unsigned int color,string msg) {
return ((t_scrap_log)P_SCRAP_LOG)(color,msg.c_str());
}
@ -67,6 +79,12 @@ int scrap_log(uint8_t r,uint8_t g,uint8_t b,string msg) {
return ((t_scrap_log)P_SCRAP_LOG)(scrap_RGB(r,g,b),msg.c_str());
}
int scrap_err() {
string err("Error: ");
err+=GetLastErrorAsString();
err+="\n";
return scrap_log(ERR_COLOR,err);
}
size_t size_ht(HashTable<EntityList> *ht) {
size_t cnt = 0;

View file

@ -26,7 +26,7 @@ string GetLastErrorAsString() {
size_t m_size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
NULL, errorMessageID, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
(LPSTR)&messageBuffer, 0, NULL);
string message(messageBuffer, m_size);
LocalFree(messageBuffer);

View file

@ -12,9 +12,10 @@ HANDLE hThread = INVALID_HANDLE_VALUE;
bool loaded = false;
HMODULE mod = nullptr;
DLL_EXPORT void initScrapHack() {
DLL_EXPORT void init_ScrapHack() {
DllPreInit();
if (!loaded) {
Sleep(1000);
hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)DllInit, mod,
0, 0);
CloseHandle(hThread);

View file

@ -0,0 +1,23 @@
# ScrapHack python interface
import _ScrapHack
class Mem:
def __init__(self):
return
def __getitem__(self, key):
print("GI:", key)
def __setitem__(self, key, value):
print("SI:", key, value)
Mem = Mem()
def asm(code,address=0):
"""
asm(code, address):
Assemble code at address
"""
return _ScrapHack.asm(address, code)

View file

@ -5,10 +5,12 @@ import MissionsFuncs
import SScorer
import Menu
import sys
QC = quickconsole
MF = MissionsFuncs
last_frame = None
level = 3
initialized = 0
sys.path.append(".\\pylib\\Lib")
sys.path.append(".\\pylib\\Libs")
sys.path.append(".\\pylib")
@ -16,11 +18,18 @@ sys.path.append(".\\pylib")
def reload():
sys.settrace(None)
sys.modules['__builtin__'].reload(sys.modules['dbg'])
sys.modules['__builtin__'].reload(sys.modules[__name__])
def dgb_info():
SScorer.SetLabelText(`last_frame`, Scrap.GetTime() + 0.1)
if me:
try:
dbg_text = str(SVec.Mod(me.Vel))
except:
dbg_text=""
else:
dbg_text = ""
SScorer.SetLabelText(dbg_text, Scrap.GetTime() + 0.1)
Scrap.DeleteScheduledFuncs("dbg.dbg_info")
Scrap.DeleteScheduledFuncs("dbg.dbg_info")
Scrap.AddScheduledFunc(Scrap.GetTime()+0.1, dgb_info, (), "dbg.dbg_info")
@ -197,12 +206,11 @@ def helplib():
logfile_name = None
print "Done!"
def enable_all_conv():
try:
import CharConversor
except ImportError:
print("CharConversor not available")
# print("CharConversor not available")
return
CharConversor.ConversionChars = list(CharConversor.ConversionChars)
E = Scrap.GetFirst()
@ -244,20 +252,6 @@ def nuke():
E = Scrap.GetEntity(E.NextInSlot)
def test_func():
E = Scrap.GetFirst()
me = Scrap.UsrEntity(0)
while E:
if E.Name == me.Name:
E = Scrap.GetEntity(E.NextInSlot)
try:
E.Money=1024*1024*1024
# SAI.SetStateVehicle(8,me.Name,E.Name)
except:
pass
E = Scrap.GetEntity(E.NextInSlot)
def become(name):
import CharConversor
enable_all_conv()
@ -299,7 +293,7 @@ def getall():
me = Scrap.UsrEntity(0)
while E:
try:
E.Pos = me.Pos
E.Descriptor = "HAXX!"
except:
pass
E = Scrap.GetEntity(E.NextInSlot)
@ -308,6 +302,11 @@ def getall():
def god(e=None):
if e == None:
e = Scrap.UsrEntity(0)
if e:
try:
e.IsType("Car")
except:
return
if e:
if e.IsType("Car"):
e.Ammo00 = SWeap.GetFAmmo(0, "Max")
@ -324,9 +323,18 @@ def god(e=None):
elif e.IsType("WalkChar"):
e.Energy = 1
e.Invulnerable = 1
e.TimeSpeed = 2.0
e.Mass = 100
# Scrap.SetAlarm(0.0)
Scrap.SetAlarmGrow(-0.5)
Scrap.DeleteScheduledFuncs("dbg.god")
Scrap.DeleteScheduledFuncs("dbg.god")
Scrap.AddScheduledFunc(Scrap.GetTime(), god, (e,), "dbg.god")
Scrap.AddScheduledFunc(Scrap.GetTime() + 0.01, god, (e,), "dbg.god")
def ungod():
for _ in range(1024):
Scrap.DeleteScheduledFuncs("dbg.god")
def ultranuke():
@ -337,11 +345,92 @@ def ultranuke():
(), "dbg.ultranuke")
def freeze(_=None):
QC.freeze()
Scrap.DeleteScheduledFuncs("dbg.freeze")
Scrap.DeleteScheduledFuncs("dbg.freeze")
Scrap.AddScheduledFunc(Scrap.GetTime()+0.1, freeze, (None,), "dbg.freeze")
def unfreeze(_):
Scrap.DeleteScheduledFuncs("dbg.freeze")
Scrap.DeleteScheduledFuncs("dbg.freeze")
QC.unfreeze()
def brake():
if me:
me.Vel = (0, 0, 0)
weaps_hacked = {
"Laser": {
"AmmoCost": 0,
"TimeDelay": 0,
},
"Vulcan": {
"TimeDelay": 0.01,
"TimeDelayUPG": 0.01,
"AmmoCost": 0
},
"Devastator": {
"AmmoCost": 0,
"RechargeTime": 0,
"SpreadAngle": 0,
},
"Tesla": {
"AmmoCost": 0,
},
"ATPC": {
"AmmoCost": 0,
"UpgradeDelay": 0,
"Delay": 0,
},
"Swarm": {
"AmmoCost1": 0,
"AmmoCost2": 0,
"AmmoCost3": 0,
"AmmoCost4": 0,
"Number1": 20,
"Number2": 20,
"Number3": 20,
"Number4": 20,
"TurnSpeed": 360000,
"TurnSpeedUPG": 360000,
"TimeDelay": 1.0,
},
"Inferno": {
"AmmoCost": 1
}
}
def weaphacks():
for weapon, properties in weaps_hacked.items():
for prop, value in properties.items():
Scrap.Set(weapon+prop, value)
def unweaphacks():
for weapon, properties in weaps_hacked.items():
for prop, value in properties.items():
Scrap.Set(weapon+prop, Scrap.Def(weapon+prop))
def test_func():
E = Scrap.GetFirst()
me = Scrap.UsrEntity(0)
while E:
if E.Name == me.Name:
E = Scrap.GetEntity(E.NextInSlot)
try:
E.Money = 1024*1024*1024
# SAI.SetStateVehicle(8,me.Name,E.Name)
except:
pass
E = Scrap.GetEntity(E.NextInSlot)
for _ in range(1024):
Scrap.DeleteScheduledFuncs("dbg.dbg_info")
Scrap.DeleteScheduledFuncs("dbg.god")
@ -354,18 +443,31 @@ for module in sys.builtin_module_names:
exec("import " + module)
sys.settrace(None)
me = Scrap.UsrEntity(0)
notrace()
helplib()
# settrace()
dgb_info()
enable_all_conv()
god()
Scrap.Set("debug", 3)
Scrap.Set("ShowConsoleLog", 1)
Scrap.Set("AlwaysFlushLog", 1)
Scrap.Set("PythonExecute", "import dbg")
exec("import QuickConsole;QuickConsole.debug=sys.modules['dbg']")
print "Debug Module loaded"
def init():
global me
global initialized
if initialized == 0:
from ScrapHack import Mem, asm
sys.modules[__name__].mem = Mem
sys.modules[__name__].asm = asm
me = Scrap.UsrEntity(0)
dgb_info()
enable_all_conv()
god()
Scrap.Set("debug", level)
Scrap.Set("ShowConsoleLog", 1)
Scrap.Set("AlwaysFlushLog", 1)
Scrap.Set("PythonExecute", "import dbg;dbg.init()")
Scrap.DeleteScheduledFuncs("dbg_init")
Scrap.DeleteScheduledFuncs("dbg_init")
Scrap.AddScheduledFunc(Scrap.GetTime()+1, init, (), "dbg_init")
initialized = 1
exec("import QuickConsole;QuickConsole.dbg=sys.modules['dbg']")
print "Debug Module loaded use /dbg.init to initialize"

View file

@ -1,15 +1,26 @@
notes: |
0x7faa4c: temp storage?
0x7d2094: some reference count
0x4039b0: fcn.handle_cli_opts?
0x668007: ?
comments:
0x6113f9: Check if Window exists
flags:
0x7fbfa0: P_HT_SaveVars
0x7fbe50: P_HT_Eng_Vars
0x8c8d60: P_Addr_master
0x8c8d50: P_Addr_client
0x7fa748: P_Socket
0x8045dc: P_Socket_Server
0x7FE944: P_World
0x792618: P_Eng3d_ver
0x853a24: P_gWorld
0x7FBE4C: P_Vars
0x79C698: Py_Mods
0x852914: P_D3D8_Dev
0x850258: P_D3D8_ZBuffer
0x850408: P_D3D8_BackBuffer
0x7FCC00: N_Paks_opened
0x7fcbec: Hash_Index_Size
0x7fcbf0: P_Hash_Index
@ -36,7 +47,27 @@ flags:
0x7fadd8: is_python
0x7fc084: pak_lock
0x7fbe7c: current_language
0x7d2094: py_refcnt_unk
0x7d2094: refcnt_Py_None
0x7fa830: P_Window
0x7fadd0: P_PyExecute
0x84d3ec: Py_Initialized
0x8c8f10: Py_Debug
0x84d3e8: Py_Verbose
0x84db38: Py_Optimize
0x84dd60: Py_interpr
0x7fae38: Debug_Level
0x7fae40: Console_Out_Buffer_132_23
0x7fbe20: Console_Curr_Line
0x84db30: Py_Dummy
0x8ca2c4: cmdline
0x8c6350: module_filename
0x8c6140: P_module_filename
0x853954: P_D3DApp
0x853091: N_Uniones
# 0x7fbe24:
# 0x7fa778:
# 0x8c8d78:
VMTs:
0x78d4d8: Py_entity
@ -56,133 +87,309 @@ VMTs:
0x7933ac: 3d_Gfx
0x7933a0: NodeFX
classes:
World:
types:
- "struct PyMethodDef { char *ml_name; void *ml_meth; int ml_flags; char *ml_doc;};"
- "struct PyMethodDef { char* ml_name; void* ml_meth; int ml_flags; char* ml_doc;};"
- "struct GameVar { struct GameVar* next; const char* name; const char* desc; uint64_t d_type; void* value; void* def_value; };"
- "struct HT_Entry { void* data; const char* key; struct HT_Entry* next;};"
- "struct PakEntry { unsigned char* filename; bool locked; void* data; uint32_t seek;};"
- "struct HashIndexEntry { uint32_t offset; uint32_t size; uint32_t status; const char* name; struct HashIndexEntry* next; };"
- "struct HashIndex { uint32_t size; struct HashIndexEntry** data; };"
- "struct HashTableEntry { void* data; const char *key; struct HashTableEntry* next; };"
- "struct HashTableEntry { void* data; const char* key; struct HashTableEntry* next; };"
- "struct HashTable { uint32_t size; struct HashTableEntry** data; };"
function_signatures:
0x5A8390: "int PyRun_SimpleString(const char* command);"
0x5BB9D0: "int PyArg_ParseTuple(void* PyObj, char* format, ...);"
0x413ee0: "int dbg_log(const char* fmt,...);"
0x4134C0: "int write_log(unsigned int color, const char* msg);"
0x47C1E0: "int ht_hash_ent_list(const char* str);"
0x404BB0: "int ht_hash_ent(const char* str);"
0x4016F0: "int reg_get_val(const char* value);"
0x414280: "int prepare_html_log(const char* filename);"
0x6597d0: "bool read_ini_entry(void* dest,const char* key, const char* section);"
0x5A8FB0: "void* Py_InitModule(const char* name,void* methods);"
0x5E3800: "int fopen_from_pak(const char* filename);"
0x419950: "int fopen_2(const char* filename);"
0x41AB50: "int open_pak(const char* filename, int unk_1,void* unk_ptr);"
0x404460: "int register_c_callback(const char* name,void* func);"
0x414070: "void throw_assertion_2(const char* check,const char* file,const char* date, unsigned int line);"
0x5FBC50: "void throw_assertion_1(const char* check,const char* file, unsigned int line);"
0x5BC140: "static char* convertsimple1(void *arg, char **p_format, void *p_va);"
0x5E3800: "int32_t fopen_from_pak(const char* filename,const char* mode);"
0x5a90f0: "void* Py_BuildValue(const char* format, ...);"
0x5B9E70: "void* PyObject_GetAttrString(void* obj, const char* attr);"
- "struct va_list { unsigned int gp_offset; unsigned int fp_offset; void *overflow_arg_area; void *reg_save_area; };"
functions:
0x6B1C70: strcmp
0x5BB9D0: PyArg_ParseTuple
0x5DD510: init_engine_3d
0x401180: create_window
0x401240: create_main_window
0x4016F0: reg_get_val
0x4134C0: write_log
0x414280: prepare_html_log
0x418220: get_version_info
0x4137E0: write_html_log
0x402190: handle_console_input
0x5F9520: handle_render_console_input
0x404A50: find_entity
0x47C1E0: ht_hash_ent_list
0x404BB0: ht_hash_ent
0x404460: register_c_callback
0x417470: load_game
0x5E3800: fopen_from_pak
0x5e3500: fopen
0x403370: init_debug
0x401770: init
0x4026D0: init_py
0x405B40: init_py_sub
0x5A8FB0: Py_InitModule
0x41AB50: open_pak
0x5A8390: PyRun_SimpleString
0x414570: setup_game_vars
0x5FBC50: throw_assertion_1
0x414070: throw_assertion_2
0x5F7000: read_ini
0x650F80: load_sm3
0x6665A0: load_m3d_1
0x666900: load_m3d_2
0x479B20: world_constructor
0x479B40: init_world
0x402510: deinit_world
0x479870: make_world
0x602A70: render_frame
0x6B738C: handle_exception
0x5B9E70: PyObject_GetAttrString
0x413ee0: dbg_log
0x5f75e0: init_d3d
0x63a2f0: gdi_draw_line
0x5e3250: read_stream
0x5e3bb0: read_stream_wrapper
0x50b9b0: init_scorer
0x582e10: init_action_class_list
0x528910: init_sound_sys
0x5268d0: try_init_sound_sys
0x404280: cPyFunction_set_func
0x414680: load_config
0x414810: save_config
0x4f42a0: close_server_socket
0x4f4d10: close_server
0x4f48e0: close_client
0x4f4fb0: is_server
0x4f4a10: is_client
0x4fac50: is_master
0x526910: close_sound_sys
0x526520: shutdown_sound_sys
0x5dd700: close_3d_engine
0x5a7320: close_window
0x5dff20: set_exception_handler
0x5a7f20: get_console_wnd
0x5a73a0: show_console
0x666c60: read_m3d
0x417df0: snprintf
0x5fc930: printf
0x6597d0: read_ini_entry
0x5fc0a0: engine_debug_log
0x5a7440: create_console_window
0x6114e0: setup_window
0x404420: clear_functions
0x405ca0: close_py_subsys
0x50bcb0: close_scorer
0x479b20: close_world
0x582e70: close_action_class
0x50b6a0: get_scorer
0x50ea20: scorer_parse_type
0x636580: list_models
0x5a90f0: Py_BuildValue
0x41c5a0: has_lst_file
0x5a8e90: py_error
0x5a9890: get_module_dict
0x5c7bb0: get_current_thread
0x5aa140: preload_lib
0x413c10: sprintf
0x405850: check_is_python
0x47bf90: setup_ent_list
0x474f80: ent_list_get_set
0x6283a0:
name: load_emi
0x4fa9f0:
name: send_pkt
0x5ca9e0:
signature: void* PyFrame_New(void* thread_state, void* code_object,void* globals, void* locals)
name: PyFrame_New
0x5bcae0:
signature: void PyErr_SetString(void* obj, const char* err_msg);
name: PyErr_SetString
0x5cb040:
signature: void* eval_code2(void* dict, const char* key, void* item);
name: eval_code2
0x5e3c50:
convention: cdecl-thiscall-ms
name: read_int
0x5e3b50:
convention: cdecl-thiscall-ms
name: read_block_header
0x5c66d0:
signature: void initerrors(void* dict);
name: initerrors
0x5bb370:
signature: int PyDict_SetItemString(void* dict, const char* key, void* item);
name: PyDict_SetItemString
0x5b9960:
signature: void* PyObject_NEW(void* type, void* typeobj);
name: PyObject_NEW
0x4145e0:
convention: cdecl-thiscall-ms
signature: bool get_config_var(char* name);
name: get_config_var
0x413470:
signature: void init_logging();
name: init_logging
0x5a8040:
signature: void Py_Initialize();
name: Py_Initialize
0x5bb4e0:
name: PyModule_GetDict
signature: void* PyModule_GetDict(void*);
0x5c6610:
name: _PyBuiltin_Init_1
signature: void* _PyBuiltin_Init_1();
0x5b5db0:
name: PyString_FromString
signature: void* PyString_FromString(const char*);
0x5ba3a0:
name: PyDict_New
signature: void* PyDict_New();
0x5c7bd0:
name: PyThreadState_Swap
signature: void* PyThreadState_Swap(void* new);
0x5c7870:
name: PyInterpreterState_New
signature: void* PyInterpreterState_New();
0x5c79b0:
name: PyThreadState_New
signature: void* PyThreadState_New(void* interp);
0x6ad1e9:
name: getenv
signature: char* getenv(char* var);
0x401180:
name: create_window
0x401240:
name: create_main_window
0x4016f0:
name: reg_get_val
signature: int reg_get_val(const char* value);
0x401770:
name: init
0x402190:
name: handle_console_input
signature: int handle_console_input(const char* input);
0x402510:
name: deinit_world
0x4026d0:
name: init_py
0x403370:
name: init_engine
0x4037e0:
name: init_debug
0x404280:
name: cPyFunction_set_func
0x404420:
name: clear_functions
0x404460:
name: register_c_callback
signature: int register_c_callback(const char* name,void* func);
0x404a50:
name: find_entity
0x404bb0:
name: ht_hash_ent
signature: int ht_hash_ent(const char* str);
0x405850:
name: check_is_python
0x405b40:
name: init_py_sub
0x405ca0:
name: close_py_subsys
0x4134c0:
signature: int write_log(unsigned int color, const char* msg);
name: write_log
0x4137e0:
signature: void write_html_log(const char* fmt,...);
name: write_html_log
0x413c10:
name: sprintf
0x413ee0:
name: dbg_log
signature: int dbg_log(const char* fmt,...);
0x414070:
name: throw_assertion_2
signature: void throw_assertion_2(const char* check,const char* file,const char* date, unsigned int line);
0x414280:
name: prepare_html_log
signature: int prepare_html_log(const char* filename);
0x414570:
name: setup_game_vars
0x414680:
name: load_config
0x414810:
name: save_config
0x417470:
name: load_game
0x417df0:
name: snprintf_1
0x417d80:
name: snprintf_2
0x418220:
name: get_version_info
0x419950:
name: fopen_2
signature: int fopen_2(const char* filename);
0x41ab50:
name: open_pak
signature: int open_pak(const char* filename, int unk_1,void* unk_ptr);
0x41c5a0:
name: has_lst_file
signature: int has_lst_file(void* unk_ptr);
0x474f80:
name: ent_list_get_set
signature: bool ent_list_get_set(const char* name);
0x479870:
name: make_world
0x479b20:
name: close_world
0x479b40:
name: init_world
0x47bf90:
convention: cdecl-thiscall-ms
name: create_ent_list
signature: bool create_ent_list(const char* name);
0x47c1e0:
name: ht_hash_ent_list
signature: int ht_hash_ent_list(const char* str);
0x4f42a0:
name: close_server_socket
0x4f48e0:
name: close_client
0x4f4a10:
name: is_client
0x4f4d10:
name: close_server
0x4f4fb0:
name: is_server
0x4fac50:
name: is_master
0x50b6a0:
name: get_scorer
0x50b9b0:
name: init_scorer
0x50bcb0:
name: close_scorer
0x50ea20:
name: scorer_parse_type
0x526520:
name: shutdown_sound_sys
0x5268d0:
name: try_init_sound_sys
0x526910:
name: close_sound_sys
0x528910:
name: init_sound_sys
0x582e10:
name: init_action_class_list
0x582e70:
name: close_action_class
0x5a7320:
name: close_window
0x5a73a0:
name: show_console
0x5a7440:
name: create_console_window
0x5a7f20:
name: get_console_wnd
0x5a8390:
name: PyRun_SimpleString
signature: int PyRun_SimpleString(const char* command);
0x5a8e90:
name: Py_FatalError
signature: void Py_FatalError(const char* msg);
0x5a8fb0:
name: Py_InitModule
signature: void* Py_InitModule(const char* name,void* methods);
0x5a90f0:
name: Py_BuildValue
signature: void* Py_BuildValue(const char* format, ...);
0x5a9890:
name: PyImport_GetModuleDict
signature: void* PyImport_GetModuleDict();
0x5aa140:
name: preload_lib
0x5b9e70:
name: PyObject_GetAttrString
signature: void* PyObject_GetAttrString(void* obj, const char* attr);
0x5bb9d0:
name: PyArg_ParseTuple
signature: int PyArg_ParseTuple(void* PyObj, char* format, ...);
0x5bc140:
name: convertsimple1
signature: static char* convertsimple1(void* arg, char** p_format, void* va_list);
0x5bc0f0:
name: convertsimple
signature: static char* convertsimple(void* arg, char** p_format, char* msgbuf);
0x5bbf60:
name: converttuple
signature: static char* converttuple(void* arg, char** p_format, void* va_list, int* levels, char* msgbuf, int toplevel);
0x5bbee0:
name: convertitem
signature: static char* convertitem(void* arg, char** p_format, void* va_list, int* levels, char* msgbuf);
0x5c7bb0:
name: PyThreadState_Get
signature: void* PyThreadState_Get();
0x5dd510:
name: init_engine_3d
0x5dd700:
name: close_3d_engine
0x5dff20:
name: set_exception_handler
0x5e3250:
name: read_stream
0x5e3500:
name: fopen
0x5e3800:
name: fopen_from_pak
signature: int fopen_from_pak(const char* filename,const char* mode);
0x5e3bb0:
name: read_stream_wrapper
0x5f7000:
name: read_ini
0x5f75e0:
name: init_d3d
0x5f9520:
name: handle_render_console_input
0x5fbc50:
name: throw_assertion_1
signature: void throw_assertion_1(const char* check,const char* file, unsigned int line);
0x5fc0a0:
name: engine_debug_log
0x5fc930:
name: printf
0x602a70:
name: render_frame
0x6114e0:
name: setup_window
0x636580:
name: list_models
0x63a2f0:
name: gdi_draw_line
0x650f80:
name: load_sm3
0x6597d0:
name: read_ini_entry
signature: bool read_ini_entry(void* dest,const char* key, const char* section);
0x6665a0:
name: load_m3d_1
0x666900:
name: load_m3d_2
0x666c60:
name: read_m3d
0x6b1c70:
name: strcmp
signature: bool strcmp(const char* s1,const char* s2);
0x6b738c:
name: handle_exception
script: |
e asm.cmt.right = true
e cmd.stack = true
e scr.utf8 = true
e asm.describe = false
e graph.cmtright = true

22
file_formats/ai_path.md Normal file
View file

@ -0,0 +1,22 @@
# Structure of Graph
```cpp
template<size_t n>
struct Node {
float pos[n],
}
template<size_t n>
struct Edge {
uint32_t num_edge_nodes,
Node<n> nodes[],
}
template<size_t n>
struct Graph {
uint32_t num_nodes,
Node<n> nodes[],
uint32_t num_edges,
Edge<n> edges[],
}
```

126
file_formats/chunked.md Normal file
View file

@ -0,0 +1,126 @@
# General Block format
```cpp
struct Block {
unsigned char block_id[4],
uint32_t size,
unsigned char data[size],
}
template<typename T>
struct Block {
unsigned char block_id[4],
uint32_t size,
T data,
}
```
# Block IDs
| File Extension | Description |
|----------------|--------------------------|
| .cm3 | Animation file |
| .sm3 | 3d model file |
| .dum | Dummy (map object) file |
| .pth | AI Path |
| .emi | Emission maps/Materials? |
| .amc | ??? |
| File ID | Chunk IDs |
|---------|--------------------------------------------------------------------------|
| AMC | AMC, CMSH, QUAD |
| CM3 | ANI, CM3, EVA, NAE, NAM, SCN |
| DUM | DUM, INI |
| EMI | EMI, LFVF, MAP, MAT, TRI |
| SM3 | ANI, CAM, INI, LFVF, LUZ, MAP, MAT, MD3D, NAE, NAM, PORT, SCN, SM3, SUEL |
| Chunk ID | Description |
|----------|-----------------------------|
| ANI | Animation data? |
| AMC | Collision Data |
| CMSH | Mesh data? |
| INI | INI-Configuration data |
| LUZ | Lighting information |
| MAT | Material information |
| QUAD | Mesh data? |
| SCN | Scene data? |
| CAM | Camera info? |
| PORT | Light portals? |
| SUEL | Ground plane? |
| DUM | Dummy (map object) data |
| MAP | UV Map? |
| LFVF | FVF Vertex Data |
| TRI | Triangle strip definitions? |
| NAM,NAE | Animation Data? |
# Format of Specific chunks
## INI
Configuration Data
```cpp
struct INI {
uint32_t num_sections,
struct {
uint32_t num_lines,
struct {
uint32_t num_chars,
char line[num_chars]
} lines[num_lines],
} sections[num_sections]
}
```
## LFVF
DirectX Flexible Vertex Format Data
```cpp
struct Vertex { // fields according to flags
float position[3],
float rhw,
float weights[3],
float normal[3],
float point_size,
uint32_t diffuse, //RGBA
uint32_t specular, //RGBA
float tex_coords[1 to 4][8] // ??? decided by flags?
}
struct LFVF {
uint32_t unk,
uint32_t num_entries,
struct {
uint32_t FVF, // FVF vertex data configuration
uint32_t vert_size //?,
uint32_t num_verts,
Vertex vertices[num_vers]
} entry[num_entries]
}
```
## DUM
Map object data
```cpp
struct DUM {
uint32_t unk_1,
uint32_t num_dummies,
uint32_t unk_2,
struct {
uint32_t name_length,
char name[name_length],
float position[3],
float rotation[3],
uint32_t has_ini,
if (has_ini) {
Block<INI> ini
},
uint32_t unk_1 // has_next?
} sections[num_sections]
}
```

15
file_formats/packed.md Normal file
View file

@ -0,0 +1,15 @@
# Format
```cpp
struct Packed {
unsigned char magic[4], // always BFPK
uint32_t version,
uint32_t number_of_files,
struct File {
uint32_t path_length,
char path[path_length], // latin1 encoding
uint32_t data_size,
uint32_t data_offset
} files[number_of_files],
char data[]
}
```

60
frida/frida_hook_net.js Normal file
View file

@ -0,0 +1,60 @@
var sendto = Module.getExportByName("WSOCK32.dll", "sendto")
var recvfrom = Module.getExportByName("WSOCK32.dll", "recvfrom")
Interceptor.attach(ptr("0x004f9300"), {
onEnter: function (args) {
console.log("[SendUsrString]", JSON.stringify({
data: args[0].readCString(),
dst: args[1].toInt32(),
chat: args[2].toInt32()
}));
}
})
Interceptor.attach(ptr(sendto), {
onEnter: function (args) {
this.socket = args[0];
this.buffer = args[1];
this.size = args[2].toInt32();
this.flags = args[3].toInt32();
this.sock_addr = args[4];
this.to_len = args[5].toInt32();
},
onLeave: function (ret) {
var port = this.sock_addr.add(2).readU16();
var addr = this.sock_addr.add(4).readU32();
var data = Memory.readByteArray(this.buffer, ret.toInt32())
send({
type: "SEND",
ptr: this.buffer.toInt32(),
addr,
port
}, data);
return ret;
}
})
Interceptor.attach(ptr(recvfrom), {
onEnter: function (args) {
this.socket = args[0];
this.buffer = args[1];
this.size = args[2].toInt32();
this.flags = args[3].toInt32();
this.sock_addr = args[4];
this.from_len = args[5].toInt32();
},
onLeave: function (ret) {
if (!ret.equals(ptr("0xffffffff"))) {
var port = this.sock_addr.add(2).readU16();
var addr = this.sock_addr.add(4).readU32();
var data = Memory.readByteArray(this.buffer, ret.toInt32())
send({
type: "RECV",
ptr: this.buffer.toInt32(),
addr,
port
}, data);
}
return ret;
}
})

View file

@ -0,0 +1,174 @@
var pak_files = {}
var ftypes = {}
var record=false;
var current_block_id;
var filename;
var t0 = performance.now();
Interceptor.attach(ptr("0x5e3b50"), { //read_block_header
onEnter: function (args) {
filename = pak_files[this.context.ecx] || this.context.ecx;
current_block_id = args[0].readUtf8String();
},
onLeave: function(ret) {
return ret;
}
})
// Interceptor.attach(ptr("0x5e3c50"), { // read_block_id
// onEnter: function (args) {
// var filename=pak_files[this.context.ecx]||this.context.ecx;
// var id=args[1].readUtf8String();
// console.log("[+read_block("+filename+")]",id,args[1]);
// },
// // onLeave: function(ret) {
// // console.log("[-read_ini_block] Ret:",ret);
// // }
// })
Interceptor.attach(ptr("0x7B43B020"),{
onEnter: function(args) {
var info={};
info['this']=args[0];
info['Length']=args[1];
info['Usage']=args[2];
info['FVF']=args[3];
info['Pool']=args[4];
info['ppVertexBuffer']=args[4];
send({CreateVertexBuffer:info});
}
})
Interceptor.attach(ptr("0x5e3bb0"), { // read_stream_wrapper
onEnter: function (args) {
this.args = {};
this.args[0] = args[0];
this.args[1] = args[1];
this.timestamp = performance.now()-t0;
},
onLeave: function (ret) {
var data=Memory.readByteArray(this.args[0],this.args[1].toInt32());
var stack = Thread.backtrace(this.context,Backtracer.ACCURATE);
var obj={
filename,
timestamp: this.timestamp,
block_id: current_block_id,
stack
};
send(obj,data);
}
})
Interceptor.attach(ptr("0x5e3800"), { // fopen_from_pak
onEnter: function (args) {
this.filename = args[0].readUtf8String();
},
onLeave: function (ret) {
if (ret != 0) {
pak_files[ret] = this.filename;
}
}
})
// Interceptor.attach(ptr("0x5e3c50"), { // read_block_id
// onEnter: function (args) {
// console.log("[+read]",args[0],args[1]);
// },
// onLeave: function(ret) {
// console.log("[-read] Ret:",ret);
// }
// })
// Interceptor.attach(ptr("0x6665a0"), { // load_m3d_1
// onEnter: function (args) {
// console.log("[M3D_1]",args[0].readUtf8String());
// }
// })
// Interceptor.attach(ptr("0x666900"), { // load_m3d_2
// onEnter: function (args) {
// console.log("[M3D_2]",args[0].readUtf8String());
// }
// })
function dasm(addr, size) {
var size = size || 8;
var offset = 0;
var ret = [];
while (ret.length != size) {
var inst = Instruction.parse(ptr(addr).add(offset));
ret.push(("[" + inst.address + "] " + inst.mnemonic + " " + inst.opStr).trim());
offset += inst.size;
}
return ret;
}
function r(addr, options) {
var options = options || {}
var max_depth = options.max_depth || 4;
var num = options.num || 4;
var ret = {};
var vals = [
"S8",
"U8",
"S16",
"U16",
"S32",
"U32",
"Float",
"Double",
"Pointer",
"CString",
"Utf8String",
"Utf16String",
"AnsiString"
];
vals.forEach(function (k) {
try {
ret[k] = ptr(addr)['read' + k]()
} catch (e) {
ret[k] = undefined;
}
})
try {
ret["code"] = dasm(addr, 8);
} catch (e) {
ret["code"] = undefined;
}
if (max_depth > 1) {
var p = {};
var read_ptr = false;
for (var i = 0; i < num; ++i) {
if (ret["Pointer"] === undefined) {
continue;
}
p[i * Process.pointerSize] = r(ret["Pointer"].add(i * Process.pointerSize), {
max_depth: max_depth - 1,
num
});
read_ptr = true;
}
if (read_ptr) {
ret["p"] = p;
}
}
return ret;
}
// function test() {
// for (var p = 0; p < 4; ++p) {
// var player = ptr(0x7FE944).readPointer().add(0x288 + p * 4).readPointer();
// if (!player.isNull()) {
// console.log("Player " + (p+1) + ":", player);
// console.log(JSON.stringify(r(player),null,4));
// }
// }
// }

56
frida/frida_inject_net.py Normal file
View file

@ -0,0 +1,56 @@
import frida
import psutil
from binascii import hexlify
import subprocess as SP
import string
import ipaddress
from dissect_net import packet,printable_chars,hexdump,is_printable
def on_message(msg, data=None):
if not data:
return
msg = msg["payload"]
IP = ipaddress.IPv4Address(msg["addr"])
IP = ipaddress.IPv4Address(IP.packed[::-1])
direction = msg["type"]
port = msg["port"]
ptr = msg["ptr"]
with open("netlog.txt","a",encoding="utf8") as of:
print(
"{} {}:{} 0x{:x} {}".format(msg["type"], IP, port, ptr, str(hexlify(data),"utf8")),
file=of
)
if is_printable(data):
print(direction, addr, buffer_addr, data)
return
try:
parsed_data = packet.parse(data)
print(
"{} {}:{} 0x{:x}".format(msg["type"], IP, port, ptr)
)
print(hexdump(data))
print(parsed_data)
print()
except Exception as e:
print(e)
pass
def main():
pid = frida.spawn(sys.argv[1:])
session = frida.attach(pid)
session.enable_jit()
script = session.create_script(open("frida_hook_net.js").read())
open(f"netlog.txt","w",encoding="utf8").close()
script.on("message", on_message)
script.load()
frida.resume(pid)
proc = psutil.Process(pid)
proc.wait()
session.detach()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,59 @@
from __future__ import print_function
import frida
import os
import sys
import psutil
import binascii
import sqlite3
import json
import time
import msgpack
from multiprocessing import JoinableQueue
import threading
q = JoinableQueue()
def db_worker(q):
with open("dump.mp", "wb") as of:
while True:
args = q.get()
if args is None:
q.task_done()
break
msgpack.dump(args, of)
q.task_done()
db_w = threading.Thread(target=db_worker, args=(q,))
db_w.start()
def on_message(msg, data):
filename = msg.get("payload", {}).get("filename", "<UNKNOWN>").replace("\\", "/")
block_id = msg.get("payload", {}).get("block_id", "<UNKNOWN>")
print(filename,block_id,data)
msg["payload"]["data"] = data
q.put(msg["payload"])
def main():
pid = frida.spawn(sys.argv[1:])
session = frida.attach(pid)
script = session.create_script(open("frida_hook_read_trace.js").read())
script.on("message", on_message)
script.load()
frida.resume(pid)
proc = psutil.Process(pid)
proc.wait()
session.detach()
q.put(None)
q.join()
q.close()
db_w.join()
if __name__ == "__main__":
main()

8
frida/frida_mem_mon.js Normal file
View file

@ -0,0 +1,8 @@
MemoryAccessMonitor.enable({
base: ptr("0x7fe944"),
size: 4
}, {
onAccess: function (details) {
console.log(details.operation, details.from, details.address)
},
})

22
frida/frida_mem_mon.py Normal file
View file

@ -0,0 +1,22 @@
import frida
import sys
import psutil
def on_message(msg, data=None):
print(msg,data)
def main():
pid = frida.spawn(sys.argv[1:])
session = frida.attach(pid)
session.enable_jit()
script = session.create_script(open("frida_mem_mon.js").read())
script.on("message", on_message)
script.load()
frida.resume(pid)
proc = psutil.Process(pid)
proc.wait()
session.detach()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,43 @@
var stalked_threads = [];
var excluded_modules = []
var sent=false;
setInterval(() => {
Process.enumerateModules().forEach(mod => {
if (mod.name == "Scrap.exe") {
if (!sent) {
send({
mod: mod
})
sent=true;
}
return;
}
if (excluded_modules.indexOf(mod.name) == -1) {
Stalker.exclude(mod);
excluded_modules.push(mod.name);
}
})
Process.enumerateThreads().forEach(thread => {
if (stalked_threads.indexOf(thread.id) != -1) {
return;
}
Stalker.follow(thread.id, {
events: {
call: true,
block: true,
compile: true,
ret: true,
exec: true
},
onReceive: function (events) {
send({
stalker: Stalker.parse(events, {
annotate: true,
stringify: true
})
});
}
})
stalked_threads.push(thread.id);
})
}, 0)

View file

@ -0,0 +1,67 @@
import frida
import sys
import psutil
import subprocess as SP
import threading
from multiprocessing import JoinableQueue
import msgpack
q = JoinableQueue()
def db_worker(q):
events = 0
with open("trace.mp", "wb") as of:
while True:
args = q.get()
if args is None:
q.task_done()
break
events += 1
msgpack.dump(args, of)
q.task_done()
print("Wrote", events, "events")
db_w = threading.Thread(target=db_worker, args=(q,))
db_w.start()
modules = {}
mem_range = None
def on_message(msg, data=None):
global mem_range
data = msg["payload"]
if "stalker" in data:
for val in data["stalker"]:
q.put(val)
def main():
pid = frida.spawn(sys.argv[1:])
session = frida.attach(pid)
session.enable_jit()
script = session.create_script(open("frida_stalker_test.js").read())
script.on("message", on_message)
script.load()
frida.resume(pid)
proc = psutil.Process(pid)
proc.wait()
session.detach()
q.put(None)
q.join()
q.close()
db_w.join()
"""
import msgpack as mp
from collections import Counter
data=list(mp.Unpacker(open("trace.mp","rb"), raw=False))
Counter(v[1] for v in data).most_common(10)
"""
if __name__ == "__main__":
main()

View file

@ -1,21 +0,0 @@
import sys
from construct import *
from pprint import pprint
ScrapSaveVar = Struct(
"name" / PascalString(Int32ul, encoding="utf-8"),
"data" / PascalString(Int32ul, encoding="utf-8"),
)
ScrapSave = "ScarpSaveGame" / Struct(
"title" / PascalString(Int32ul, encoding="utf-8"),
"id" / PascalString(Int32ul, encoding="utf-8"),
"data" / PrefixedArray(Int32ul, ScrapSaveVar),
Terminated,
)
with open(sys.argv[1], "rb") as sav_file:
save = ScrapSave.parse_stream(sav_file)
print("ID:", save.id)
print("Title:", save.title)
for var in save.data:
print(" {}: {}".format(var.name, var.data))

View file

@ -4,19 +4,19 @@ import json
from datetime import datetime
import subprocess as SP
from tqdm import tqdm
from pprint import pprint
import os
import sys
import yaml
tqdm_ascii = False
r2cmds = []
x64_dbg_script=[]
x64_dbg_script = []
script_path = os.path.dirname(os.path.abspath(__file__))
scrap_exe = os.path.abspath(sys.argv[1])
scrapland_folder = os.path.abspath(os.path.dirname(scrap_exe))
r2_script_path=os.path.join(scrapland_folder, "scrap_dissect.r2")
x64_dbg_script_path=os.path.join(scrapland_folder, "scrap_dissect.x32dbg.txt")
json_path=os.path.join(scrapland_folder, "scrap_dissect.json")
r2_script_path = os.path.join(scrapland_folder, "scrap_dissect.r2")
x64_dbg_script_path = os.path.join(scrapland_folder, "scrap_dissect.x32dbg.txt")
json_path = os.path.join(scrapland_folder, "scrap_dissect.json")
assert os.path.isfile(scrap_exe), "File not found!"
r2 = r2pipe.open(scrap_exe)
@ -24,19 +24,22 @@ file_hashes = r2.cmdj("itj")
target_hashes = {
"sha1": "d2dde960e8eca69d60c2e39a439088b75f0c89fa",
"md5": "a934c85dca5ab1c32f05c0977f62e186",
"sha256": "24ef449322f28f87b702834f1a1aac003f885db6d68757ff29fad3ddba6c7b88",
}
assert file_hashes == target_hashes, "Hash mismatch"
def x64_dbg_label(addr,name,prefix=None):
def x64_dbg_label(addr, name, prefix=None):
global x64_dbg_script
if isinstance(addr,int):
addr=hex(addr)
if isinstance(addr, int):
addr = hex(addr)
if prefix:
x64_dbg_script.append(f'lbl {addr},"{prefix}.{name}"')
else:
x64_dbg_script.append(f'lbl {addr},"{name}"')
def r2_cmd(cmd):
global r2, r2cmds
r2cmds.append(cmd)
@ -54,13 +57,15 @@ def r2_cmdJ(cmd):
r2cmds.append(cmd)
return r2.cmdJ(cmd)
t_start=datetime.today()
t_start = datetime.today()
def analysis(full=False):
print("[*] Running analysis")
steps=[]
steps = []
if full:
steps=[
steps = [
"e anal.dataref = true",
# "e anal.esil = true",
"e anal.jmp.after = true",
@ -72,52 +77,58 @@ def analysis(full=False):
"e anal.vinfun = true",
"e asm.anal = true",
]
steps+=["aaaaa"]
if full:
steps += ["aaaa"]
else:
steps += ["aaa"]
for ac in steps:
print(f"[*] Running '{ac}'")
r2_cmd(f"{ac} 2>NUL")
with open(os.path.join(script_path,"config.yml")) as cfg:
with open(os.path.join(script_path, "config.yml")) as cfg:
print("[*] Loading config")
config = type("Config",(object,),yaml.load(cfg,Loader=yaml.SafeLoader))
config = type("Config", (object,), yaml.load(cfg, Loader=yaml.SafeLoader))
for line in config.script.strip().splitlines():
r2_cmd(line)
analysis(False)
for addr,comment in config.comments.items():
for addr, comment in config.comments.items():
r2_cmd(f"CC {comment} @ {hex(addr)}")
for t in config.types:
r2_cmd(f'"td {t}"')
for addr, name in config.flags.items():
x64_dbg_label(addr,name,"loc")
x64_dbg_label(addr, name, "loc")
r2_cmd(f"f loc.{name} 4 {hex(addr)}")
for addr, name in config.functions.items():
x64_dbg_label(addr,name,"fcn")
r2_cmd(f"afr fcn.{name} {hex(addr)}")
for addr,sig in config.function_signatures.items():
r2_cmd(f'"afs {config.function_signatures[addr]}" @{hex(addr)}')
for addr, func in config.functions.items():
name, sig = func.get("name"), func.get("signature")
if name:
x64_dbg_label(addr, name, "fcn")
r2_cmd(f"afr fcn.{name} {hex(addr)}")
r2_cmd(f"afn fcn.{name} {hex(addr)}")
if sig:
sig = sig.replace(name, "fcn." + name)
r2_cmd(f'"afs {sig}" @{hex(addr)}')
def vtables():
ret = {}
print("[*] Analyzing VTables")
vtables = r2_cmdJ("avj")
for c in tqdm(vtables, ascii=True):
for c in tqdm(vtables, ascii=tqdm_ascii):
methods = []
name=config.VMTs.get(c.offset,f"{c.offset:08x}")
x64_dbg_label(c.offset,name,"vmt")
name = config.VMTs.get(c.offset, f"{c.offset:08x}")
x64_dbg_label(c.offset, name, "vmt")
r2_cmd(f"f vmt.{name} 4 {hex(c.offset)}")
for idx,m in enumerate(tqdm(c.methods, ascii=True, leave=False)):
for idx, m in enumerate(tqdm(c.methods, ascii=tqdm_ascii, leave=False)):
methods.append(hex(m.offset))
x64_dbg_label(m.offset,f"{name}.{idx}","fcn.vmt")
x64_dbg_label(m.offset, f"{name}.{idx}", "fcn.vmt")
r2_cmd(f"afr fcn.vmt.{name}.{idx} {hex(m.offset)} 2>NUL")
ret[hex(c.offset)] = methods
return ret
@ -127,14 +138,14 @@ def c_callbacks():
print("[*] Parsing C Callbacks")
funcs = {}
res = r2_cmd("/r fcn.register_c_callback ~CALL[1]").splitlines()
for addr in tqdm(res, ascii=True):
for addr in tqdm(res, ascii=tqdm_ascii):
r2_cmd(f"s {addr}")
r2_cmd(f"so -3")
func, name = r2_cmdJ(f"pdj 2")
func = func.refs[0].addr
name = r2_cmd(f"psz @{hex(name.refs[0].addr)}").strip()
r2_cmd(f"afr fcn.callbacks.{name} {hex(func)} 2>NUL")
x64_dbg_label(func,f"{name}","fcn.callbacks")
x64_dbg_label(func, f"{name}", "fcn.callbacks")
funcs[name] = hex(func)
return funcs
@ -142,22 +153,22 @@ def c_callbacks():
def assertions():
assertions = {}
for (n_args, a_addr) in [
(4, "fcn.throw_assertion_1"),
(3, "fcn.throw_assertion_2"),
(3, "fcn.throw_assertion_1"),
(4, "fcn.throw_assertion_2"),
]:
print(f"[*] Parsing C assertions for {a_addr}")
res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines()
print()
for line in tqdm(res, ascii=True):
for line in tqdm(res, ascii=tqdm_ascii):
addr = line.strip()
r2_cmd(f"s {addr}")
r2_cmd(f"so -{n_args}")
dis=r2_cmdJ(f"pij {n_args}")
dis = r2_cmdJ(f"pij {n_args}")
if n_args == 4:
file, msg, date, line = dis
line, date, file, msg = dis
elif n_args == 3:
date = None
file, msg, line = dis
line, file, msg = dis
try:
file = r2_cmd(f"psz @{file.refs[0].addr}").strip()
msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip()
@ -180,32 +191,35 @@ def bb_refs(addr):
ret = {}
res = r2_cmd(f"/r {addr} ~fcn[0,1]").splitlines()
print()
for ent in res:
for ent in tqdm(res, ascii=tqdm_ascii):
func, hit = ent.split()
ret[hit] = {"asm": [], "func": func}
for ins in r2_cmdJ(f"pdbj @{hit}"):
ret[hit]["asm"].append(ins.disasm)
return ret
def world():
print("[*] Parsing World offsets")
return bb_refs("loc.P_World")
def render():
print("[*] Parsing D3D_Device offsets")
return bb_refs("loc.P_D3D8_Dev")
def py_mods():
print("[*] Parsing Python modules")
res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines()
print()
py_mods = {}
for call_loc in tqdm(res, ascii=True):
for call_loc in tqdm(res, ascii=tqdm_ascii):
r2_cmd(f"s {call_loc}")
r2_cmd(f"so -3")
args = r2_cmdJ("pdj 3")
refs = []
if not all([arg.type == "push" for arg in args]):
if not all(arg.type == "push" for arg in args):
continue
for arg in args:
refs.append(hex(arg.val))
@ -214,7 +228,7 @@ def py_mods():
name = r2_cmd(f"psz @{name}").strip()
r2_cmd(f"s {methods}")
r2_cmd(f"f py.{name} 4 {methods}")
x64_dbg_label(methods,f"{name}","py")
x64_dbg_label(methods, f"{name}", "py")
py_mods[name] = {"methods_addr": methods, "doc": doc, "methods": {}}
while True:
m_name, m_func, _, m_doc = [v.value for v in r2_cmdJ(f"pfj xxxx")]
@ -223,14 +237,14 @@ def py_mods():
m_name, m_func, m_doc = map(hex, (m_name, m_func, m_doc))
m_name = r2_cmd(f"psz @{m_name}").strip()
r2_cmd(f"f py.{name}.{m_name}.__doc__ 4 {m_doc}")
if int(m_doc,16)!=0:
x64_dbg_label(m_doc,f"{name}.{m_name}.__doc__","py")
if int(m_doc, 16) != 0:
x64_dbg_label(m_doc, f"{name}.{m_name}.__doc__", "py")
m_doc = r2_cmd(f"psz @{m_doc}").strip()
else:
m_doc=None
m_doc = None
py_mods[name]["methods"][m_name] = {"addr": m_func, "doc": m_doc}
r2_cmd(f"afr py.{name}.{m_name} {m_func} 2>NUL")
x64_dbg_label(m_func,f"{name}.{m_name}","fcn.py")
x64_dbg_label(m_func, f"{name}.{m_name}", "fcn.py")
r2_cmd("s +16")
return py_mods
@ -240,7 +254,7 @@ def game_vars():
print("[*] Parsing Game variables")
res = r2_cmd("/r fcn.setup_game_vars ~CALL[1]").splitlines()
print()
for line in tqdm(res, ascii=True):
for line in tqdm(res, ascii=tqdm_ascii):
addr = line.strip()
r2_cmd(f"s {addr}")
args = r2_cmd("pdj -5") # seek and print disassembly
@ -259,27 +273,22 @@ def game_vars():
break
if len(args_a) != 4:
continue
if not all(["val" in v for v in args_a]):
if not all("val" in v for v in args_a):
continue
addr, name, _, desc = [v["val"] for v in args_a]
name = r2_cmd(f"psz @{hex(name)}").strip()
desc = r2_cmd(f"psz @{hex(desc)}").strip()
addr = hex(addr)
r2_cmd(f"f loc.gvar.{name} 4 {addr}")
x64_dbg_label(addr,f"{name}","loc.gvar")
x64_dbg_label(addr, f"{name}", "loc.gvar")
ret[addr] = {"name": name, "desc": desc}
return ret
ret = dict(
game_vars=game_vars(),
c_callbacks=c_callbacks(),
py_mods=py_mods(),
assertions=assertions(),
vtables=vtables(),
world=world(),
render=render(),
)
ret = {}
# world, render
for func in ["game_vars", "c_callbacks", "py_mods", "assertions", "vtables"]:
ret[func] = globals()[func]()
analysis(True)
@ -288,7 +297,7 @@ with open(json_path, "w") as of:
print("[+] Wrote scrap_dissect.json")
with open(x64_dbg_script_path,"w") as of:
with open(x64_dbg_script_path, "w") as of:
of.write("\n".join(x64_dbg_script))
print("[+] Wrote scrap_dissect.x32dbg.txt")
@ -296,30 +305,37 @@ print("[+] Wrote scrap_dissect.x32dbg.txt")
with open(r2_script_path, "w") as of:
wcmds = []
for cmd in r2cmds:
record=True
for start in ["p","/","s"]:
if cmd == "avj":
continue
record = True
for start in ["p", "/", "s"]:
if cmd.strip('"').startswith(start):
record=False
record = False
if record:
wcmds.append(cmd)
of.write("\n".join(wcmds))
print("[+] Wrote scrap_dissect.r2")
r2.quit()
def start_program(cmdl,**kwargs):
if os.name=='nt':
return SP.Popen(['cmd','/c','start']+cmdl,**kwargs)
def start_program(cmdl, **kwargs):
if os.name == "nt":
return SP.Popen(["cmd", "/c", "start"] + cmdl, **kwargs)
else:
return SP.Popen(cmdl,**kwargs)
return SP.Popen(cmdl, **kwargs)
print("[+] Analysis took:",datetime.today()-t_start)
print("[+] Analysis took:", datetime.today() - t_start)
print("[+] Executing Cutter")
try:
start_program(['cutter','-A','0','-i',r2_script_path,scrap_exe],cwd=scrapland_folder,shell=False)
start_program(
["cutter", "-A", "0", "-i", r2_script_path, scrap_exe],
cwd=scrapland_folder,
shell=False,
)
except FileNotFoundError:
print("[-] cutter not installed, falling back to r2")
start_program(['r2','-i',r2_script_path,scrap_exe],cwd=scrapland_folder,shell=False)
start_program(
["r2", "-i", r2_script_path, scrap_exe], cwd=scrapland_folder, shell=False
)

View file

@ -0,0 +1,68 @@
import msgpack as mp
import sys
import os
from tqdm import tqdm
import struct
import binascii
import string
import re
from binascii import hexlify
def gen():
with open(sys.argv[1], "rb") as fh:
size = os.stat(sys.argv[1]).st_size
progbar = tqdm(total=size, unit="bytes", unit_scale=True, unit_divisor=1024)
pos = 0
for entry in mp.Unpacker(fh, raw=True):
progbar.update(fh.tell() - pos)
pos = fh.tell()
for k in entry.copy():
k_s = str(k, "utf8")
if k_s not in ["data", "stack", "timestamp"]:
entry[k] = str(entry.pop(k), "utf8")
entry[k_s] = entry.pop(k)
entry["stack"] = "|".join(
["{:08X}".format(int(str(v, "utf8"), 16)) for v in entry["stack"][::-1]]
)
yield entry
def strdump(data):
printable_chars = set(bytes(string.printable, "ascii")) - set(b"\n\r\t\x0b\x0c")
return "".join(chr(c) if c in printable_chars else "." for c in data)
def tohex(data):
return str(hexlify(data), "utf8").upper()
# best=sorted(tqdm(gen(),ascii=True),key=lambda v:len(v['data']),reverse=True)
# def score(entry):
# return len(entry['data'])
# def analyze(entry):
# data=entry['data']
# entry['infos'] = {
# 'len':len(data),
# }
# for bo in "><":
# for t in "hHiIlLqQefd":
# fmt="{}{}".format(bo,t)
# if len(data)%struct.calcsize(fmt)==0:
# entry['infos'][fmt]=[v[0] for v in struct.iter_unpack(fmt,data)]
# return entry
filters=[re.compile(s) for s in sys.argv[2:]]
with open("all.log", "w") as of:
for entry in gen():
fm=[(f.match(entry['filename']) is not None) for f in filters]
if filters and not any(fm):
continue
entry["data_len"] = len(entry["data"])
entry["str"] = strdump(entry["data"])
entry["data"] = tohex(entry["data"])
print(
"{timestamp} {block_id} {filename} {data_len:08X} {data} {str}".format(**entry), file=of
)

57
tools/binvis.py Normal file
View file

@ -0,0 +1,57 @@
import struct
from collections import OrderedDict, ChainMap
class LittleEndian:
byteorder = "<"
class BigEndian:
byteorder = ">"
class NativeEndian:
byteorder = "@"
class Field:
def __init__(self, struct_type=None, size=None, byteorder=None):
self.struct = struct_type
self.size = size
self.byteorder = byteorder
self.data = None
self.parsed = False
def parse(self, data):
return
class ParserMeta(type):
def __new__(cls, name, bases, namespace, **kwargs):
if object in bases:
return type.__new__(cls, name, bases, dict(namespace))
fields = []
for item_name, item_value in namespace.items():
if isinstance(item_value, Field):
fields.append(item_name)
ret = super().__new__(cls, name, bases, namespace)
ret._fields = fields
return ret
@classmethod
def __prepare__(metacls, name, bases, **kwds):
return OrderedDict()
class Parser(metaclass=ParserMeta):
def __init__(self, data):
for field in self._fields:
print(field, getattr(self, field))
class ChunkedHeader(Parser, LittleEndian):
size = Field("I")
data = Field(size=size)
print(ChunkedHeader(b""))

149
tools/dissect_net.py Normal file
View file

@ -0,0 +1,149 @@
from construct import *
from binascii import unhexlify
from collections import defaultdict, Counter
import string
class CustomError(SymmetricAdapter):
def __init__(self, msg):
super(SymmetricAdapter, self).__init__(Pass)
self._message = msg
def _decode(self, obj, context, path):
# print("Error",path)
# print(str(context))
msg = self._message.format(ctx=context, obj=obj)
raise ValidationError(message=msg, path=this.path)
paket_type = Enum(
Int8ub,
GetGameInfo=0x7F01, # 0x7f3d ?
Connect=0x7F47,
GameInfo=0xBACE,
LevelInfo=0x8017,
Announce=0x4842,
Disconnect=0x0F02,
UpdatePlayerInfo=0xC49, # ???
# UpdatePlayerInfo=0x8a4c,
ChatIn=0x921E,
ChatOut=0x0A1E,
# Movement=0x802
)
paket_subtype = Enum(
Int8ub
)
packet_types = {
"Movement": Struct("data" / GreedyBytes),
"ChatIn": Struct(
"unk" / Int16ub,
"unk_2" / Int8ub,
"msg" / PascalString(Int8ub, "utf-8"),
"rest" / GreedyBytes,
),
"ChatOut": Struct(
"unk" / Int16ub,
"unk_2" / Int8ub,
"msg" / PascalString(Int8ub, "utf-8"),
"rest" / GreedyBytes,
),
"UpdatePlayerInfo": Struct(
"data" / GreedyBytes
# "name"/PascalString(Int32ub,"utf-8"),
# "ship"/PascalString(Int8ub,"utf-8"),
# "max_life"/Int8ub,
# "player_char"/PascalString(Int16ub,"utf-8"),
# "engines"/PascalString(Int8ub,"utf-8")[4],
# "weapons"/PascalString(Int8ub,"utf-8"),
# "team_id"/Int32ul
),
"Announce": "info" / CString("utf-8"),
"GetGameInfo": Const(b"\x00\x00\x07"),
"Disconnect": Const(b"\x00\x0c\x02"),
"GameInfo": Struct(
"version_minor" / Int8ul,
"version_major" / Int8ul,
"port" / Int16ul,
"max_players" / Int16ul,
"curr_players" / Int16ul,
"name" / FixedSized(0x20, CString("utf-8")),
"mode" / FixedSized(0x10, CString("utf-8")),
"map" / Bytes(2),
"rest" / GreedyBytes,
),
"Connect": Struct(
"name" / PascalString(Int32ub, "utf-8"),
"ship" / PascalString(Int8ub, "utf-8"),
"max_life" / Int8ub,
"player_char" / PascalString(Int16ub, "utf-8"),
"engines" / PascalString(Int8ub, "utf-8")[4],
"weapons" / PascalString(Int8ub, "utf-8"),
"team_id" / Int32ul,
),
"LevelInfo": Struct(
"path" / PascalString(Int32ub, "utf-8"),
"mode" / PascalString(Int8ub, "utf-8"),
"rest" / GreedyBytes,
),
}
default = "Unknown ID" / Struct("data" / GreedyBytes)
# CustomError("Invalid ID: 0x{ctx.type:02x}")
packet = Struct(
"type" / Int8ub,
"subtype"/ Int8ub
# "data" / Switch(this.type, packet_types, default=default)
)
printable_chars = set(bytes(string.printable, "ascii")) - set(b"\n\r\t\x0b\x0c")
def is_printable(s):
return all(c in printable_chars for c in s.rstrip(b"\0"))
def hexdump(data, cols=16, offset=0):
lines = []
while data:
hexdata = " ".join("{:02X}".format(v) for v in data[:cols]).ljust(
3 * cols - 1, " "
)
print_data = "".join(
[chr(v) if v in printable_chars else "." for v in data[:cols]]
)
lines.append("{:04X} {} {}".format(offset, hexdata, print_data))
offset += len(data[:cols])
data = data[cols:]
return "\n".join(lines).strip()
def main():
data_type = Counter()
with open("netlog.txt", "r") as netlog:
for line in netlog:
direction, addr, buffer_addr, data = line.strip().split()
data = unhexlify(data)
print(direction, addr, buffer_addr)
print(hexdump(data))
print()
try:
parsed_data = packet.parse(data)
data_type["{0} {1:08b}:{2:08b} ({1:02X}:{2:02X})".format(direction, parsed_data.type,parsed_data.subtype)] += len(data)
except Exception:
pass
bar_width = 50
label = "Data type (main:sub)"
print("=" * 10, label, "=" * 10)
max_v = max(data_type.values())
total = sum(data_type.values())
for k, v in sorted(data_type.items(), key=lambda v: v[1], reverse=True):
bar = ("#" * round((v / max_v) * bar_width)).ljust(bar_width, " ")
print(k, bar, "({}, {:.02%})".format(v, v / total))
if __name__ == "__main__":
main()

46
tools/packed.ksy Normal file
View file

@ -0,0 +1,46 @@
meta:
id: packed
application: Scrapland
file-extension: packed
endian: le
xref: http://wiki.xentax.com/index.php/Scrapland_PACKED
license: MIT
encoding: latin1
seq:
- id: magic
contents: BFPK
doc: File Magic
- id: version
type: u2
size: 4
doc: Second File Magic
- id: num_files
type: u4
doc: Number of files
- id: files
type: file_entry
repeat: expr
repeat-expr: num_files
doc: Directory entry for each file
types:
file_entry:
seq:
- id: path_len
type: u4
doc: Length of file path
- id: path
type: str
size: path_len
doc: File path
- id: size
type: u4
doc: File size
- id: offset
type: u4
doc: Absoulte File offset
instances:
data:
pos: offset
size: size

122
tools/parse_LFVF.py Normal file
View file

@ -0,0 +1,122 @@
import os
import json
from construct import *
blocksize = 1024 * 4
def search(pattern, path):
seen = set()
with open(path, "rb") as infile:
buffer = bytearray(infile.read(blocksize))
while infile.peek(1):
for block in iter(lambda: infile.read(blocksize), b""):
buffer += block
buffer = buffer[-(blocksize * 2) :]
idx = buffer.find(pattern)
if idx != -1:
pos = (infile.tell() - blocksize * 2) + idx
if pos not in seen:
seen.add(pos)
return sorted(seen)
has_pos = [
"D3DFVF_XYZ",
"D3DFVF_XYZRHW",
]
num_blend = {
'D3DFVF_XYZB1': 1,
'D3DFVF_XYZB2': 2,
'D3DFVF_XYZB3': 3,
'D3DFVF_XYZB4': 4,
}
Vertex = Struct(
"pos" / If(lambda ctx: ctx._._.fvf.position in has_pos, Float32l[3]),
"rhw" / If(lambda ctx: ctx._._.fvf.position == "D3DFVF_XYZRHW", Float32l),
"w_blend" / If(lambda ctx: num_blend.get(ctx._._.fvf.position,0)!=0, Int32ul),
"normal" / If(lambda ctx: ctx._._.fvf.flags.D3DFVF_NORMAL, Float32l[3]),
"diffuse" / If(lambda ctx: ctx._._.fvf.flags.D3DFVF_DIFFUSE, Int8ul[4]),
"specular" / If(lambda ctx: ctx._._.fvf.flags.D3DFVF_SPECULAR, Int8ul[4]),
"tex" / Float32l[this.num_tex_coords][this._._.fvf.num_tex],
)
D3DFVF_POSITION_MASK = 0xE
D3DFVF_TEXCOUNT_MASK = 0xF00
D3DFVF_TEXCOUNT_SHIFT = 8
FVF = "fvf" / Union(
0,
"value" / Int32ul,
"num_tex"
/ Computed(
lambda ctx: 1 + ((ctx.value & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_MASK)
),
"position"
/ Enum(
Computed(lambda ctx: (ctx.value & D3DFVF_POSITION_MASK)),
D3DFVF_XYZ=0x2,
D3DFVF_XYZRHW=0x4,
D3DFVF_XYZB1=0x6,
D3DFVF_XYZB2=0x8,
D3DFVF_XYZB3=0xA,
D3DFVF_XYZB4=0xC,
),
"flags"
/ FlagsEnum(
Int32ul,
D3DFVF_RESERVED0=0x1,
D3DFVF_NORMAL=0x10,
D3DFVF_PSIZE=0x20,
D3DFVF_DIFFUSE=0x40,
D3DFVF_SPECULAR=0x80,
),
)
LFVF_Data = Struct(
"unk" / Int32ul,
"num_entries"/Int32ul,
"data"/Struct(
FVF,
"unk_size" / Int32ul,
"vertices" / PrefixedArray(Int32ul, Vertex),
)
# Terminated,
)
LFVF = Struct(
Const(b"LFVF"), "size" / Int32ul, "data" / RestreamData(Bytes(this.size), LFVF_Data)
)
files = [
r"D:\Games\Deep Silver\Scrapland\extracted\Data.packed\models\skies\orbit\sky.sm3",
r"D:\Games\Deep Silver\Scrapland\extracted\Data.packed\models\chars\boss\boss.sm3",
r"D:\Games\Deep Silver\Scrapland\extracted\Data.packed\models\chars\dtritus\dtritus.sm3",
r"D:\Games\Deep Silver\Scrapland\extracted\Data.packed\levels\gdb\map\map3d.emi"
]
vert_pos = {}
for path in files:
name = os.path.split(path)[-1]
fh = open(path, "rb")
offsets = search(b"LFVF", path)
for offset in sorted(offsets):
fh.seek(offset)
print("Offset:", offset)
s = LFVF.parse_stream(fh)
print(s)
print("=" * 10)
continue
# # print(s)
# print(path, fh.tell(), list(s.unk_ints), list(s.data.unk), fh.read(8))
# s = s.data
# vpos = [
# tuple(p for p in v.pos) for v in s.vertices
# ] # leave vertices alone because we don't need to reproject shit :|
# vert_pos["{}@{}".format(name, hex(offset))] = vpos
# with open("LFVF_Data.json", "w") as of:
# json.dump(vert_pos, of)
# break

117
tools/parse_chunked.py Normal file
View file

@ -0,0 +1,117 @@
from construct import *
import binascii
import os
Chunked = LazyBound(lambda: struct)
class CustomError(SymmetricAdapter):
def __init__(self, msg):
super(SymmetricAdapter, self).__init__(Pass)
self._message = msg
def _decode(self, obj, context, path):
# print("Error",path)
# print(str(context))
msg = "Invalid ID: " + repr(context.id)
raise ValidationError(message=msg, path=this.path)
RGB = NamedTuple("RGB", "R G B", Int8ul[3])
RGBA = NamedTuple("RGBA", "R G B A", Int8ul[4])
def make_chain(*sizes):
"utility function to make sequence of byte arrays"
return Sequence(*[Bytes(s) for s in sizes])
child_nodes = "children" / Struct("num" / Int32ul, "nodes" / Chunked[this.num])
subchunks = {
b"SM3\0": Struct(
"unk" / Bytes(4),
"timestamp" / Timestamp(Int32ul, 1, 1970),
child_nodes,
"scene" / Chunked,
),
b"SCN\0": Struct(
"version" / Int32ul,
"m3d_name" / PascalString(Int32ul, "utf8"),
"name" / PascalString(Int32ul, "utf8"),
child_nodes,
),
b"INI\0": Struct(
"data"
/ PrefixedArray(Int32ul, PrefixedArray(Int32ul, PascalString(Int32ul, "utf8"))),
"colors?" / Sequence(Int8ul, Int8ul, Int8ul, Int8ul, Float32l)[2],
"unk_data" / Bytes(0x18),
"unk_float" / Float32l,
"unk_int" / Int32ul,
child_nodes,
),
b"EMI\0": Struct(
"version"/Int32ul,
"num_materials"/Int32ul,
"num_unk"/Int32ul,
"materials"/Chunked
),
b"MAT\0": Struct(
"tris"/Int32ul,
"name"/PascalString(Int32ul,"utf8"),
"idx"/Bytes(this.tris*4*4)
),
None: Bytes(lambda ctx:ctx.size),
}
struct = Struct(
"id" / Bytes(4),
"size" / Int32ul,
"data" / Switch(this.id, subchunks, default=subchunks[None]),
)
def io_peek(fh, n):
p = fh.tell()
ret = fh.read(n)
fh.seek(p)
return ret
basedir = r"D:/Games/Deep Silver/Scrapland/extracted/Data.packed"
files = [
r"Models/Elements/AnilloEstructuraA/AnilloEstructuraA.SM3",
r"models/elements/antenaa/antenaa.lod1.sm3",
r"models/elements/abshield/anm/loop.cm3",
r"levels/fake/map/map3d.amc",
r"levels/shipedit/map/map3d.dum",
r"levels/menu/map/map3d.emi",
r"Models/Skies/Menu/Sky.SM3",
r"Levels/Menu/Map/Map3D.SM3",
r"Models/Elements/AnilloEstructuraD/AnilloEstructuraD.LOD1.SM3",
r"levels/menu/map/map3d.amc",
r"levels/menu/map/map3d.dum",
r"levels/menu/map/scenecamera/anm/loop.cm3",
r"models/chars/boss/boss.sm3",
r"models/chars/boss/anm/boss_walk.cm3",
]
for file in files:
file = os.path.join(basedir, file).replace("/","\\")
print()
print("#" * 3, file)
with open(file, "rb") as infile:
try:
data = struct.parse_stream(infile)
# assert infile.read()==b"","leftover data"
except Exception as ex:
print("Error:", ex)
data = None
if data:
print(data)
print("OFFSET:", hex(infile.tell()))
print("NEXT:", io_peek(infile, 16))
print("NEXT:", binascii.hexlify(io_peek(infile, 16)))

255
tools/parse_chunked_new.py Normal file
View file

@ -0,0 +1,255 @@
import os
import sys
import struct
import string
from pprint import pprint
from io import BytesIO
from contextlib import contextmanager
from datetime import timedelta, datetime
import glob
printable_chars = set(bytes(string.printable, "ascii")) - set(b"\n\r\t\x0b\x0c")
def hexdump(data, cols=16, offset=0, markers=None):
if markers is None:
markers = []
lines = []
while True:
hexdata = " ".join("{:02X}".format(v) for v in data[:cols]).ljust(
3 * cols - 1, " "
)
print_data = "".join(
[chr(v) if v in printable_chars else "." for v in data[:cols]]
)
lines.append("{:04X} {} {}".format(offset, hexdata, print_data))
offset += len(data[:cols])
data = data[cols:]
if not data:
break
return "\n".join(lines).strip()
@contextmanager
def seek_to(fh, offset, pos=None):
if pos is None:
pos = fh.tell()
fh.seek(offset)
yield
fh.seek(pos)
def read_array(s,fh):
ret=[]
count = read_struct("<I", fh)[0]
size = struct.calcsize(s)
for _ in range(count):
ret.append(read_struct(s,fh))
return ret
def read_struct(s, fh):
size = struct.calcsize(s)
return struct.unpack(s, fh.read(size))
def read_str(fh):
size = read_struct("<I", fh)[0]
return fh.read(size)
def read_block(fh):
try:
pos = fh.tell()
magic = str(fh.read(4).rstrip(b"\x00"), "utf8")
size = read_struct("<I", fh)[0]
data = fh.read(size)
return magic, data
except struct.error:
fh.seek(pos)
return
vals = set()
# ================================
class Parser:
depth = 0
dump_size = 0x100
def __init__(self, debug=False):
self.debug = debug
def _default(self, magic, fh):
print("=====", magic, "=====")
if self.debug:
print(hexdump(fh.read(self.dump_size)))
rest = len(fh.read())
if rest:
print("<{} more bytes>".format(rest))
fh.seek(0)
return "<Unparsed {} ({} bytes)>".format(magic, len(fh.read()))
def parse(self, magic, data, depth=0):
print("{}[{}] {} bytes".format(" " * self.depth, magic, len(data)))
self.depth += 1
fh = BytesIO(data)
ret = getattr(self, magic, lambda fh: self._default(magic, fh))(fh)
pos = fh.tell()
leftover = len(fh.read())
fh.seek(pos)
self.depth -= 1
if leftover:
print("{}[{}] {} bytes unparsed".format(" " * self.depth, magic, leftover))
if self.debug:
print(hexdump(fh.read(self.dump_size)))
rest = len(fh.read())
if rest:
print("<{} more bytes>".format(rest))
print("-" * 50)
return ret
def parse_block(self, fh):
block = read_block(fh)
if block:
return self.parse(*block)
# Block definitions
def SM3(self, fh):
ret = {}
ret["unk_1"] = fh.read(4) # always F8156500
ret["timestamp_2"] = datetime.fromtimestamp(read_struct("<I", fh)[0])
ret["unk_2"] = fh.read(4) # always 00000000
ret["scene"] = self.parse_block(fh)
assert fh.read() == b"", "Leftover Data"
return ret
def SCN(self, fh):
ret = {}
ret["unk_1"] = read_struct("<I", fh)[0]
ret["model_name"] = read_str(fh)
ret["node_name"] = read_str(fh)
if read_struct("<I", fh)[0]:
ret["ini_1"] = self.parse_block(fh)
ret["unk_c_1"] = read_struct("<BBBB", fh)
ret["unk_f_1"] = read_struct("<f", fh)[0]
ret["unk_c_2"] = read_struct("<BBBB", fh)
ret["unk_f_l"] = read_struct("<ffffffff", fh)
if read_struct("<I", fh)[0]:
ret["ini_2"] = self.parse_block(fh)
ret["num_mat"] = read_struct("<I", fh)[0]
ret["mat"] = []
for _ in range(ret["num_mat"]):
ret["mat"].append(self.parse_block(fh))
# ret["children"] = []
# for _ in range(read_struct("<I", fh)[0]):
# ret["children"].append(self.parse_block(fh))
# ret["unk_2"] = []
# for _ in range(4):
# ret["unk_2"].append(read_struct("<fff", fh))
# ret["materials"] = []
# for _ in range(read_struct("<I", fh)[0]):
# ret["materials"].append(self.parse_block(fh))
return ret
def INI(self, fh):
num_sections = read_struct("<I", fh)[0]
sections = []
for _ in range(num_sections):
num_lines = read_struct("<I", fh)[0]
lines = []
for _ in range(num_lines):
lines.append(str(read_str(fh).rstrip(b"\0"), "latin1"))
sections.append("\n".join(lines))
lines.clear()
assert fh.read() == b"", "Leftover Data"
return sections
def MAT(self, fh):
# ret = {}
# ret["unk_1"] = read_struct("<I", fh)[0]
# ret["name"] = read_str(fh)
# ret["colors?"] = ["{:08X}".format(v) for v in read_struct(">7I", fh)]
# ret["maps"]=[]
# for _ in range(ret["num_maps"]):
# ret["maps"].append(self.parse_block(fh))
return {"maps": fh.read().count(b"MAP\0")}
def MAP(self, fh):
ret = {}
ret["unk_1"] = read_struct("<I", fh)[0]
ret["name"] = read_str(fh)
ret["unk_2"] = read_struct("<IIII", fh)
ret["unk_3"] = read_struct("<fff", fh)
ret["unk_4"] = read_struct("<II", fh)
ret["rest"] = fh.read()
return ret
# def CM3(self, fh):
# return len(fh.read())
def DUM(self, fh):
ret = {}
ret["unk_1"] = read_struct("<I", fh)
ret["num_dummies"] = read_struct("<I", fh)[0]
ret["unk_2"] = read_struct("<I", fh)
ret["dummies"] = []
for _ in range(ret["num_dummies"]):
dum = {}
dum["name"] = read_str(fh)
dum["pos"] = read_struct("<fff", fh)
dum["rot"] = read_struct("<fff", fh)
dum["has_ini"] = read_struct("<I", fh)[0]
if dum["has_ini"]:
dum['ini']=self.parse_block(fh)
dum["has_next"] = read_struct("<I", fh)[0]
ret["dummies"].append(dum)
assert fh.read() == b"", "Leftover Data"
return ret
# def AMC(self, fh):
# return len(fh.read())
# def EMI(self, fh):
# return len(fh.read())
# ================================
basedir = r"D:/Games/Deep Silver/Scrapland/extracted/Data.packed"
files = [
r"Models/Chars/Dtritus/Dtritus.sm3",
r"Models/Elements/AnilloEstructuraA/AnilloEstructuraA.SM3",
r"models/elements/antenaa/antenaa.lod1.sm3",
# r"models/elements/abshield/anm/loop.cm3",
# r"levels/fake/map/map3d.amc",
# r"levels/shipedit/map/map3d.dum",
# r"levels/menu/map/map3d.emi",
r"Models/Skies/Menu/Sky.SM3",
r"Levels/Menu/Map/Map3D.SM3",
r"Models/Elements/AnilloEstructuraD/AnilloEstructuraD.LOD1.SM3",
# r"levels/menu/map/map3d.amc",
# r"levels/menu/map/map3d.dum",
# r"levels/menu/map/scenecamera/anm/loop.cm3",
r"models/chars/boss/boss.sm3",
# r"models/chars/boss/anm/boss_walk.cm3",
]
filt = [s.lower() for s in sys.argv[1:]]
for root, folders, files in os.walk(basedir):
for file in files:
path = os.path.join(root, file).replace("\\","/")
if not path.lower().endswith(".dum".lower()):
continue
print("Parsing", path)
p = Parser(debug=True)
with open(path, "rb") as fh:
while True:
parsed = p.parse_block(fh)
if not parsed:
break
pprint(parsed, compact=False, indent=4)
print("#" * 50)

31
tools/rbingrep.py Normal file
View file

@ -0,0 +1,31 @@
import binascii
import os
import sys
exe_file = os.path.abspath(sys.argv[1])
def search(pattern, path):
seen = set()
with open(path, "rb") as infile:
buffer = bytearray(infile.read(blocksize))
while infile.peek(1):
for block in iter(lambda: infile.read(blocksize), b""):
buffer += block
buffer = buffer[-(blocksize * 2) :]
idx = buffer.find(pattern)
if idx != -1:
pos = (infile.tell() - blocksize * 2) + idx
if pos not in seen:
seen.add(pos)
return sorted(seen)
markers = [ "AMC", "ANI", "CAM", "CM3", "CMSH", "DUM", "EMI", "EVA", "INI", "LFVF", "LUZ", "MAP", "MAT", "MD3D", "NAE", "NAM", "PORT", "QUAD", "SCN", "SM3", "SUEL", "TRI", ]
blocksize = 1024 * 4
for marker in markers:
pattern = bytes(marker, "utf8").ljust(4, b"\0")
res = search(pattern, exe_file)
print("?e "+marker)
for addr in res:
print("/r `?P {}`".format(hex(addr)))

50
tools/render_ai_path.py Normal file
View file

@ -0,0 +1,50 @@
from construct import *
AI_PATH = "Path" / Struct(
"num_nodes" / Int32ul,
"nodes" / Float32l[3][this.num_nodes],
"edges" / PrefixedArray(Int32ul, Float32l[3])[this.num_nodes],
)
data = AI_PATH.parse_file(sys.argv[1])
nodes = [tuple(node) for node in data.nodes]
edges = [[nodes.index(tuple(p)) for p in edge] for edge in data.edges]
# Run in Blender:
"""
import bpy
import numpy as np
import itertools as ITT
nodes = <paste_nodes>
edges_=<paste_edges>
# pasted node and edges here
edges=[]
for edge in edges_:
for a,b in zip(edge,edge[1:]):
edges.append(a)
edges.append(b)
nodes=[[p*0.0001 for p in node] for node in nodes]
me = bpy.data.meshes.new("Test")
nodes = np.array(list(ITT.chain.from_iterable(nodes)))
me.vertices.add(len(nodes)//3)
me.vertices.foreach_set("co", nodes)
me.edges.add(len(edges)//2)
me.edges.foreach_set("vertices", np.array(edges))
me.update(calc_edges=True)
me.validate()
ob = bpy.data.objects.new("Test", me)
scene = bpy.context.scene
scene.collection.objects.link(ob)
"""

27
tools/save_to_json.py Normal file
View file

@ -0,0 +1,27 @@
import sys
import os
from construct import *
import json
save_data = {}
ScrapSaveVar = Struct(
"name" / PascalString(Int32ul, encoding="windows-1252"),
"data" / PascalString(Int32ul, encoding="windows-1252"),
)
ScrapSave = "ScarpSaveGame" / Struct(
"title" / PascalString(Int32ul, encoding="windows-1252"),
"id" / PascalString(Int32ul, encoding="windows-1252"),
"data" / PrefixedArray(Int32ul, ScrapSaveVar),
Terminated,
)
with open(sys.argv[1], "rb") as sav_file:
save = ScrapSave.parse_stream(sav_file)
save_data["id"] = save.id
save_data["title"] = save.title
save_data["data"] = {}
for var in save.data:
save_data["data"][var.name] = var.data
with open(os.path.basename(sys.argv[1]) + ".json", "w") as of:
json.dump(save_data, of, indent=4)

View file

@ -3,18 +3,29 @@ from collections import OrderedDict
import glob
import os
import shutil
from construct import *
from construct import (
Struct,
PascalString,
Int32ul,
Lazy,
Pointer,
Bytes,
this,
PrefixedArray,
Const,
Debugger
)
from tqdm import tqdm
setglobalstringencoding(None)
ScrapFile = Struct(
"path" / PascalString(Int32ul),
"path" / PascalString(Int32ul, encoding="ascii"),
"size" / Int32ul,
"offset" / Int32ul,
"data" / OnDemandPointer(this.offset, Bytes(this.size)),
"data" / Lazy(Pointer(this.offset, Bytes(this.size))),
)
DummyFile = Struct(
"path" / PascalString(Int32ul, encoding="u8"), "size" / Int32ul, "offset" / Int32ul
)
DummyFile = Struct("path" / PascalString(Int32ul), "size" / Int32ul, "offset" / Int32ul)
PackedHeader = Struct(
Const(b"BFPK"), Const(b"\0\0\0\0"), "files" / PrefixedArray(Int32ul, ScrapFile)

45
tools/server.py Normal file
View file

@ -0,0 +1,45 @@
import socket
import binascii
import select
from construct import *
from socketserver import BaseRequestHandler,UDPServer
INFO = Struct(
"version_minor" / Int8ul,
"version_major" / Int8ul,
"port" / Int16ul,
"max_players" / Int16ul,
"curr_players" / Int16ul,
"name" / FixedSized(0x20, CString("utf-8")),
"mode" / FixedSized(0x10, CString("utf-8")),
"map" / Bytes(2),
"rest" / GreedyBytes,
)
class ScrapHandler(BaseRequestHandler):
def handle(self):
data, socket = self.request
print(self.client_address,data)
socket.sendto(data, self.client_address)
class ScrapSrv(UDPServer):
def __init__(self,port=5000):
super().__init__(("0.0.0.0",port),ScrapHandler)
with ScrapSrv() as srv:
srv.serve_forever()
exit()
# sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
while True:
rl, wl, xl = select.select([sock], [sock], [sock], 0.1)
if rl:
print(rl)
for sock in rl:
data, src = sock.recvfrom(1024)
print(src, data)
if data == b"\x7f\x01\x00\x00\x07":
game_info = INFO.build()
sock.sendto(game_info, src)

4
tools/test.bv Normal file
View file

@ -0,0 +1,4 @@
<
magic s 4
size I
data n $size