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:
parent
aabacafd9c
commit
8d92f25b8c
47 changed files with 2744 additions and 411 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
|
104
NOTES.md
104
NOTES.md
|
@ -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,10 +25,10 @@
|
||||||
|
|
||||||
## 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`
|
||||||
|
@ -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:
|
||||||
|
|
||||||
|
@ -131,19 +135,20 @@ 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
|
||||||
|
|
37
README.md
37
README.md
|
@ -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
|
3
ScrapHacks/.vscode/c_cpp_properties.json
vendored
3
ScrapHacks/.vscode/c_cpp_properties.json
vendored
|
@ -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
|
||||||
|
|
8
ScrapHacks/.vscode/settings.json
vendored
8
ScrapHacks/.vscode/settings.json
vendored
|
@ -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
21
ScrapHacks/.vscode/tasks.json
vendored
Normal 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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -5,6 +5,15 @@ if "%VSINSTALLDIR%"=="" (
|
||||||
call "%%i" x86
|
call "%%i" x86
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if "%VSINSTALLDIR%"=="" (
|
||||||
|
echo "VSINSTALLDIR" not set something is wrong!
|
||||||
|
) else (
|
||||||
if not exist build cmake -G"NMake Makefiles" -B build
|
if not exist build cmake -G"NMake Makefiles" -B build
|
||||||
|
if "%1"=="--run" (
|
||||||
|
cmake --build build --target run
|
||||||
|
) else (
|
||||||
cmake --build build --target install
|
cmake --build build --target install
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
endlocal
|
endlocal
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
74
ScrapHacks/src/Py_Mod.hpp
Normal 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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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",{
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
23
ScrapHacks/src/python/ScrapHack.py
Normal file
23
ScrapHacks/src/python/ScrapHack.py
Normal 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)
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
dgb_info()
|
||||||
enable_all_conv()
|
enable_all_conv()
|
||||||
god()
|
god()
|
||||||
Scrap.Set("debug", 3)
|
Scrap.Set("debug", level)
|
||||||
Scrap.Set("ShowConsoleLog", 1)
|
Scrap.Set("ShowConsoleLog", 1)
|
||||||
Scrap.Set("AlwaysFlushLog", 1)
|
Scrap.Set("AlwaysFlushLog", 1)
|
||||||
Scrap.Set("PythonExecute", "import dbg")
|
Scrap.Set("PythonExecute", "import dbg;dbg.init()")
|
||||||
exec("import QuickConsole;QuickConsole.debug=sys.modules['dbg']")
|
Scrap.DeleteScheduledFuncs("dbg_init")
|
||||||
|
Scrap.DeleteScheduledFuncs("dbg_init")
|
||||||
|
Scrap.AddScheduledFunc(Scrap.GetTime()+1, init, (), "dbg_init")
|
||||||
|
initialized = 1
|
||||||
|
|
||||||
print "Debug Module loaded"
|
|
||||||
|
exec("import QuickConsole;QuickConsole.dbg=sys.modules['dbg']")
|
||||||
|
print "Debug Module loaded use /dbg.init to initialize"
|
439
config.yml
439
config.yml
|
@ -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,6 +87,9 @@ 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; };"
|
||||||
|
@ -65,124 +99,297 @@ types:
|
||||||
- "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
22
file_formats/ai_path.md
Normal 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
126
file_formats/chunked.md
Normal 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
15
file_formats/packed.md
Normal 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
60
frida/frida_hook_net.js
Normal 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;
|
||||||
|
}
|
||||||
|
})
|
174
frida/frida_hook_read_trace.js
Normal file
174
frida/frida_hook_read_trace.js
Normal 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
56
frida/frida_inject_net.py
Normal 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()
|
59
frida/frida_inject_read_trace.py
Normal file
59
frida/frida_inject_read_trace.py
Normal 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
8
frida/frida_mem_mon.js
Normal 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
22
frida/frida_mem_mon.py
Normal 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()
|
43
frida/frida_stalker_test.js
Normal file
43
frida/frida_stalker_test.js
Normal 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)
|
67
frida/frida_stalker_test.py
Normal file
67
frida/frida_stalker_test.py
Normal 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()
|
|
@ -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))
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ 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__))
|
||||||
|
@ -24,10 +24,12 @@ 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):
|
||||||
|
@ -37,6 +39,7 @@ def x64_dbg_label(addr,name,prefix=None):
|
||||||
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,8 +57,10 @@ 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 = []
|
||||||
|
@ -72,11 +77,15 @@ 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))
|
||||||
|
@ -97,25 +106,27 @@ for addr, name in config.flags.items():
|
||||||
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():
|
||||||
|
name, sig = func.get("name"), func.get("signature")
|
||||||
|
if name:
|
||||||
x64_dbg_label(addr, name, "fcn")
|
x64_dbg_label(addr, name, "fcn")
|
||||||
r2_cmd(f"afr fcn.{name} {hex(addr)}")
|
r2_cmd(f"afr fcn.{name} {hex(addr)}")
|
||||||
|
r2_cmd(f"afn fcn.{name} {hex(addr)}")
|
||||||
for addr,sig in config.function_signatures.items():
|
if sig:
|
||||||
r2_cmd(f'"afs {config.function_signatures[addr]}" @{hex(addr)}')
|
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")
|
||||||
|
@ -127,7 +138,7 @@ 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")
|
||||||
|
@ -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))
|
||||||
|
@ -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,7 +273,7 @@ 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()
|
||||||
|
@ -271,15 +285,10 @@ def game_vars():
|
||||||
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)
|
||||||
|
|
||||||
|
@ -296,6 +305,8 @@ 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:
|
||||||
|
if cmd == "avj":
|
||||||
|
continue
|
||||||
record = True
|
record = True
|
||||||
for start in ["p", "/", "s"]:
|
for start in ["p", "/", "s"]:
|
||||||
if cmd.strip('"').startswith(start):
|
if cmd.strip('"').startswith(start):
|
||||||
|
@ -306,20 +317,25 @@ with open(r2_script_path, "w") as of:
|
||||||
|
|
||||||
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
|
||||||
|
)
|
||||||
|
|
68
tools/analyze_read_trace.py
Normal file
68
tools/analyze_read_trace.py
Normal 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
57
tools/binvis.py
Normal 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
149
tools/dissect_net.py
Normal 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
46
tools/packed.ksy
Normal 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
122
tools/parse_LFVF.py
Normal 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
117
tools/parse_chunked.py
Normal 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
255
tools/parse_chunked_new.py
Normal 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
31
tools/rbingrep.py
Normal 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
50
tools/render_ai_path.py
Normal 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
27
tools/save_to_json.py
Normal 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)
|
|
@ -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
45
tools/server.py
Normal 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
4
tools/test.bv
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<
|
||||||
|
magic s 4
|
||||||
|
size I
|
||||||
|
data n $size
|
Loading…
Reference in a new issue