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/build/*
ScrapHacks/src/D3D8_VMT.hpp ScrapHacks/src/D3D8_VMT.hpp
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json
tools/*.log
frida/*.mp

108
NOTES.md
View file

@ -1,6 +1,6 @@
# Infos # Infos
- Engine: ScrapEngine - Engine: ScrapEngine/Mercury Engine
- Ingame Scripting Language: Python 1.5.2 - Ingame Scripting Language: Python 1.5.2
- Interesting memory locations and functions are noted in `config.yml` - Interesting memory locations and functions are noted in `config.yml`
@ -8,6 +8,8 @@
- `-console`: open external console window on start - `-console`: open external console window on start
- `-wideWindow`: start game in widescreen mode - `-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: # Functions identified:
@ -23,11 +25,11 @@
## External Console (Scenegraph Debugging?) (Handler @ `0x5f9520`): ## External Console (Scenegraph Debugging?) (Handler @ `0x5f9520`):
* `listar luces` * `listar luces` List lights in scene
* `listar` * `listar` list models in scene
* `arbol` (Patch Scrap.exe@offset 0x314bc9 replace 0x20 with 0x00 (or just type `arbol ` with a space at the end)) * `arbol <model_name>` show details for model
* `mem` * `mem` (doesn't do anything?)
* `ver uniones` * `ver uniones`
* Easter Eggs: * Easter Eggs:
* `imbecil` * `imbecil`
* `idiota` * `idiota`
@ -67,12 +69,11 @@ unsigned long hash(const unsigned char *s)
## Other Functions: ## Other Functions:
Check `r2_analyze.py` for full list Check `config.yml` for full list
## File Index struct @ `0x7fcbec` ## File Index struct @ `0x7fcbec`
```cpp ```cpp
struct FileEntry { struct FileEntry {
uint32_t offset; uint32_t offset;
uint32_t size; uint32_t size;
@ -87,7 +88,7 @@ struct FileIDX {
}; };
``` ```
## Packed Index struct (array @ `0x7fc1b0`) ## Packed Index struct (array of 0x80 entries @ `0x7fc1b0`)
```cpp ```cpp
struct PackedIDX { struct PackedIDX {
void** VMT; void** VMT;
@ -111,7 +112,10 @@ struct CPP_Callback {
} }
``` ```
## Game engine Variables Pointer @ `0x7FBE4C`
## Game engine Variables Hashtable @ `0x7fbe50`
## Game engine Variables @ `0x7fbe4c`
Structure: Structure:
@ -130,20 +134,21 @@ struct GameVar {
Types Types
| Value | Type | | Value | Type |
| ------ | ----------------------- | |-------|-----------------|
| `0x10` | const char* | | `0x1` | const char* |
| `0x20` | int32_t | | `0x2` | int32_t |
| `0x30` | User Control Definition | | `0x3` | List of Defines |
| `0x40` | float | | `0x4` | float |
| `0x60` | Callback function | | `0x5` | function |
| `0x6` | Script function |
## Game World/State Pointer @ `0x7fe944` ## Game World/State Pointer @ `0x7fe944`
Points to World struct Points to World struct
| Offset | Type | Description | | Offset | Type | Description |
| ------ | ------------------------ | -------------------------------------- | |--------|--------------------------|----------------------------------------|
| 0x0000 | `void**` | Virtual Method Table | | 0x0000 | `void**` | Virtual Method Table |
| 0x0004 | `uint32_t` | Size of Entity Hashtable | | 0x0004 | `uint32_t` | Size of Entity Hashtable |
| 0x0008 | `void**` | Pointer to Entity Hashtable | | 0x0008 | `void**` | Pointer to Entity Hashtable |
@ -172,6 +177,15 @@ Points to World struct
| 0x2238 | `???` | Used in `World_Init` | | 0x2238 | `???` | Used in `World_Init` |
| 0x2254 | `float` | 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 ## Entity Hash Table
Hash-function used: [PJW](https://en.wikipedia.org/wiki/PJW_hash_function) (Same parameters as the example implementation) 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: Data format:
| Offset | Type | Description | | Offset | Type | Description |
| ------ | ------------- | ------------------------ | |--------|---------------|--------------------------|
| 0x0 | `void**` | Virtual Method Table (?) | | 0x0 | `void**` | Virtual Method Table (?) |
| 0x4 | `const char*` | name as string | | 0x4 | `const char*` | name as string |
| 0x14 | `void*` | pointer to self (why?) | | 0x14 | `void*` | pointer to self (why?) |
@ -204,21 +218,57 @@ Attributes:
- `OnDeath` - `OnDeath`
- `OnDamage` - `OnDamage`
# File Formats # Netplay protocol (WIP)
## .packed File Format: Game Info Packet
``` ```
Header: Server 'B':FZ (0/10) Ver 1.0 at 192.168.99.1:28086
"BFPK\0\0\0\0" [0-3] header/ID?
Int32ul: number of files [4-5] port (16-bit)
for each file: [6-7] max_players (16-bit)
Int32ul: path length [8-9] curr_player (16-bit)
String: path [10-x] server name (char*)
Int32ul: size
Int32ul: offset in file 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: # Virtual Method Tables:
check `r2_analyze.py` for full list check `r2_analyze.py` for full list

View file

@ -1,13 +1,24 @@
# Scrapland Reverse Engineering notes and tools # 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 * `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 - 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 * `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 * `lib/dbg.py`: general Script for poking around inside the game's scripting system
- Run `import dbg` inside the Game's Console, - Run `import dbg;dbg.init()` inside the Game's Console,
this will load all builtin modules and enable godmode this will load all builtin modules, ScrapHacks and enable godmode
- The dbg module also enables writing to the ingame console using `print <var>` - 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 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) - `dbg.menu()` Displays the Game's built in Debug Menu (doesn't work properly)
@ -24,9 +35,15 @@ WIP Memory hacking library
# Tools used: # Tools used:
- [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/) - Binary parsing:
- [IDA](https://www.hex-rays.com/products/ida/index.shtml) and [x32dbg](https://x64dbg.com/) - [HxD](https://mh-nexus.de/en/hxd/) for initial file analysis
- [Reclass.NET](https://github.com/ReClassNET/ReClass.NET) - [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/) for binary parsing
- [HxD](https://mh-nexus.de/en/hxd/) - [Kaitai Struct](http://kaitai.io/) for binary parsing
- [Kaitai Struct](http://kaitai.io/) - Static analysis:
- [Radare2](https://www.radare.org/) + [Cutter](https://cutter.re/) - [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", "cStandard": "c11",
"cppStandard": "c++17", "cppStandard": "c++17",
"intelliSenseMode": "msvc-x86", "intelliSenseMode": "msvc-x86",
"configurationProvider": "vector-of-bool.cmake-tools", "configurationProvider": "vector-of-bool.cmake-tools"
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
} }
], ],
"version": 4 "version": 4

View file

@ -78,5 +78,11 @@
"xtr1common": "cpp", "xtr1common": "cpp",
"xtree": "cpp", "xtree": "cpp",
"xutility": "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_minimum_required(VERSION 3.1)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
project(ScrapHacks project(ScrapHacks
VERSION 1.0 VERSION 1.0
DESCRIPTION "Scrapland memory hacking library" 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!") message(FATAL_ERROR "Scrapland installation folder not found!")
endif() endif()
message(STATUS "Scrapland found at ${SCRAPLAND_DIR}")
message(STATUS "Checking Scrap.exe hash") message(STATUS "Checking Scrap.exe hash")
file(SHA1 "${SCRAPLAND_DIR}/Bin/Scrap.exe" 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!") message(FATAL_ERROR "Scrap.exe hash miss match!")
endif() 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(FETCHCONTENT_QUIET 0)
set(CMAKE_BUILD_TYPE "RelMinSize") set(CMAKE_BUILD_TYPE "RelMinSize")
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}") set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}")
set(ASMJIT_EMBED true) set(ASMJIT_EMBED ON CACHE INTERNAL "")
set(ASMTK_EMBED true) set(ASMTK_EMBED ON CACHE INTERNAL "")
set(ZYDIS_BUILD_TOOLS false) set(ZYDIS_BUILD_TOOLS OFF CACHE INTERNAL "")
set(ZYDIS_BUILD_EXAMPLES false) set(ZYDIS_BUILD_EXAMPLES OFF CACHE INTERNAL "")
set(toml11_BUILD_TEST OFF CACHE INTERNAL "")
if(WIN32) if(WIN32)
if(MSVC) if(MSVC)
@ -44,54 +57,67 @@ endif(WIN32)
include(FetchContent) 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( FetchContent_Declare(
DirectX DirectX
PREFIX ${CMAKE_CURRENT_BINARY_DIR} PREFIX ${CMAKE_CURRENT_BINARY_DIR}
URL URL https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip
https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312
URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312) )
FetchContent_MakeAvailable(DirectX)
FetchContent_Declare( FetchContent_Declare(
ASM_JIT ASM_JIT
PREFIX ${CMAKE_CURRENT_BINARY_DIR} PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:asmjit/asmjit.git GIT_REPOSITORY git@github.com:asmjit/asmjit.git
GIT_SHALLOW true GIT_SHALLOW true
GIT_PROGRESS true INSTALL_COMMAND ""
CONFIGURE_COMMAND ""
) )
FetchContent_MakeAvailable(ASM_JIT)
FetchContent_Declare( FetchContent_Declare(
ASM_TK ASM_TK
PREFIX ${CMAKE_CURRENT_BINARY_DIR} PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:asmjit/asmtk.git GIT_REPOSITORY git@github.com:asmjit/asmtk.git
GIT_SHALLOW true 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( FetchContent_Declare(
Zydis Zydis
PREFIX ${CMAKE_CURRENT_BINARY_DIR} PREFIX ${CMAKE_CURRENT_BINARY_DIR}
GIT_REPOSITORY git@github.com:zyantific/zydis.git GIT_REPOSITORY git@github.com:zyantific/zydis.git
GIT_SHALLOW true 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/) 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( add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/D3D8_VMT.hpp OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/D3D8_VMT.hpp
@ -111,7 +137,10 @@ add_library(ScrapHack SHARED
${ASMJIT_SRC} ${ASMJIT_SRC}
) )
set_target_properties(ScrapHack PROPERTIES SUFFIX ".pyd") set_target_properties(ScrapHack PROPERTIES SUFFIX ".pyd")
set_target_properties(ScrapHack PROPERTIES PREFIX "_")
add_dependencies(ScrapHack D3D8_VMT) add_dependencies(ScrapHack D3D8_VMT)
# add_dependencies(ScrapHack Python152) # add_dependencies(ScrapHack Python152)
# add_dependencies(ScrapHack Python152_Bin) # add_dependencies(ScrapHack Python152_Bin)
@ -119,10 +148,13 @@ target_link_libraries(ScrapHack
d3d8 d3d8
d3dx8 d3dx8
dxerr8 dxerr8
gdiplus # gdiplus
# PYTHON15 # PYTHON15
# Zydis Zydis
legacy_stdio_definitions) legacy_stdio_definitions)
install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib) install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks)
target_compile_features(ScrapHack PUBLIC cxx_std_11) 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 ## Features
- read and write memory - read and write memory
- disassemble memory (using zydis)
- change DirectX state - change DirectX state
- Draw DirectX overlay (still need to make a useful overlay) - Draw DirectX overlay (still need to make a useful overlay)
- Dump various data structures to the console - 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) - Can be controlled via keyboard shortcuts (TODO: allow defining own shortcuts for commands)
## Prerequisites ## Prerequisites
@ -30,7 +31,34 @@ This will find the Games's installation folder, verify that the version you have
- type `import ScrapHack` - type `import ScrapHack`
- type `$help` - 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 call "%%i" x86
) )
) )
if not exist build cmake -G"NMake Makefiles" -B build if "%VSINSTALLDIR%"=="" (
cmake --build build --target install 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 endlocal

View file

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

View file

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

View file

@ -10,6 +10,7 @@
#include <string> #include <string>
#include <asmtk/asmtk.h> #include <asmtk/asmtk.h>
#include <Zydis/Zydis.h>
#include "Scrapland.hpp" #include "Scrapland.hpp"
#include "Util.hpp" #include "Util.hpp"
@ -40,17 +41,20 @@ size_t assemble(vector<string> assembly,uint64_t base) {
for (string line:assembly) { for (string line:assembly) {
if (err = p.parse((line+"\n").c_str())) { 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)); 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); scrap_log(ERR_COLOR,err_msg);
return 0; return 0;
} }
} }
if (err=code.flatten()) { if (err=code.flatten()) {
snprintf(err_msg,1024,"FLATTEN ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err)); snprintf(err_msg,1024,"FLATTEN ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err));
cerr<<err_msg<<endl;
scrap_log(ERR_COLOR,err_msg); scrap_log(ERR_COLOR,err_msg);
return 0; return 0;
} }
if (err=code.resolveUnresolvedLinks()) { if (err=code.resolveUnresolvedLinks()) {
snprintf(err_msg,1024,"RESOLVE ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err)); snprintf(err_msg,1024,"RESOLVE ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err));
cerr<<err_msg<<endl;
scrap_log(ERR_COLOR,err_msg); scrap_log(ERR_COLOR,err_msg);
return 0; return 0;
} }
@ -63,6 +67,7 @@ size_t assemble(vector<string> assembly,uint64_t base) {
MEMORY_BASIC_INFORMATION mbi; MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery((void*)base, &mbi, sizeof(mbi)) == 0) { if (VirtualQuery((void*)base, &mbi, sizeof(mbi)) == 0) {
cerr<<"ERROR: "<< GetLastErrorAsString() <<endl;
scrap_log(ERR_COLOR, "ERROR: "); scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString()); scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n"); 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)) if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect))
{ {
cerr<<"ERROR: "<< GetLastErrorAsString() <<endl;
scrap_log(ERR_COLOR, "ERROR: "); scrap_log(ERR_COLOR, "ERROR: ");
scrap_log(ERR_COLOR, GetLastErrorAsString()); scrap_log(ERR_COLOR, GetLastErrorAsString());
scrap_log(ERR_COLOR, "\n"); scrap_log(ERR_COLOR, "\n");
return 0; return 0;
}; };
cout<<"CODE: "<< hexdump_s((void*)base,buffer.size()) << endl;
memcpy((void*)base,buffer.data(),buffer.size()); memcpy((void*)base,buffer.data(),buffer.size());
scrap_log(INFO_COLOR,"Code:"+hexdump_s((void*)base,buffer.size())); scrap_log(INFO_COLOR,"Code:"+hexdump_s((void*)base,buffer.size()));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL); VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
@ -85,7 +92,38 @@ size_t asm_size(vector<string> assembly) {
return assemble(assembly,0); 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 { struct Command {
t_cmd_func func; t_cmd_func func;
@ -217,6 +255,46 @@ get_protection(void *addr) {
return mbi.Protect; 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 cmd_exec(Command* cmd, vector<string> args) {
void *addr; void *addr;
MEMORY_BASIC_INFORMATION mbi; MEMORY_BASIC_INFORMATION mbi;
@ -339,7 +417,7 @@ void cmd_read(Command* cmd,vector<string> args) {
return; return;
}; };
string hxd = hexdump_s(mptr, size); 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); VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
if (buffer) { if (buffer) {
free(buffer); free(buffer);
@ -399,22 +477,26 @@ void cmd_dx8(Command* cmd,vector<string> args) {
} }
void cmd_dump_stack(Command* cmd, vector<string> args) { void cmd_dump_stack(Command* cmd, vector<string> args) {
stringstream ret;
void** stack=(void**)_AddressOfReturnAddress(); void** stack=(void**)_AddressOfReturnAddress();
cout<<"ESP: "<<stack<<endl; cout<<"ESP: "<<stack<<endl;
for (size_t n=0;n<0x100;++n) { for (size_t n=0;n<0x100;++n) {
if (!addr_exists(stack[n])) { if (!addr_exists(stack[n])) {
continue; continue;
} }
char R=can_read(stack[n]) ? 'R' : ' '; bool r,w,x;
char W=can_write(stack[n]) ? 'W' : ' '; char R=(r=can_read(stack[n])) ? 'R' : ' ';
char X=can_execute(stack[n]) ? 'X' : ' '; char W=(w=can_write(stack[n])) ? 'W' : ' ';
cout<<"ESP[" << std::hex << setfill('0') << setw(2) << n <<"]: "<<stack[n]<<" "<<R<<W<<X; char X=(x=can_execute(stack[n])) ? 'X' : ' ';
if (can_read(stack[n])) { ret<< std::hex << setfill('0') << setw(8) << stack+(n*sizeof(void*)) << ": "<<stack[n]<<" "<<R<<W<<X;
cout<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]"<<endl; if (r && !x) {
} else { ret<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]";
cout<<endl; } else if (r && x) {
ret<<" [ "<<disassemble(stack[n],5,true)<<"]";
} }
ret<<endl;
} }
scrap_log(INFO_COLOR,ret.str());
return; return;
} }
@ -426,7 +508,7 @@ void cmd_dump_py(Command* cmd,vector<string> args) {
<< meth.second->ml_meth << endl; << 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) { 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; 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; 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) { 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); dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS), &out);
out << "Entity Lists:" << endl; out << "Entity Lists:" << endl;
dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS), &out); 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; return;
} }
@ -478,10 +560,14 @@ void cmd_disable_overlay(Command* cmd,vector<string> args) {
void cmd_print_alarm(Command* cmd,vector<string> args) { void cmd_print_alarm(Command* cmd,vector<string> args) {
stringstream out; stringstream out;
float *alarm = ptr<float>(P_WORLD, O_ALARM); float alarm = ptr<float>(P_WORLD, O_ALARM)[0];
float *alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW); float alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW)[0];
out << "Alarm: " << alarm[0] << " + " << alarm_grow[0] << endl; if (alarm_grow<0) {
scrap_log(INFO_COLOR,out.str().c_str()); out << "Alarm: " << alarm << " - " << alarm_grow << endl;
} else {
out << "Alarm: " << alarm << " + " << alarm_grow << endl;
}
scrap_log(INFO_COLOR,out.str());
return; return;
} }
@ -528,6 +614,7 @@ void cmd_asm(Command* cmd, vector<string> args) {
assemble(split(code,';'),buffer_addr); assemble(split(code,';'),buffer_addr);
} }
void cmd_help(Command* cmd,vector<string> args); void cmd_help(Command* cmd,vector<string> args);
static REPL* repl=new REPL( 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")}, {"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")}, {"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")}, {"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")}, {"unload",new Command(cmd_unload,"Usage: $unload","Unload ScrapHacks")},
{"dx8",new Command(cmd_dx8,"Usage: $dx8 <subcommand>","Manipulate DirectX 8 functions and state",{ {"dx8",new Command(cmd_dx8,"Usage: $dx8 <subcommand>","Manipulate DirectX 8 functions and state",{

View file

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

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "Structures.hpp" #include "Structures.hpp"
#include "Util.hpp"
#include <sstream> #include <sstream>
using namespace std; using namespace std;
@ -17,14 +17,21 @@ using namespace std;
#define P_PY_MODS 0x79C698 #define P_PY_MODS 0x79C698
// FUNCTION ADDRESSES // FUNCTION ADDRESSES
// ENGINE INTERNALS
#define P_CON_HANDLER 0x402190 #define P_CON_HANDLER 0x402190
#define P_SCRAP_LOG 0x4134C0 #define P_SCRAP_LOG 0x4134C0
#define P_SCRAP_EXEC 0x5a8390 #define P_SCRAP_EXEC 0x5a8390
#define P_SCRAP_EXIT 0x4010c0 #define P_SCRAP_EXIT 0x4010c0
#define P_D3DCHECK 0x602a70 #define P_D3DCHECK 0x602a70
#define P_D3DDEV 0x852914 #define P_D3DDEV 0x852914
// PYTHON FUNCTIONS
#define P_Py_InitModule 0x5A8FB0 #define P_Py_InitModule 0x5A8FB0
#define P_PyArg_ParseTuple 0x5bb9d0 #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) #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_log)(unsigned int color, const char *message);
typedef int(_cdecl *t_scrap_exec)(const char *code); typedef int(_cdecl *t_scrap_exec)(const char *code);
typedef int(_cdecl *t_PyArg_ParseTuple)(void *PyObj, char *format, ...); 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, typedef int(_cdecl *t_Py_InitModule)(const char *name, void *methods,
const char *doc, void *passthrough, const char *doc, void *passthrough,
int module_api_version); 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 // GLOBAL FUNCTIONS
auto scrap_exec = (t_scrap_exec)P_SCRAP_EXEC; auto scrap_exec = (t_scrap_exec)P_SCRAP_EXEC;
auto pyarg_parsetuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple; auto PyArg_ParseTuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple;
auto py_initmodule = (t_Py_InitModule)P_Py_InitModule; 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) { int scrap_log(unsigned int color,const char* msg) {
return ((t_scrap_log)P_SCRAP_LOG)(color,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); return ((t_scrap_log)P_SCRAP_LOG)(scrap_RGB(r,g,b),msg);
} }
int scrap_log(unsigned int color,string msg) { int scrap_log(unsigned int color,string msg) {
return ((t_scrap_log)P_SCRAP_LOG)(color,msg.c_str()); 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()); 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 size_ht(HashTable<EntityList> *ht) {
size_t cnt = 0; size_t cnt = 0;

View file

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

View file

@ -12,9 +12,10 @@ HANDLE hThread = INVALID_HANDLE_VALUE;
bool loaded = false; bool loaded = false;
HMODULE mod = nullptr; HMODULE mod = nullptr;
DLL_EXPORT void initScrapHack() { DLL_EXPORT void init_ScrapHack() {
DllPreInit(); DllPreInit();
if (!loaded) { if (!loaded) {
Sleep(1000);
hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)DllInit, mod, hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)DllInit, mod,
0, 0); 0, 0);
CloseHandle(hThread); 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 SScorer
import Menu import Menu
import sys import sys
QC = quickconsole QC = quickconsole
MF = MissionsFuncs MF = MissionsFuncs
last_frame = None last_frame = None
level = 3
initialized = 0
sys.path.append(".\\pylib\\Lib") sys.path.append(".\\pylib\\Lib")
sys.path.append(".\\pylib\\Libs") sys.path.append(".\\pylib\\Libs")
sys.path.append(".\\pylib") sys.path.append(".\\pylib")
@ -16,11 +18,18 @@ sys.path.append(".\\pylib")
def reload(): def reload():
sys.settrace(None) sys.settrace(None)
sys.modules['__builtin__'].reload(sys.modules['dbg']) sys.modules['__builtin__'].reload(sys.modules[__name__])
def dgb_info(): 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.DeleteScheduledFuncs("dbg.dbg_info") Scrap.DeleteScheduledFuncs("dbg.dbg_info")
Scrap.AddScheduledFunc(Scrap.GetTime()+0.1, dgb_info, (), "dbg.dbg_info") Scrap.AddScheduledFunc(Scrap.GetTime()+0.1, dgb_info, (), "dbg.dbg_info")
@ -197,12 +206,11 @@ def helplib():
logfile_name = None logfile_name = None
print "Done!" print "Done!"
def enable_all_conv(): def enable_all_conv():
try: try:
import CharConversor import CharConversor
except ImportError: except ImportError:
print("CharConversor not available") # print("CharConversor not available")
return return
CharConversor.ConversionChars = list(CharConversor.ConversionChars) CharConversor.ConversionChars = list(CharConversor.ConversionChars)
E = Scrap.GetFirst() E = Scrap.GetFirst()
@ -244,20 +252,6 @@ def nuke():
E = Scrap.GetEntity(E.NextInSlot) 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): def become(name):
import CharConversor import CharConversor
enable_all_conv() enable_all_conv()
@ -299,7 +293,7 @@ def getall():
me = Scrap.UsrEntity(0) me = Scrap.UsrEntity(0)
while E: while E:
try: try:
E.Pos = me.Pos E.Descriptor = "HAXX!"
except: except:
pass pass
E = Scrap.GetEntity(E.NextInSlot) E = Scrap.GetEntity(E.NextInSlot)
@ -308,6 +302,11 @@ def getall():
def god(e=None): def god(e=None):
if e == None: if e == None:
e = Scrap.UsrEntity(0) e = Scrap.UsrEntity(0)
if e:
try:
e.IsType("Car")
except:
return
if e: if e:
if e.IsType("Car"): if e.IsType("Car"):
e.Ammo00 = SWeap.GetFAmmo(0, "Max") e.Ammo00 = SWeap.GetFAmmo(0, "Max")
@ -324,9 +323,18 @@ def god(e=None):
elif e.IsType("WalkChar"): elif e.IsType("WalkChar"):
e.Energy = 1 e.Energy = 1
e.Invulnerable = 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.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(): def ultranuke():
@ -337,11 +345,92 @@ def ultranuke():
(), "dbg.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(): def brake():
if me: if me:
me.Vel = (0, 0, 0) 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): for _ in range(1024):
Scrap.DeleteScheduledFuncs("dbg.dbg_info") Scrap.DeleteScheduledFuncs("dbg.dbg_info")
Scrap.DeleteScheduledFuncs("dbg.god") Scrap.DeleteScheduledFuncs("dbg.god")
@ -354,18 +443,31 @@ for module in sys.builtin_module_names:
exec("import " + module) exec("import " + module)
sys.settrace(None) sys.settrace(None)
me = Scrap.UsrEntity(0)
notrace() notrace()
helplib() helplib()
# settrace() # 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: | notes: |
0x7faa4c: temp storage? 0x7faa4c: temp storage?
0x7d2094: some reference count 0x4039b0: fcn.handle_cli_opts?
0x668007: ?
comments: comments:
0x6113f9: Check if Window exists 0x6113f9: Check if Window exists
flags: 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 0x7FE944: P_World
0x792618: P_Eng3d_ver
0x853a24: P_gWorld
0x7FBE4C: P_Vars 0x7FBE4C: P_Vars
0x79C698: Py_Mods 0x79C698: Py_Mods
0x852914: P_D3D8_Dev 0x852914: P_D3D8_Dev
0x850258: P_D3D8_ZBuffer
0x850408: P_D3D8_BackBuffer
0x7FCC00: N_Paks_opened 0x7FCC00: N_Paks_opened
0x7fcbec: Hash_Index_Size 0x7fcbec: Hash_Index_Size
0x7fcbf0: P_Hash_Index 0x7fcbf0: P_Hash_Index
@ -36,7 +47,27 @@ flags:
0x7fadd8: is_python 0x7fadd8: is_python
0x7fc084: pak_lock 0x7fc084: pak_lock
0x7fbe7c: current_language 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: VMTs:
0x78d4d8: Py_entity 0x78d4d8: Py_entity
@ -56,133 +87,309 @@ VMTs:
0x7933ac: 3d_Gfx 0x7933ac: 3d_Gfx
0x7933a0: NodeFX 0x7933a0: NodeFX
classes:
World:
types: 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 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 HT_Entry { void* data; const char* key; struct HT_Entry* next;};"
- "struct PakEntry { unsigned char* filename; bool locked; void* data; uint32_t seek;};" - "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 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 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; };" - "struct HashTable { uint32_t size; struct HashTableEntry** data; };"
- "struct va_list { unsigned int gp_offset; unsigned int fp_offset; void *overflow_arg_area; void *reg_save_area; };"
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);"
functions: functions:
0x6B1C70: strcmp 0x6283a0:
0x5BB9D0: PyArg_ParseTuple name: load_emi
0x5DD510: init_engine_3d 0x4fa9f0:
0x401180: create_window name: send_pkt
0x401240: create_main_window 0x5ca9e0:
0x4016F0: reg_get_val signature: void* PyFrame_New(void* thread_state, void* code_object,void* globals, void* locals)
0x4134C0: write_log name: PyFrame_New
0x414280: prepare_html_log 0x5bcae0:
0x418220: get_version_info signature: void PyErr_SetString(void* obj, const char* err_msg);
0x4137E0: write_html_log name: PyErr_SetString
0x402190: handle_console_input 0x5cb040:
0x5F9520: handle_render_console_input signature: void* eval_code2(void* dict, const char* key, void* item);
0x404A50: find_entity name: eval_code2
0x47C1E0: ht_hash_ent_list 0x5e3c50:
0x404BB0: ht_hash_ent convention: cdecl-thiscall-ms
0x404460: register_c_callback name: read_int
0x417470: load_game 0x5e3b50:
0x5E3800: fopen_from_pak convention: cdecl-thiscall-ms
0x5e3500: fopen name: read_block_header
0x403370: init_debug 0x5c66d0:
0x401770: init signature: void initerrors(void* dict);
0x4026D0: init_py name: initerrors
0x405B40: init_py_sub 0x5bb370:
0x5A8FB0: Py_InitModule signature: int PyDict_SetItemString(void* dict, const char* key, void* item);
0x41AB50: open_pak name: PyDict_SetItemString
0x5A8390: PyRun_SimpleString 0x5b9960:
0x414570: setup_game_vars signature: void* PyObject_NEW(void* type, void* typeobj);
0x5FBC50: throw_assertion_1 name: PyObject_NEW
0x414070: throw_assertion_2 0x4145e0:
0x5F7000: read_ini convention: cdecl-thiscall-ms
0x650F80: load_sm3 signature: bool get_config_var(char* name);
0x6665A0: load_m3d_1 name: get_config_var
0x666900: load_m3d_2 0x413470:
0x479B20: world_constructor signature: void init_logging();
0x479B40: init_world name: init_logging
0x402510: deinit_world 0x5a8040:
0x479870: make_world signature: void Py_Initialize();
0x602A70: render_frame name: Py_Initialize
0x6B738C: handle_exception 0x5bb4e0:
0x5B9E70: PyObject_GetAttrString name: PyModule_GetDict
0x413ee0: dbg_log signature: void* PyModule_GetDict(void*);
0x5f75e0: init_d3d 0x5c6610:
0x63a2f0: gdi_draw_line name: _PyBuiltin_Init_1
0x5e3250: read_stream signature: void* _PyBuiltin_Init_1();
0x5e3bb0: read_stream_wrapper 0x5b5db0:
0x50b9b0: init_scorer name: PyString_FromString
0x582e10: init_action_class_list signature: void* PyString_FromString(const char*);
0x528910: init_sound_sys 0x5ba3a0:
0x5268d0: try_init_sound_sys name: PyDict_New
0x404280: cPyFunction_set_func signature: void* PyDict_New();
0x414680: load_config 0x5c7bd0:
0x414810: save_config name: PyThreadState_Swap
0x4f42a0: close_server_socket signature: void* PyThreadState_Swap(void* new);
0x4f4d10: close_server 0x5c7870:
0x4f48e0: close_client name: PyInterpreterState_New
0x4f4fb0: is_server signature: void* PyInterpreterState_New();
0x4f4a10: is_client 0x5c79b0:
0x4fac50: is_master name: PyThreadState_New
0x526910: close_sound_sys signature: void* PyThreadState_New(void* interp);
0x526520: shutdown_sound_sys 0x6ad1e9:
0x5dd700: close_3d_engine name: getenv
0x5a7320: close_window signature: char* getenv(char* var);
0x5dff20: set_exception_handler 0x401180:
0x5a7f20: get_console_wnd name: create_window
0x5a73a0: show_console 0x401240:
0x666c60: read_m3d name: create_main_window
0x417df0: snprintf 0x4016f0:
0x5fc930: printf name: reg_get_val
0x6597d0: read_ini_entry signature: int reg_get_val(const char* value);
0x5fc0a0: engine_debug_log 0x401770:
0x5a7440: create_console_window name: init
0x6114e0: setup_window 0x402190:
0x404420: clear_functions name: handle_console_input
0x405ca0: close_py_subsys signature: int handle_console_input(const char* input);
0x50bcb0: close_scorer 0x402510:
0x479b20: close_world name: deinit_world
0x582e70: close_action_class 0x4026d0:
0x50b6a0: get_scorer name: init_py
0x50ea20: scorer_parse_type 0x403370:
0x636580: list_models name: init_engine
0x5a90f0: Py_BuildValue 0x4037e0:
0x41c5a0: has_lst_file name: init_debug
0x5a8e90: py_error 0x404280:
0x5a9890: get_module_dict name: cPyFunction_set_func
0x5c7bb0: get_current_thread 0x404420:
0x5aa140: preload_lib name: clear_functions
0x413c10: sprintf 0x404460:
0x405850: check_is_python name: register_c_callback
0x47bf90: setup_ent_list signature: int register_c_callback(const char* name,void* func);
0x474f80: ent_list_get_set 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: | script: |
e asm.cmt.right = true e asm.cmt.right = true
e cmd.stack = true
e scr.utf8 = true e scr.utf8 = true
e asm.describe = false e asm.describe = false
e graph.cmtright = true 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 from datetime import datetime
import subprocess as SP import subprocess as SP
from tqdm import tqdm from tqdm import tqdm
from pprint import pprint
import os
import sys import sys
import yaml import yaml
tqdm_ascii = False
r2cmds = [] r2cmds = []
x64_dbg_script=[] x64_dbg_script = []
script_path = os.path.dirname(os.path.abspath(__file__)) script_path = os.path.dirname(os.path.abspath(__file__))
scrap_exe = os.path.abspath(sys.argv[1]) scrap_exe = os.path.abspath(sys.argv[1])
scrapland_folder = os.path.abspath(os.path.dirname(scrap_exe)) scrapland_folder = os.path.abspath(os.path.dirname(scrap_exe))
r2_script_path=os.path.join(scrapland_folder, "scrap_dissect.r2") r2_script_path = os.path.join(scrapland_folder, "scrap_dissect.r2")
x64_dbg_script_path=os.path.join(scrapland_folder, "scrap_dissect.x32dbg.txt") x64_dbg_script_path = os.path.join(scrapland_folder, "scrap_dissect.x32dbg.txt")
json_path=os.path.join(scrapland_folder, "scrap_dissect.json") json_path = os.path.join(scrapland_folder, "scrap_dissect.json")
assert os.path.isfile(scrap_exe), "File not found!" assert os.path.isfile(scrap_exe), "File not found!"
r2 = r2pipe.open(scrap_exe) r2 = r2pipe.open(scrap_exe)
@ -24,19 +24,22 @@ file_hashes = r2.cmdj("itj")
target_hashes = { target_hashes = {
"sha1": "d2dde960e8eca69d60c2e39a439088b75f0c89fa", "sha1": "d2dde960e8eca69d60c2e39a439088b75f0c89fa",
"md5": "a934c85dca5ab1c32f05c0977f62e186", "md5": "a934c85dca5ab1c32f05c0977f62e186",
"sha256": "24ef449322f28f87b702834f1a1aac003f885db6d68757ff29fad3ddba6c7b88",
} }
assert file_hashes == target_hashes, "Hash mismatch" 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 global x64_dbg_script
if isinstance(addr,int): if isinstance(addr, int):
addr=hex(addr) addr = hex(addr)
if prefix: if prefix:
x64_dbg_script.append(f'lbl {addr},"{prefix}.{name}"') x64_dbg_script.append(f'lbl {addr},"{prefix}.{name}"')
else: else:
x64_dbg_script.append(f'lbl {addr},"{name}"') x64_dbg_script.append(f'lbl {addr},"{name}"')
def r2_cmd(cmd): def r2_cmd(cmd):
global r2, r2cmds global r2, r2cmds
r2cmds.append(cmd) r2cmds.append(cmd)
@ -54,13 +57,15 @@ def r2_cmdJ(cmd):
r2cmds.append(cmd) r2cmds.append(cmd)
return r2.cmdJ(cmd) return r2.cmdJ(cmd)
t_start=datetime.today()
t_start = datetime.today()
def analysis(full=False): def analysis(full=False):
print("[*] Running analysis") print("[*] Running analysis")
steps=[] steps = []
if full: if full:
steps=[ steps = [
"e anal.dataref = true", "e anal.dataref = true",
# "e anal.esil = true", # "e anal.esil = true",
"e anal.jmp.after = true", "e anal.jmp.after = true",
@ -72,52 +77,58 @@ def analysis(full=False):
"e anal.vinfun = true", "e anal.vinfun = true",
"e asm.anal = true", "e asm.anal = true",
] ]
steps+=["aaaaa"] if full:
steps += ["aaaa"]
else:
steps += ["aaa"]
for ac in steps: for ac in steps:
print(f"[*] Running '{ac}'") print(f"[*] Running '{ac}'")
r2_cmd(f"{ac} 2>NUL") 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") 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(): for line in config.script.strip().splitlines():
r2_cmd(line) r2_cmd(line)
analysis(False) analysis(False)
for addr,comment in config.comments.items(): for addr, comment in config.comments.items():
r2_cmd(f"CC {comment} @ {hex(addr)}") r2_cmd(f"CC {comment} @ {hex(addr)}")
for t in config.types: for t in config.types:
r2_cmd(f'"td {t}"') r2_cmd(f'"td {t}"')
for addr, name in config.flags.items(): 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)}") r2_cmd(f"f loc.{name} 4 {hex(addr)}")
for addr, name in config.functions.items(): for addr, func in config.functions.items():
x64_dbg_label(addr,name,"fcn") name, sig = func.get("name"), func.get("signature")
r2_cmd(f"afr fcn.{name} {hex(addr)}") if name:
x64_dbg_label(addr, name, "fcn")
for addr,sig in config.function_signatures.items(): r2_cmd(f"afr fcn.{name} {hex(addr)}")
r2_cmd(f'"afs {config.function_signatures[addr]}" @{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(): def vtables():
ret = {} ret = {}
print("[*] Analyzing VTables") print("[*] Analyzing VTables")
vtables = r2_cmdJ("avj") vtables = r2_cmdJ("avj")
for c in tqdm(vtables, ascii=True): for c in tqdm(vtables, ascii=tqdm_ascii):
methods = [] methods = []
name=config.VMTs.get(c.offset,f"{c.offset:08x}") name = config.VMTs.get(c.offset, f"{c.offset:08x}")
x64_dbg_label(c.offset,name,"vmt") x64_dbg_label(c.offset, name, "vmt")
r2_cmd(f"f vmt.{name} 4 {hex(c.offset)}") 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)) 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") r2_cmd(f"afr fcn.vmt.{name}.{idx} {hex(m.offset)} 2>NUL")
ret[hex(c.offset)] = methods ret[hex(c.offset)] = methods
return ret return ret
@ -127,14 +138,14 @@ def c_callbacks():
print("[*] Parsing C Callbacks") print("[*] Parsing C Callbacks")
funcs = {} funcs = {}
res = r2_cmd("/r fcn.register_c_callback ~CALL[1]").splitlines() 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"s {addr}")
r2_cmd(f"so -3") r2_cmd(f"so -3")
func, name = r2_cmdJ(f"pdj 2") func, name = r2_cmdJ(f"pdj 2")
func = func.refs[0].addr func = func.refs[0].addr
name = r2_cmd(f"psz @{hex(name.refs[0].addr)}").strip() name = r2_cmd(f"psz @{hex(name.refs[0].addr)}").strip()
r2_cmd(f"afr fcn.callbacks.{name} {hex(func)} 2>NUL") 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) funcs[name] = hex(func)
return funcs return funcs
@ -142,22 +153,22 @@ def c_callbacks():
def assertions(): def assertions():
assertions = {} assertions = {}
for (n_args, a_addr) in [ for (n_args, a_addr) in [
(4, "fcn.throw_assertion_1"), (3, "fcn.throw_assertion_1"),
(3, "fcn.throw_assertion_2"), (4, "fcn.throw_assertion_2"),
]: ]:
print(f"[*] Parsing C assertions for {a_addr}") print(f"[*] Parsing C assertions for {a_addr}")
res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines() res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines()
print() print()
for line in tqdm(res, ascii=True): for line in tqdm(res, ascii=tqdm_ascii):
addr = line.strip() addr = line.strip()
r2_cmd(f"s {addr}") r2_cmd(f"s {addr}")
r2_cmd(f"so -{n_args}") r2_cmd(f"so -{n_args}")
dis=r2_cmdJ(f"pij {n_args}") dis = r2_cmdJ(f"pij {n_args}")
if n_args == 4: if n_args == 4:
file, msg, date, line = dis line, date, file, msg = dis
elif n_args == 3: elif n_args == 3:
date = None date = None
file, msg, line = dis line, file, msg = dis
try: try:
file = r2_cmd(f"psz @{file.refs[0].addr}").strip() file = r2_cmd(f"psz @{file.refs[0].addr}").strip()
msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip() msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip()
@ -180,32 +191,35 @@ def bb_refs(addr):
ret = {} ret = {}
res = r2_cmd(f"/r {addr} ~fcn[0,1]").splitlines() res = r2_cmd(f"/r {addr} ~fcn[0,1]").splitlines()
print() print()
for ent in res: for ent in tqdm(res, ascii=tqdm_ascii):
func, hit = ent.split() func, hit = ent.split()
ret[hit] = {"asm": [], "func": func} ret[hit] = {"asm": [], "func": func}
for ins in r2_cmdJ(f"pdbj @{hit}"): for ins in r2_cmdJ(f"pdbj @{hit}"):
ret[hit]["asm"].append(ins.disasm) ret[hit]["asm"].append(ins.disasm)
return ret return ret
def world(): def world():
print("[*] Parsing World offsets") print("[*] Parsing World offsets")
return bb_refs("loc.P_World") return bb_refs("loc.P_World")
def render(): def render():
print("[*] Parsing D3D_Device offsets") print("[*] Parsing D3D_Device offsets")
return bb_refs("loc.P_D3D8_Dev") return bb_refs("loc.P_D3D8_Dev")
def py_mods(): def py_mods():
print("[*] Parsing Python modules") print("[*] Parsing Python modules")
res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines() res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines()
print() print()
py_mods = {} 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"s {call_loc}")
r2_cmd(f"so -3") r2_cmd(f"so -3")
args = r2_cmdJ("pdj 3") args = r2_cmdJ("pdj 3")
refs = [] refs = []
if not all([arg.type == "push" for arg in args]): if not all(arg.type == "push" for arg in args):
continue continue
for arg in args: for arg in args:
refs.append(hex(arg.val)) refs.append(hex(arg.val))
@ -214,7 +228,7 @@ def py_mods():
name = r2_cmd(f"psz @{name}").strip() name = r2_cmd(f"psz @{name}").strip()
r2_cmd(f"s {methods}") r2_cmd(f"s {methods}")
r2_cmd(f"f py.{name} 4 {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": {}} py_mods[name] = {"methods_addr": methods, "doc": doc, "methods": {}}
while True: while True:
m_name, m_func, _, m_doc = [v.value for v in r2_cmdJ(f"pfj xxxx")] 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, m_func, m_doc = map(hex, (m_name, m_func, m_doc))
m_name = r2_cmd(f"psz @{m_name}").strip() m_name = r2_cmd(f"psz @{m_name}").strip()
r2_cmd(f"f py.{name}.{m_name}.__doc__ 4 {m_doc}") r2_cmd(f"f py.{name}.{m_name}.__doc__ 4 {m_doc}")
if int(m_doc,16)!=0: if int(m_doc, 16) != 0:
x64_dbg_label(m_doc,f"{name}.{m_name}.__doc__","py") x64_dbg_label(m_doc, f"{name}.{m_name}.__doc__", "py")
m_doc = r2_cmd(f"psz @{m_doc}").strip() m_doc = r2_cmd(f"psz @{m_doc}").strip()
else: else:
m_doc=None m_doc = None
py_mods[name]["methods"][m_name] = {"addr": m_func, "doc": m_doc} py_mods[name]["methods"][m_name] = {"addr": m_func, "doc": m_doc}
r2_cmd(f"afr py.{name}.{m_name} {m_func} 2>NUL") 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") r2_cmd("s +16")
return py_mods return py_mods
@ -240,7 +254,7 @@ def game_vars():
print("[*] Parsing Game variables") print("[*] Parsing Game variables")
res = r2_cmd("/r fcn.setup_game_vars ~CALL[1]").splitlines() res = r2_cmd("/r fcn.setup_game_vars ~CALL[1]").splitlines()
print() print()
for line in tqdm(res, ascii=True): for line in tqdm(res, ascii=tqdm_ascii):
addr = line.strip() addr = line.strip()
r2_cmd(f"s {addr}") r2_cmd(f"s {addr}")
args = r2_cmd("pdj -5") # seek and print disassembly args = r2_cmd("pdj -5") # seek and print disassembly
@ -259,27 +273,22 @@ def game_vars():
break break
if len(args_a) != 4: if len(args_a) != 4:
continue continue
if not all(["val" in v for v in args_a]): if not all("val" in v for v in args_a):
continue continue
addr, name, _, desc = [v["val"] for v in args_a] addr, name, _, desc = [v["val"] for v in args_a]
name = r2_cmd(f"psz @{hex(name)}").strip() name = r2_cmd(f"psz @{hex(name)}").strip()
desc = r2_cmd(f"psz @{hex(desc)}").strip() desc = r2_cmd(f"psz @{hex(desc)}").strip()
addr = hex(addr) addr = hex(addr)
r2_cmd(f"f loc.gvar.{name} 4 {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} ret[addr] = {"name": name, "desc": desc}
return ret return ret
ret = dict( ret = {}
game_vars=game_vars(), # world, render
c_callbacks=c_callbacks(), for func in ["game_vars", "c_callbacks", "py_mods", "assertions", "vtables"]:
py_mods=py_mods(), ret[func] = globals()[func]()
assertions=assertions(),
vtables=vtables(),
world=world(),
render=render(),
)
analysis(True) analysis(True)
@ -288,7 +297,7 @@ with open(json_path, "w") as of:
print("[+] Wrote scrap_dissect.json") 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)) of.write("\n".join(x64_dbg_script))
print("[+] Wrote scrap_dissect.x32dbg.txt") print("[+] Wrote scrap_dissect.x32dbg.txt")
@ -296,30 +305,37 @@ print("[+] Wrote scrap_dissect.x32dbg.txt")
with open(r2_script_path, "w") as of: with open(r2_script_path, "w") as of:
wcmds = [] wcmds = []
for cmd in r2cmds: for cmd in r2cmds:
record=True if cmd == "avj":
for start in ["p","/","s"]: continue
record = True
for start in ["p", "/", "s"]:
if cmd.strip('"').startswith(start): if cmd.strip('"').startswith(start):
record=False record = False
if record: if record:
wcmds.append(cmd) wcmds.append(cmd)
of.write("\n".join(wcmds)) of.write("\n".join(wcmds))
print("[+] Wrote scrap_dissect.r2") print("[+] Wrote scrap_dissect.r2")
r2.quit()
def start_program(cmdl,**kwargs): def start_program(cmdl, **kwargs):
if os.name=='nt': if os.name == "nt":
return SP.Popen(['cmd','/c','start']+cmdl,**kwargs) return SP.Popen(["cmd", "/c", "start"] + cmdl, **kwargs)
else: 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") print("[+] Executing Cutter")
try: 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: except FileNotFoundError:
print("[-] cutter not installed, falling back to r2") 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 glob
import os import os
import shutil import shutil
from construct import * from construct import (
Struct,
PascalString,
Int32ul,
Lazy,
Pointer,
Bytes,
this,
PrefixedArray,
Const,
Debugger
)
from tqdm import tqdm from tqdm import tqdm
setglobalstringencoding(None)
ScrapFile = Struct( ScrapFile = Struct(
"path" / PascalString(Int32ul), "path" / PascalString(Int32ul, encoding="ascii"),
"size" / Int32ul, "size" / Int32ul,
"offset" / 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( PackedHeader = Struct(
Const(b"BFPK"), Const(b"\0\0\0\0"), "files" / PrefixedArray(Int32ul, ScrapFile) 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