forked from ReScrap/ScrapHacks
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/src/D3D8_VMT.hpp
|
||||
.vscode/c_cpp_properties.json
|
||||
tools/*.log
|
||||
frida/*.mp
|
108
NOTES.md
108
NOTES.md
|
@ -1,6 +1,6 @@
|
|||
# Infos
|
||||
|
||||
- Engine: ScrapEngine
|
||||
- Engine: ScrapEngine/Mercury Engine
|
||||
- Ingame Scripting Language: Python 1.5.2
|
||||
- Interesting memory locations and functions are noted in `config.yml`
|
||||
|
||||
|
@ -8,6 +8,8 @@
|
|||
|
||||
- `-console`: open external console window on start
|
||||
- `-wideWindow`: start game in widescreen mode
|
||||
- `-dedicated`: start in mutliplayer dedicated server mode (needs to be used with `-server`)
|
||||
- `-server`: start in multiplayer server mode
|
||||
|
||||
# Functions identified:
|
||||
|
||||
|
@ -23,11 +25,11 @@
|
|||
|
||||
## External Console (Scenegraph Debugging?) (Handler @ `0x5f9520`):
|
||||
|
||||
* `listar luces`
|
||||
* `listar`
|
||||
* `arbol` (Patch Scrap.exe@offset 0x314bc9 replace 0x20 with 0x00 (or just type `arbol ` with a space at the end))
|
||||
* `mem`
|
||||
* `ver uniones`
|
||||
* `listar luces` List lights in scene
|
||||
* `listar` list models in scene
|
||||
* `arbol <model_name>` show details for model
|
||||
* `mem` (doesn't do anything?)
|
||||
* `ver uniones`
|
||||
* Easter Eggs:
|
||||
* `imbecil`
|
||||
* `idiota`
|
||||
|
@ -67,12 +69,11 @@ unsigned long hash(const unsigned char *s)
|
|||
|
||||
## Other Functions:
|
||||
|
||||
Check `r2_analyze.py` for full list
|
||||
Check `config.yml` for full list
|
||||
|
||||
## File Index struct @ `0x7fcbec`
|
||||
|
||||
```cpp
|
||||
|
||||
struct FileEntry {
|
||||
uint32_t offset;
|
||||
uint32_t size;
|
||||
|
@ -87,7 +88,7 @@ struct FileIDX {
|
|||
};
|
||||
```
|
||||
|
||||
## Packed Index struct (array @ `0x7fc1b0`)
|
||||
## Packed Index struct (array of 0x80 entries @ `0x7fc1b0`)
|
||||
```cpp
|
||||
struct PackedIDX {
|
||||
void** VMT;
|
||||
|
@ -111,7 +112,10 @@ struct CPP_Callback {
|
|||
}
|
||||
```
|
||||
|
||||
## Game engine Variables Pointer @ `0x7FBE4C`
|
||||
|
||||
## Game engine Variables Hashtable @ `0x7fbe50`
|
||||
|
||||
## Game engine Variables @ `0x7fbe4c`
|
||||
|
||||
Structure:
|
||||
|
||||
|
@ -130,20 +134,21 @@ struct GameVar {
|
|||
|
||||
Types
|
||||
|
||||
| Value | Type |
|
||||
| ------ | ----------------------- |
|
||||
| `0x10` | const char* |
|
||||
| `0x20` | int32_t |
|
||||
| `0x30` | User Control Definition |
|
||||
| `0x40` | float |
|
||||
| `0x60` | Callback function |
|
||||
| Value | Type |
|
||||
|-------|-----------------|
|
||||
| `0x1` | const char* |
|
||||
| `0x2` | int32_t |
|
||||
| `0x3` | List of Defines |
|
||||
| `0x4` | float |
|
||||
| `0x5` | function |
|
||||
| `0x6` | Script function |
|
||||
|
||||
## Game World/State Pointer @ `0x7fe944`
|
||||
|
||||
Points to World struct
|
||||
|
||||
| Offset | Type | Description |
|
||||
| ------ | ------------------------ | -------------------------------------- |
|
||||
|--------|--------------------------|----------------------------------------|
|
||||
| 0x0000 | `void**` | Virtual Method Table |
|
||||
| 0x0004 | `uint32_t` | Size of Entity Hashtable |
|
||||
| 0x0008 | `void**` | Pointer to Entity Hashtable |
|
||||
|
@ -172,6 +177,15 @@ Points to World struct
|
|||
| 0x2238 | `???` | Used in `World_Init` |
|
||||
| 0x2254 | `float` | Used in `World_Init` |
|
||||
|
||||
## cPyEntity structure
|
||||
|
||||
| Offset | Type | Description |
|
||||
|--------|----------|----------------------|
|
||||
| 0x0000 | `void**` | Virtual Method Table |
|
||||
| 0x0004 | `char*` | Name |
|
||||
| 0x0008 | `void*` | ??? |
|
||||
|
||||
|
||||
## Entity Hash Table
|
||||
|
||||
Hash-function used: [PJW](https://en.wikipedia.org/wiki/PJW_hash_function) (Same parameters as the example implementation)
|
||||
|
@ -189,7 +203,7 @@ struct HT_Entry {
|
|||
Data format:
|
||||
|
||||
| Offset | Type | Description |
|
||||
| ------ | ------------- | ------------------------ |
|
||||
|--------|---------------|--------------------------|
|
||||
| 0x0 | `void**` | Virtual Method Table (?) |
|
||||
| 0x4 | `const char*` | name as string |
|
||||
| 0x14 | `void*` | pointer to self (why?) |
|
||||
|
@ -204,21 +218,57 @@ Attributes:
|
|||
- `OnDeath`
|
||||
- `OnDamage`
|
||||
|
||||
# File Formats
|
||||
# Netplay protocol (WIP)
|
||||
|
||||
## .packed File Format:
|
||||
Game Info Packet
|
||||
|
||||
```
|
||||
Header:
|
||||
"BFPK\0\0\0\0"
|
||||
Int32ul: number of files
|
||||
for each file:
|
||||
Int32ul: path length
|
||||
String: path
|
||||
Int32ul: size
|
||||
Int32ul: offset in file
|
||||
Server 'B':FZ (0/10) Ver 1.0 at 192.168.99.1:28086
|
||||
[0-3] header/ID?
|
||||
[4-5] port (16-bit)
|
||||
[6-7] max_players (16-bit)
|
||||
[8-9] curr_player (16-bit)
|
||||
[10-x] server name (char*)
|
||||
|
||||
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
|
||||
0019fdc0 ba ce 00 01 b6 6d 0a 00 00 00 42 00 30 fe 19 00 .....m....B.0...
|
||||
0019fdd0 ff ff ff ff 27 2b b3 9b c7 3e bb 00 9c af 29 00 ....'+...>....).
|
||||
0019fde0 db 69 00 00 00 00 00 00 00 00 44 65 61 74 68 4d .i........DeathM
|
||||
0019fdf0 61 74 63 68 00 00 00 00 ff ff 46 5a 00 4a 91 f0 atch......FZ.J..
|
||||
0019fe00 92 8b 57 4e 7f 00 00 00 10 21 fe 38 0d ae 00 00 ..WN.....!.8....
|
||||
0019fe10 f0 ce f3 36 a0 e8 0b 77 a0 e8 ...6...w..
|
||||
```
|
||||
|
||||
|
||||
Player Join Packet
|
||||
|
||||
```
|
||||
[0-3] header/ID?
|
||||
[6-x] Player name
|
||||
|
||||
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
|
||||
09c9dfe8 7f 47 00 00 00 0e 55 6e 6e 61 6d 65 64 20 50 6c .G....Unnamed Pl
|
||||
09c9dff8 61 79 65 72 06 53 42 6f 73 73 31 b9 00 07 50 5f ayer.SBoss1...P_
|
||||
09c9e008 42 65 74 74 79 06 4d 42 4f 53 53 31 06 4d 42 4f Betty.MBOSS1.MBO
|
||||
09c9e018 53 53 31 00 00 10 30 2c 31 35 2c 30 2c 30 2c 31 SS1...0,15,0,0,1
|
||||
09c9e028 35 2c 31 35 2c 31 02 00 00 00 5,15,1....
|
||||
```
|
||||
|
||||
| Message | Description |
|
||||
|------------------------------------------|-------------------------------------------------------------------|
|
||||
| `5c68625c32383230395c73637261706c616e64` | "Scrapland Server" announcement broadcast (`\hb\28209\scrapland`) |
|
||||
| `7f01000007` | Retrieve Game info |
|
||||
| `48423d35323932322c3235363a323830383600` | Connection Information (`HB=52922,256:28086`) |
|
||||
|
||||
# [Notes](NOTES.md)
|
||||
|
||||
## File Formats
|
||||
|
||||
- [Chunked](file_formats/chunked.md)
|
||||
- [Packed](file_formats/packed.md)
|
||||
- [AI Pathfinding Graph](file_formats/ai_path.md)
|
||||
|
||||
|
||||
# Virtual Method Tables:
|
||||
|
||||
check `r2_analyze.py` for full list
|
||||
|
|
37
README.md
37
README.md
|
@ -1,13 +1,24 @@
|
|||
# Scrapland Reverse Engineering notes and tools
|
||||
|
||||
## Scripts:
|
||||
* `parse_save.py`: Dumps information extracted from Save file
|
||||
|
||||
## Note!
|
||||
|
||||
All memory addresses are only valid for an unprotected `Scrap.exe` v1.0 with a SHA1 checksum of `d2dde960e8eca69d60c2e39a439088b75f0c89fa` , other version will crash if the memory offsets don't match and you try to inject ScrapHacks
|
||||
|
||||
[Computer Bild Spiele Issue 2006/08](https://archive.org/download/cbs-2006-08-coverdisc/) Contains a full version of the game which was used as the basis for this project
|
||||
|
||||
## Scripts
|
||||
|
||||
* `tools/rbingrep.py`: Search for pattern in all files and generate radare2 script to find all references (currently configured to search for chunked file section headers)
|
||||
* `frida/`: Scripts for use with Frida
|
||||
* `parse_chunked.py`: WIP Parser for the game's chunked data format (Models, Animations, Maps)
|
||||
* `save_to_json.py`: Convert game save to JSON
|
||||
* `scrapper.py`: Extractor and Repacker for *.packed files, needs the `construct` and `tqdm` python modules and python 3.x
|
||||
- Run `scrapper.py -h` for help
|
||||
* `r2_analyze.py`: uses radare2 to parse and label a lot of interesting stuff in the `Scrap.exe` binary
|
||||
* `lib/dbg.py`: general Script for poking around inside the game's scripting system
|
||||
- Run `import dbg` inside the Game's Console,
|
||||
this will load all builtin modules and enable godmode
|
||||
- Run `import dbg;dbg.init()` inside the Game's Console,
|
||||
this will load all builtin modules, ScrapHacks and enable godmode
|
||||
- The dbg module also enables writing to the ingame console using `print <var>`
|
||||
and defines two global functions s_write() and e_write() for writing to the Ingame Console's Stdout and Stderr Stream
|
||||
- `dbg.menu()` Displays the Game's built in Debug Menu (doesn't work properly)
|
||||
|
@ -24,9 +35,15 @@ WIP Memory hacking library
|
|||
|
||||
# Tools used:
|
||||
|
||||
- [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/)
|
||||
- [IDA](https://www.hex-rays.com/products/ida/index.shtml) and [x32dbg](https://x64dbg.com/)
|
||||
- [Reclass.NET](https://github.com/ReClassNET/ReClass.NET)
|
||||
- [HxD](https://mh-nexus.de/en/hxd/)
|
||||
- [Kaitai Struct](http://kaitai.io/)
|
||||
- [Radare2](https://www.radare.org/) + [Cutter](https://cutter.re/)
|
||||
- Binary parsing:
|
||||
- [HxD](https://mh-nexus.de/en/hxd/) for initial file analysis
|
||||
- [Python 3](https://python.org/) + [Construct](https://construct.readthedocs.io/en/latest/) for binary parsing
|
||||
- [Kaitai Struct](http://kaitai.io/) for binary parsing
|
||||
- Static analysis:
|
||||
- [IDA](https://www.hex-rays.com/products/ida/index.shtml) initialy used, later replaced by radare2 and Cutter
|
||||
- [radare2](https://www.radare.org/)
|
||||
- [Cutter](https://cutter.re/)
|
||||
- Dynamic analysis:
|
||||
- [x64dbg](https://x64dbg.com/) for dynamic analysis
|
||||
- [Reclass.NET](https://github.com/ReClassNET/ReClass.NET) to analyze structures and classes in memory
|
||||
- [Frida](https://frida.re/) for tracing and instrumenting functions
|
3
ScrapHacks/.vscode/c_cpp_properties.json
vendored
3
ScrapHacks/.vscode/c_cpp_properties.json
vendored
|
@ -17,8 +17,7 @@
|
|||
"cStandard": "c11",
|
||||
"cppStandard": "c++17",
|
||||
"intelliSenseMode": "msvc-x86",
|
||||
"configurationProvider": "vector-of-bool.cmake-tools",
|
||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
||||
"configurationProvider": "vector-of-bool.cmake-tools"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
|
|
8
ScrapHacks/.vscode/settings.json
vendored
8
ScrapHacks/.vscode/settings.json
vendored
|
@ -78,5 +78,11 @@
|
|||
"xtr1common": "cpp",
|
||||
"xtree": "cpp",
|
||||
"xutility": "cpp"
|
||||
}
|
||||
},
|
||||
"cmake.generator": "NMake Makefiles",
|
||||
"cmake.preferredGenerators": [
|
||||
"NMake Makefiles",
|
||||
"Ninja",
|
||||
"Unix Makefiles"
|
||||
]
|
||||
}
|
21
ScrapHacks/.vscode/tasks.json
vendored
Normal file
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_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||
|
||||
project(ScrapHacks
|
||||
VERSION 1.0
|
||||
DESCRIPTION "Scrapland memory hacking library"
|
||||
|
@ -13,6 +12,8 @@ if(NOT IS_ABSOLUTE "${SCRAPLAND_DIR}" OR NOT EXISTS "${SCRAPLAND_DIR}")
|
|||
message(FATAL_ERROR "Scrapland installation folder not found!")
|
||||
endif()
|
||||
|
||||
message(STATUS "Scrapland found at ${SCRAPLAND_DIR}")
|
||||
|
||||
message(STATUS "Checking Scrap.exe hash")
|
||||
file(SHA1 "${SCRAPLAND_DIR}/Bin/Scrap.exe" SCRAP_EXE_HASH)
|
||||
|
||||
|
@ -20,14 +21,26 @@ if(NOT ${SCRAP_EXE_HASH} STREQUAL "d2dde960e8eca69d60c2e39a439088b75f0c89fa")
|
|||
message(FATAL_ERROR "Scrap.exe hash miss match!")
|
||||
endif()
|
||||
|
||||
# ==============================
|
||||
# "${SCRAPLAND_DIR}/Bin/Scrap.exe"
|
||||
add_custom_target(
|
||||
run
|
||||
COMMAND "Scrap.exe"
|
||||
WORKING_DIRECTORY "${SCRAPLAND_DIR}/Bin"
|
||||
VERBATIM
|
||||
)
|
||||
add_dependencies(run install)
|
||||
# ==============================
|
||||
|
||||
set(COMPONENT "ScrapHacks")
|
||||
set(FETCHCONTENT_QUIET 0)
|
||||
set(CMAKE_BUILD_TYPE "RelMinSize")
|
||||
set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}")
|
||||
set(ASMJIT_EMBED true)
|
||||
set(ASMTK_EMBED true)
|
||||
set(ZYDIS_BUILD_TOOLS false)
|
||||
set(ZYDIS_BUILD_EXAMPLES false)
|
||||
|
||||
set(ASMJIT_EMBED ON CACHE INTERNAL "")
|
||||
set(ASMTK_EMBED ON CACHE INTERNAL "")
|
||||
set(ZYDIS_BUILD_TOOLS OFF CACHE INTERNAL "")
|
||||
set(ZYDIS_BUILD_EXAMPLES OFF CACHE INTERNAL "")
|
||||
set(toml11_BUILD_TEST OFF CACHE INTERNAL "")
|
||||
|
||||
if(WIN32)
|
||||
if(MSVC)
|
||||
|
@ -44,54 +57,67 @@ endif(WIN32)
|
|||
|
||||
include(FetchContent)
|
||||
|
||||
|
||||
FetchContent_Declare(
|
||||
Python
|
||||
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
|
||||
URL https://www.python.org/ftp/python/src/py152.tgz
|
||||
URL_HASH SHA1=2d648d07b1aa1aab32a3a24851c33715141779b9
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
DirectX
|
||||
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
|
||||
URL
|
||||
https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip
|
||||
URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312)
|
||||
|
||||
FetchContent_MakeAvailable(DirectX)
|
||||
URL https://archive.org/download/DirectX.8.0a.SDK_includes_libs_only/DirectX.8.0a.SDK.zip
|
||||
URL_HASH SHA1=39f168336d0df92ff14d62d5e3aef1b9e3191312
|
||||
)
|
||||
|
||||
FetchContent_Declare(
|
||||
ASM_JIT
|
||||
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
|
||||
GIT_REPOSITORY git@github.com:asmjit/asmjit.git
|
||||
GIT_SHALLOW true
|
||||
GIT_PROGRESS true
|
||||
INSTALL_COMMAND ""
|
||||
CONFIGURE_COMMAND ""
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(ASM_JIT)
|
||||
|
||||
FetchContent_Declare(
|
||||
ASM_TK
|
||||
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
|
||||
GIT_REPOSITORY git@github.com:asmjit/asmtk.git
|
||||
GIT_SHALLOW true
|
||||
GIT_PROGRESS true
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(ASM_TK)
|
||||
|
||||
set(ASMJIT_DIR ${asm_jit_SOURCE_DIR})
|
||||
|
||||
include(${asm_tk_SOURCE_DIR}/CMakeLists.txt)
|
||||
|
||||
|
||||
FetchContent_Declare(
|
||||
Zydis
|
||||
PREFIX ${CMAKE_CURRENT_BINARY_DIR}
|
||||
GIT_REPOSITORY git@github.com:zyantific/zydis.git
|
||||
GIT_SHALLOW true
|
||||
GIT_PROGRESS true
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
|
||||
# FetchContent_MakeAvailable(Zydis)
|
||||
FetchContent_MakeAvailable(Python DirectX ASM_JIT ASM_TK Zydis)
|
||||
|
||||
include_directories(AFTER ${directx_SOURCE_DIR}/8.0/include/ ${ASMTK_INCLUDE_DIRS} ${ASMJIT_INCLUDE_DIRS})
|
||||
set(ASMJIT_DIR ${asm_jit_SOURCE_DIR})
|
||||
|
||||
include(${asm_tk_SOURCE_DIR}/CMakeLists.txt)
|
||||
|
||||
|
||||
message(STATUS "Python 1.5.2: ${python_SOURCE_DIR}")
|
||||
message(STATUS "DX8: ${directx_SOURCE_DIR}")
|
||||
message(STATUS "Zydis: ${zydis_SOURCE_DIR}")
|
||||
|
||||
include_directories(AFTER
|
||||
${directx_SOURCE_DIR}/8.0/include/
|
||||
${python_SOURCE_DIR}/Include/
|
||||
${ASMTK_INCLUDE_DIRS}
|
||||
${ASMJIT_INCLUDE_DIRS}
|
||||
${toml_SOURCE_DIR}
|
||||
)
|
||||
link_directories(AFTER ${directx_SOURCE_DIR}/8.0/lib/)
|
||||
|
||||
find_package(Python3 3.6 REQUIRED COMPONENTS Interpreter)
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/D3D8_VMT.hpp
|
||||
|
@ -111,7 +137,10 @@ add_library(ScrapHack SHARED
|
|||
${ASMJIT_SRC}
|
||||
)
|
||||
|
||||
|
||||
set_target_properties(ScrapHack PROPERTIES SUFFIX ".pyd")
|
||||
set_target_properties(ScrapHack PROPERTIES PREFIX "_")
|
||||
|
||||
add_dependencies(ScrapHack D3D8_VMT)
|
||||
# add_dependencies(ScrapHack Python152)
|
||||
# add_dependencies(ScrapHack Python152_Bin)
|
||||
|
@ -119,10 +148,13 @@ target_link_libraries(ScrapHack
|
|||
d3d8
|
||||
d3dx8
|
||||
dxerr8
|
||||
gdiplus
|
||||
# gdiplus
|
||||
# PYTHON15
|
||||
# Zydis
|
||||
Zydis
|
||||
legacy_stdio_definitions)
|
||||
|
||||
install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib)
|
||||
target_compile_features(ScrapHack PUBLIC cxx_std_11)
|
||||
install(TARGETS ScrapHack RUNTIME DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/python/ScrapHack.py DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/python/dbg.py DESTINATION ${SCRAPLAND_DIR}/lib COMPONENT ScrapHacks)
|
||||
|
||||
target_compile_features(ScrapHack PUBLIC cxx_std_17)
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
## Features
|
||||
|
||||
- read and write memory
|
||||
- disassemble memory (using zydis)
|
||||
- change DirectX state
|
||||
- Draw DirectX overlay (still need to make a useful overlay)
|
||||
- Dump various data structures to the console
|
||||
- Assemble and execute code on the fly
|
||||
- Assemble and execute code on the fly (using asmtk)
|
||||
- Can be controlled via keyboard shortcuts (TODO: allow defining own shortcuts for commands)
|
||||
|
||||
## Prerequisites
|
||||
|
@ -30,7 +31,34 @@ This will find the Games's installation folder, verify that the version you have
|
|||
- type `import ScrapHack`
|
||||
- type `$help`
|
||||
|
||||
## Notes
|
||||
## Config file keys
|
||||
|
||||
(this has only been tested with a (cracked/unpacked/de-obfuscated) `Scrap.exe` v1.0 with a SHA1 checksum of `d2dde960e8eca69d60c2e39a439088b75f0c89fa` , other version will crash if the memory offsets don't match)
|
||||
- patches.asm: map of address->list of assembly instructions
|
||||
- patches.hex: map of address->hex bytes
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"patches": {
|
||||
"hex": {
|
||||
"0xDEADBEEF": "BADFOODDEADFEED"
|
||||
},
|
||||
"asm": {
|
||||
"0xBADF00D": [
|
||||
"pushad",
|
||||
"call 0xf00dbabe",
|
||||
"popad",
|
||||
"mov eax, 0x42",
|
||||
"ret"
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Third-Party components used
|
||||
|
||||
- Zydis disassembler
|
||||
- asmJIT/asmTK assembler
|
||||
- nlohmann/json JSON-parser
|
|
@ -5,6 +5,15 @@ if "%VSINSTALLDIR%"=="" (
|
|||
call "%%i" x86
|
||||
)
|
||||
)
|
||||
if not exist build cmake -G"NMake Makefiles" -B build
|
||||
cmake --build build --target install
|
||||
if "%VSINSTALLDIR%"=="" (
|
||||
echo "VSINSTALLDIR" not set something is wrong!
|
||||
) else (
|
||||
if not exist build cmake -G"NMake Makefiles" -B build
|
||||
if "%1"=="--run" (
|
||||
cmake --build build --target run
|
||||
) else (
|
||||
cmake --build build --target install
|
||||
)
|
||||
)
|
||||
|
||||
endlocal
|
|
@ -147,6 +147,13 @@ void unhook_d3d8() {
|
|||
hooked=false;
|
||||
}
|
||||
|
||||
map<size_t,void*> *dx_hooks = new map<size_t,void*>({
|
||||
{VMT_IDirect3DDevice8::m_EndScene,H_EndScene},
|
||||
{VMT_IDirect3DDevice8::m_BeginScene,H_BeginScene},
|
||||
{VMT_IDirect3DDevice8::m_DrawIndexedPrimitive,H_DrawIndexedPrimitive},
|
||||
{VMT_IDirect3DDevice8::m_SetLight,H_SetLight},
|
||||
});
|
||||
|
||||
void hook_d3d8() {
|
||||
if (hooked) {
|
||||
return;
|
||||
|
@ -162,11 +169,9 @@ void hook_d3d8() {
|
|||
hFont = CreateFontA(15, 0, 0, 0, FW_EXTRABOLD, 0, 0, 0, ANSI_CHARSET, 0, 0,
|
||||
0, 0, "Lucida Console");
|
||||
hBrush = CreateSolidBrush(D3DCOLOR_ARGB(25, 0, 0, 0));
|
||||
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_EndScene], H_EndScene);
|
||||
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_BeginScene], H_BeginScene);
|
||||
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_DrawIndexedPrimitive],
|
||||
H_DrawIndexedPrimitive);
|
||||
Hook::addr(GetVTable(dev)[VMT_IDirect3DDevice8::m_SetLight], H_SetLight);
|
||||
for (auto h: *dx_hooks) {
|
||||
Hook::addr(GetVTable(dev)[h.first], h.second);
|
||||
}
|
||||
hooked=true;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
/*
|
||||
|
||||
vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) {
|
||||
using namespace asmjit;
|
||||
vector<uint8_t> ret;
|
||||
JitRuntime rt;
|
||||
CodeHolder code;
|
||||
CodeInfo ci=rt.codeInfo();
|
||||
|
@ -23,9 +24,15 @@ vector<uint8_t> make_trampoline(uintptr_t orig,uintptr_t hook) {
|
|||
code.resolveUnresolvedLinks();
|
||||
code.relocateToBase(orig);
|
||||
size_t code_size=code.sectionById(0)->buffer().size();
|
||||
code.copyFlattenedData((void*)orig, code_size, CodeHolder::kCopyWithPadding);
|
||||
uint8_t* buffer=new uint8_t[code_size];
|
||||
code.copyFlattenedData((void*)buffer, code_size, CodeHolder::kCopyWithPadding);
|
||||
for (size_t i=0;i<code_size;++i) {
|
||||
ret.push_back(buffer[i]);
|
||||
}
|
||||
delete buffer;
|
||||
return ret;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
class Hook {
|
||||
private:
|
||||
|
@ -33,29 +40,32 @@ class Hook {
|
|||
void *orig;
|
||||
void *detour;
|
||||
bool enabled;
|
||||
uint8_t orig_bytes[6];
|
||||
uint8_t jmp_bytes[6];
|
||||
uint8_t *orig_bytes;
|
||||
uint8_t *jmp_bytes;
|
||||
size_t size;
|
||||
static map<uintptr_t, shared_ptr<Hook>> hooks;
|
||||
|
||||
public:
|
||||
Hook(void *func, void *detour) {
|
||||
// TODO: build jmp_bytes using asmjit
|
||||
uintptr_t dest = reinterpret_cast<uintptr_t>(detour);
|
||||
uintptr_t src = reinterpret_cast<uintptr_t>(func);
|
||||
this->orig = func;
|
||||
this->detour = detour;
|
||||
this->jmp_bytes[0] = 0x68; // push
|
||||
this->jmp_bytes[1] = (dest >> 0) & 0xff;
|
||||
this->jmp_bytes[2] = (dest >> 8) & 0xff;
|
||||
this->jmp_bytes[3] = (dest >> 16) & 0xff;
|
||||
this->jmp_bytes[4] = (dest >> 24) & 0xff;
|
||||
this->jmp_bytes[5] = 0xC3; // ret
|
||||
VirtualQuery(func, &mbi, sizeof(mbi));
|
||||
vector<uint8_t> code = make_trampoline(src,dest);
|
||||
this->orig_bytes = new uint8_t[code.size()];
|
||||
this->jmp_bytes = new uint8_t[code.size()];
|
||||
this->size = code.size();
|
||||
this->enabled = false;
|
||||
uint8_t* func_b = reinterpret_cast<uint8_t*>(this->orig);
|
||||
for (size_t i=0;i<this->size;++i) {
|
||||
this->orig_bytes[i]=func_b[i];
|
||||
this->jmp_bytes[i]=code[i];
|
||||
}
|
||||
VirtualQuery(this->orig, &mbi, sizeof(mbi));
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE,
|
||||
&mbi.Protect);
|
||||
memcpy(this->orig_bytes, this->orig, 1 + 4 + 1);
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
|
||||
this->enabled = false;
|
||||
cout<<"Constructed hook from "<<func<<" to "<<detour<<", size: " << this->size<<endl;
|
||||
}
|
||||
|
||||
~Hook() {
|
||||
|
@ -64,6 +74,11 @@ class Hook {
|
|||
this->disable();
|
||||
}
|
||||
|
||||
|
||||
static void addr(uintptr_t _addr, void *detour) {
|
||||
Hook::addr(reinterpret_cast<void*>(_addr),detour);
|
||||
}
|
||||
|
||||
static void addr(void *addr, void *detour) {
|
||||
cout << "Hooking: [" << addr << " -> " << detour << "]" << endl;
|
||||
uintptr_t key = reinterpret_cast<uintptr_t>(detour);
|
||||
|
@ -105,23 +120,23 @@ class Hook {
|
|||
|
||||
void disable() {
|
||||
if (this->enabled) {
|
||||
// cout << "Disabling: [" << this->orig << " <- " << this->detour <<
|
||||
// "]"
|
||||
// << endl;
|
||||
cout << "Disabling: [" << this->orig << " <- " << this->detour <<
|
||||
"]"
|
||||
<< endl;
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize,
|
||||
PAGE_EXECUTE_READWRITE, NULL);
|
||||
memcpy(this->orig, this->orig_bytes, 1 + 4 + 1);
|
||||
memcpy(this->orig, this->orig_bytes, this->size);
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
|
||||
this->enabled = false;
|
||||
}
|
||||
}
|
||||
void enable() {
|
||||
if (!this->enabled) {
|
||||
// cout << "Enabling: [" << this->orig << " -> " << this->detour <<
|
||||
// "]" << endl;
|
||||
cout << "Enabling: [" << this->orig << " -> " << this->detour <<
|
||||
"]" << endl;
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize,
|
||||
PAGE_EXECUTE_READWRITE, NULL);
|
||||
memcpy(this->orig, this->jmp_bytes, 1 + 4 + 1);
|
||||
memcpy(this->orig, this->jmp_bytes, this->size);
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
|
||||
this->enabled = true;
|
||||
}
|
||||
|
|
74
ScrapHacks/src/Py_Mod.hpp
Normal file
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 <map>
|
||||
#include <string>
|
||||
|
||||
// #include <Python.h>
|
||||
#include "Structures.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <string>
|
||||
|
||||
#include <asmtk/asmtk.h>
|
||||
#include <Zydis/Zydis.h>
|
||||
#include "Scrapland.hpp"
|
||||
#include "Util.hpp"
|
||||
|
||||
|
@ -40,17 +41,20 @@ size_t assemble(vector<string> assembly,uint64_t base) {
|
|||
for (string line:assembly) {
|
||||
if (err = p.parse((line+"\n").c_str())) {
|
||||
snprintf(err_msg,1024,"PARSE ERROR: [%s] %08x (%s)\n",line.c_str(), err, DebugUtils::errorAsString(err));
|
||||
cerr<<err_msg<<endl;
|
||||
scrap_log(ERR_COLOR,err_msg);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (err=code.flatten()) {
|
||||
snprintf(err_msg,1024,"FLATTEN ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err));
|
||||
cerr<<err_msg<<endl;
|
||||
scrap_log(ERR_COLOR,err_msg);
|
||||
return 0;
|
||||
}
|
||||
if (err=code.resolveUnresolvedLinks()) {
|
||||
snprintf(err_msg,1024,"RESOLVE ERROR: %08x (%s)\n", err, DebugUtils::errorAsString(err));
|
||||
cerr<<err_msg<<endl;
|
||||
scrap_log(ERR_COLOR,err_msg);
|
||||
return 0;
|
||||
}
|
||||
|
@ -63,6 +67,7 @@ size_t assemble(vector<string> assembly,uint64_t base) {
|
|||
MEMORY_BASIC_INFORMATION mbi;
|
||||
|
||||
if (VirtualQuery((void*)base, &mbi, sizeof(mbi)) == 0) {
|
||||
cerr<<"ERROR: "<< GetLastErrorAsString() <<endl;
|
||||
scrap_log(ERR_COLOR, "ERROR: ");
|
||||
scrap_log(ERR_COLOR, GetLastErrorAsString());
|
||||
scrap_log(ERR_COLOR, "\n");
|
||||
|
@ -70,11 +75,13 @@ size_t assemble(vector<string> assembly,uint64_t base) {
|
|||
};
|
||||
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect))
|
||||
{
|
||||
cerr<<"ERROR: "<< GetLastErrorAsString() <<endl;
|
||||
scrap_log(ERR_COLOR, "ERROR: ");
|
||||
scrap_log(ERR_COLOR, GetLastErrorAsString());
|
||||
scrap_log(ERR_COLOR, "\n");
|
||||
return 0;
|
||||
};
|
||||
cout<<"CODE: "<< hexdump_s((void*)base,buffer.size()) << endl;
|
||||
memcpy((void*)base,buffer.data(),buffer.size());
|
||||
scrap_log(INFO_COLOR,"Code:"+hexdump_s((void*)base,buffer.size()));
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
|
||||
|
@ -85,7 +92,38 @@ size_t asm_size(vector<string> assembly) {
|
|||
return assemble(assembly,0);
|
||||
}
|
||||
|
||||
string disassemble(void* addr, size_t num,bool compact) {
|
||||
stringstream ret;
|
||||
ZyanU64 z_addr = reinterpret_cast<uintptr_t>(addr);
|
||||
ZydisDecoder decoder;
|
||||
ZydisFormatter formatter;
|
||||
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_ADDRESS_WIDTH_32);
|
||||
ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL);
|
||||
|
||||
ZydisDecodedInstruction instruction;
|
||||
while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(&decoder, addr, -1, &instruction)))
|
||||
{
|
||||
char buffer[256];
|
||||
ZydisFormatterFormatInstruction(&formatter, &instruction, buffer, sizeof(buffer), z_addr);
|
||||
if (!compact) {
|
||||
ret<< "[" << std::hex << setfill('0') << setw(8) << z_addr << "]: ";
|
||||
}
|
||||
|
||||
ret << buffer;
|
||||
|
||||
if (compact) {
|
||||
ret<<"; ";
|
||||
} else {
|
||||
ret<<endl;
|
||||
}
|
||||
addr = reinterpret_cast<void*>(z_addr += instruction.length);
|
||||
num--;
|
||||
if (num==0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret.str();
|
||||
}
|
||||
|
||||
struct Command {
|
||||
t_cmd_func func;
|
||||
|
@ -217,6 +255,46 @@ get_protection(void *addr) {
|
|||
return mbi.Protect;
|
||||
}
|
||||
|
||||
void cmd_disassemble(Command* cmd, vector<string> args) {
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
if (args.size()<1) {
|
||||
scrap_log(ERR_COLOR, cmd->usage);
|
||||
scrap_log(ERR_COLOR, "\n");
|
||||
return;
|
||||
}
|
||||
uintptr_t addr = UINTPTR_MAX;
|
||||
size_t size = 0xff;
|
||||
try {
|
||||
addr = stoull(args[0], 0, 16);
|
||||
if (args.size()>1) {
|
||||
size = stoull(args[1]);
|
||||
}
|
||||
} catch (exception e) {
|
||||
scrap_log(ERR_COLOR, "ERROR: ");
|
||||
scrap_log(ERR_COLOR, e.what());
|
||||
scrap_log(ERR_COLOR, "\n");
|
||||
return;
|
||||
}
|
||||
void *mptr = reinterpret_cast<void *>(addr);
|
||||
if (VirtualQuery(mptr, &mbi, sizeof(mbi)) == 0) {
|
||||
scrap_log(ERR_COLOR, "ERROR: ");
|
||||
scrap_log(ERR_COLOR, GetLastErrorAsString());
|
||||
scrap_log(ERR_COLOR, "\n");
|
||||
return;
|
||||
};
|
||||
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE,
|
||||
&mbi.Protect)) {
|
||||
scrap_log(ERR_COLOR, "ERROR: ");
|
||||
scrap_log(ERR_COLOR, GetLastErrorAsString());
|
||||
scrap_log(ERR_COLOR, "\n");
|
||||
return;
|
||||
};
|
||||
string dasm = disassemble(mptr, size, false);
|
||||
scrap_log(INFO_COLOR, dasm);
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
void cmd_exec(Command* cmd, vector<string> args) {
|
||||
void *addr;
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
|
@ -339,7 +417,7 @@ void cmd_read(Command* cmd,vector<string> args) {
|
|||
return;
|
||||
};
|
||||
string hxd = hexdump_s(mptr, size);
|
||||
scrap_log(INFO_COLOR, hxd.c_str());
|
||||
scrap_log(INFO_COLOR, hxd);
|
||||
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, NULL);
|
||||
if (buffer) {
|
||||
free(buffer);
|
||||
|
@ -399,22 +477,26 @@ void cmd_dx8(Command* cmd,vector<string> args) {
|
|||
}
|
||||
|
||||
void cmd_dump_stack(Command* cmd, vector<string> args) {
|
||||
stringstream ret;
|
||||
void** stack=(void**)_AddressOfReturnAddress();
|
||||
cout<<"ESP: "<<stack<<endl;
|
||||
for (size_t n=0;n<0x100;++n) {
|
||||
if (!addr_exists(stack[n])) {
|
||||
continue;
|
||||
}
|
||||
char R=can_read(stack[n]) ? 'R' : ' ';
|
||||
char W=can_write(stack[n]) ? 'W' : ' ';
|
||||
char X=can_execute(stack[n]) ? 'X' : ' ';
|
||||
cout<<"ESP[" << std::hex << setfill('0') << setw(2) << n <<"]: "<<stack[n]<<" "<<R<<W<<X;
|
||||
if (can_read(stack[n])) {
|
||||
cout<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]"<<endl;
|
||||
} else {
|
||||
cout<<endl;
|
||||
bool r,w,x;
|
||||
char R=(r=can_read(stack[n])) ? 'R' : ' ';
|
||||
char W=(w=can_write(stack[n])) ? 'W' : ' ';
|
||||
char X=(x=can_execute(stack[n])) ? 'X' : ' ';
|
||||
ret<< std::hex << setfill('0') << setw(8) << stack+(n*sizeof(void*)) << ": "<<stack[n]<<" "<<R<<W<<X;
|
||||
if (r && !x) {
|
||||
ret<<" [ "<<hexdump_s(stack[n],0xf,true)<<"]";
|
||||
} else if (r && x) {
|
||||
ret<<" [ "<<disassemble(stack[n],5,true)<<"]";
|
||||
}
|
||||
ret<<endl;
|
||||
}
|
||||
scrap_log(INFO_COLOR,ret.str());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -426,7 +508,7 @@ void cmd_dump_py(Command* cmd,vector<string> args) {
|
|||
<< meth.second->ml_meth << endl;
|
||||
}
|
||||
}
|
||||
scrap_log(INFO_COLOR,out.str().c_str());
|
||||
scrap_log(INFO_COLOR,out.str());
|
||||
}
|
||||
|
||||
void cmd_dump_vars(Command* cmd, vector<string> args) {
|
||||
|
@ -437,7 +519,7 @@ void cmd_dump_vars(Command* cmd, vector<string> args) {
|
|||
out<<var->name<< "[" <<std::hex <<(uint16_t)var->type <<","<< (uint16_t)var->subtype << std::dec << "]: " << var->desc<<" ("<<var->value<<", "<<var->default<<")"<<endl;
|
||||
var=var->next;
|
||||
}
|
||||
scrap_log(INFO_COLOR,out.str().c_str());
|
||||
scrap_log(INFO_COLOR,out.str());
|
||||
}
|
||||
|
||||
void cmd_dump_ents(Command* cmd,vector<string> args) {
|
||||
|
@ -446,7 +528,7 @@ void cmd_dump_ents(Command* cmd,vector<string> args) {
|
|||
dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS), &out);
|
||||
out << "Entity Lists:" << endl;
|
||||
dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS), &out);
|
||||
scrap_log(INFO_COLOR,out.str().c_str());
|
||||
scrap_log(INFO_COLOR,out.str());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -478,10 +560,14 @@ void cmd_disable_overlay(Command* cmd,vector<string> args) {
|
|||
|
||||
void cmd_print_alarm(Command* cmd,vector<string> args) {
|
||||
stringstream out;
|
||||
float *alarm = ptr<float>(P_WORLD, O_ALARM);
|
||||
float *alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW);
|
||||
out << "Alarm: " << alarm[0] << " + " << alarm_grow[0] << endl;
|
||||
scrap_log(INFO_COLOR,out.str().c_str());
|
||||
float alarm = ptr<float>(P_WORLD, O_ALARM)[0];
|
||||
float alarm_grow = ptr<float>(P_WORLD, O_ALARM_GROW)[0];
|
||||
if (alarm_grow<0) {
|
||||
out << "Alarm: " << alarm << " - " << alarm_grow << endl;
|
||||
} else {
|
||||
out << "Alarm: " << alarm << " + " << alarm_grow << endl;
|
||||
}
|
||||
scrap_log(INFO_COLOR,out.str());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -528,6 +614,7 @@ void cmd_asm(Command* cmd, vector<string> args) {
|
|||
assemble(split(code,';'),buffer_addr);
|
||||
}
|
||||
|
||||
|
||||
void cmd_help(Command* cmd,vector<string> args);
|
||||
|
||||
static REPL* repl=new REPL(
|
||||
|
@ -538,6 +625,7 @@ static REPL* repl=new REPL(
|
|||
{"exec",new Command(cmd_exec,"Usage: $exec <addr>","Start a new thread at the specified address")},
|
||||
{"asm",new Command(cmd_asm,"Usage: $asm [addr] <inst1>;<inst2>;...","Assemble instructions at address, if no address is given allocate memory and assemble code into that")},
|
||||
{"stack",new Command(cmd_dump_stack,"Usage: $mem stack","Dump stack contents")},
|
||||
{"dasm",new Command(cmd_disassemble,"Usage: $mem dasm <addr> [num_inst]","Disassemble memory at address")},
|
||||
})},
|
||||
{"unload",new Command(cmd_unload,"Usage: $unload","Unload ScrapHacks")},
|
||||
{"dx8",new Command(cmd_dx8,"Usage: $dx8 <subcommand>","Manipulate DirectX 8 functions and state",{
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
#include <string>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
|
||||
#include <fstream>
|
||||
// #include <Python.h>
|
||||
#include <Windows.h>
|
||||
|
||||
using namespace std;
|
||||
|
@ -18,6 +19,7 @@ using namespace std;
|
|||
#include "Scrapland.hpp"
|
||||
#include "Structures.hpp"
|
||||
#include "Util.hpp"
|
||||
#include "Py_Mod.hpp"
|
||||
|
||||
bool initialized = false;
|
||||
bool running = true;
|
||||
|
@ -28,12 +30,14 @@ void DllUnload();
|
|||
int hooked_console(const char *);
|
||||
void hook_exit();
|
||||
|
||||
void setup_hooks() {
|
||||
Hook::addr(reinterpret_cast<void *>(P_SCRAP_EXIT), hook_exit);
|
||||
Hook::addr(reinterpret_cast<void *>(P_CON_HANDLER), hooked_console);
|
||||
void setup_hooks()
|
||||
{
|
||||
Hook::addr(P_SCRAP_EXIT, hook_exit);
|
||||
Hook::addr(P_CON_HANDLER, hooked_console);
|
||||
}
|
||||
|
||||
void MainLoop() {
|
||||
void MainLoop()
|
||||
{
|
||||
setup_hooks();
|
||||
overlay = true;
|
||||
cout << "[*] Starting main Loop" << endl;
|
||||
|
@ -49,42 +53,56 @@ void MainLoop() {
|
|||
cout << "[ F ] \"Handbrake\" (*Will* crash the game after some time!)"
|
||||
<< endl;
|
||||
|
||||
while (running) {
|
||||
while (running)
|
||||
{
|
||||
Sleep(100);
|
||||
while (key_down('F')) {
|
||||
while (key_down('F'))
|
||||
{
|
||||
scrap_exec("dbg.brake()");
|
||||
}
|
||||
if (key_down_norepeat(VK_F3)) {
|
||||
if (key_down_norepeat(VK_F3))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (key_down_norepeat(VK_F7)) {
|
||||
if (key_down_norepeat(VK_F7))
|
||||
{
|
||||
int32_t *money = ptr<int32_t>(P_WORLD, O_MONEY);
|
||||
money[0] = 0x7fffffff;
|
||||
}
|
||||
|
||||
if (key_down_norepeat(VK_F9)) {
|
||||
if (key_down_norepeat(VK_F8))
|
||||
{
|
||||
cout << "Not yet implemented" << endl;
|
||||
}
|
||||
|
||||
if (key_down_norepeat(VK_F9))
|
||||
{
|
||||
cout << "Entities:" << endl;
|
||||
dump_ht(ptr<HashTable<Entity>>(P_WORLD, O_ENTS));
|
||||
cout << "Entity Lists:" << endl;
|
||||
dump_ht(ptr<HashTable<EntityList>>(P_WORLD, O_ENTLISTS));
|
||||
}
|
||||
if (key_down_norepeat(VK_F10)) {
|
||||
if (key_down_norepeat(VK_F10))
|
||||
{
|
||||
scrap_exec("dbg.settrace()");
|
||||
}
|
||||
}
|
||||
FreeLibraryAndExitThread(hMod, 0);
|
||||
}
|
||||
|
||||
void InitConsole() {
|
||||
void InitConsole()
|
||||
{
|
||||
char me[1024];
|
||||
GetModuleFileName(hMod, me, 1024);
|
||||
GetModuleFileNameA(hMod, me, 1024);
|
||||
SetupConsole(me);
|
||||
}
|
||||
|
||||
int hooked_console(const char *cmd) {
|
||||
int hooked_console(const char *cmd)
|
||||
{
|
||||
typedef decltype(&hooked_console) t_func;
|
||||
if (cmd[0] == '$') {
|
||||
if (cmd[0] == '$')
|
||||
{
|
||||
handle_command(++cmd);
|
||||
return 0;
|
||||
}
|
||||
|
@ -93,7 +111,8 @@ int hooked_console(const char *cmd) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
void hook_exit() {
|
||||
void hook_exit()
|
||||
{
|
||||
typedef decltype(&hook_exit) t_func;
|
||||
shared_ptr<Hook> hook = Hook::get(hook_exit);
|
||||
DllUnload();
|
||||
|
@ -102,7 +121,8 @@ void hook_exit() {
|
|||
return;
|
||||
}
|
||||
|
||||
void DllInit(HMODULE mod) {
|
||||
void DllInit(HMODULE mod)
|
||||
{
|
||||
hMod = mod;
|
||||
char mfn[1024];
|
||||
GetModuleFileNameA(0, mfn, 1024);
|
||||
|
@ -119,36 +139,23 @@ void DllInit(HMODULE mod) {
|
|||
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MainLoop, NULL, 0, 0);
|
||||
cout << "[*] Starting message pump" << endl;
|
||||
MSG msg;
|
||||
while (GetMessage(&msg, NULL, 0, 0)) {
|
||||
while (GetMessage(&msg, NULL, 0, 0))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void *H_port_FixupExtension(char *name, char *filename) {
|
||||
cout<<"FixupExtension: "<<name<<": "<<filename<<endl;
|
||||
Hook::drop(H_port_FixupExtension);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *H_PyEval_CallObjectWithKeywords(void *func, void *arg, void *kwarg) {
|
||||
cout<<"PyEval_CallObjectWithKeywords:"<<endl;
|
||||
cout<<"\t func: "<<func<<endl;
|
||||
cout<<"\t arg: "<<arg<<endl;
|
||||
cout<<"\t kwarg: "<<kwarg<<endl;
|
||||
Hook::drop(H_PyEval_CallObjectWithKeywords);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DllPreInit() {
|
||||
void DllPreInit()
|
||||
{
|
||||
Sleep(100);
|
||||
InitConsole();
|
||||
Hook::addr(reinterpret_cast<void *>(0x5a9ca0), H_port_FixupExtension);
|
||||
Hook::addr(reinterpret_cast<void *>(0x5cdb00),
|
||||
H_PyEval_CallObjectWithKeywords);
|
||||
InitPyMod();
|
||||
}
|
||||
|
||||
void DllUnload() {
|
||||
void DllUnload()
|
||||
{
|
||||
SetConsoleCtrlHandler(NULL, false);
|
||||
unhook_d3d8();
|
||||
Hook::clear();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
#include "Structures.hpp"
|
||||
|
||||
#include "Util.hpp"
|
||||
#include <sstream>
|
||||
using namespace std;
|
||||
|
||||
|
@ -17,14 +17,21 @@ using namespace std;
|
|||
#define P_PY_MODS 0x79C698
|
||||
|
||||
// FUNCTION ADDRESSES
|
||||
// ENGINE INTERNALS
|
||||
#define P_CON_HANDLER 0x402190
|
||||
#define P_SCRAP_LOG 0x4134C0
|
||||
#define P_SCRAP_EXEC 0x5a8390
|
||||
#define P_SCRAP_EXIT 0x4010c0
|
||||
#define P_D3DCHECK 0x602a70
|
||||
#define P_D3DDEV 0x852914
|
||||
// PYTHON FUNCTIONS
|
||||
#define P_Py_InitModule 0x5A8FB0
|
||||
#define P_PyArg_ParseTuple 0x5bb9d0
|
||||
#define P_PyBuildValue 0x5a90f0
|
||||
#define P_PyList_New 0x5b3120
|
||||
#define P_PyTuple_New 0x5b91a0
|
||||
#define P_PyList_SetItem 0x5b3240
|
||||
#define P_PyTuple_SetItem 0x5b92a0
|
||||
|
||||
|
||||
#define MSG_COLOR scrap_RGB(128,0,255)
|
||||
|
@ -41,14 +48,20 @@ uint32_t scrap_RGB(uint8_t r,uint8_t g,uint8_t b) {
|
|||
typedef int(_cdecl *t_scrap_log)(unsigned int color, const char *message);
|
||||
typedef int(_cdecl *t_scrap_exec)(const char *code);
|
||||
typedef int(_cdecl *t_PyArg_ParseTuple)(void *PyObj, char *format, ...);
|
||||
typedef void*(_cdecl *t_Py_BuildValue)(char *format, ...);
|
||||
typedef int(_cdecl *t_Py_InitModule)(const char *name, void *methods,
|
||||
const char *doc, void *passthrough,
|
||||
int module_api_version);
|
||||
typedef void*(_cdecl *t_PyList_New)(char *format, ...);
|
||||
typedef void*(_cdecl *t_PyTuple_New)(char *format, ...);
|
||||
typedef void*(_cdecl *t_PyList_SetItem)(char *format, ...);
|
||||
typedef void*(_cdecl *t_PyList_SetItem)(char *format, ...);
|
||||
|
||||
// GLOBAL FUNCTIONS
|
||||
auto scrap_exec = (t_scrap_exec)P_SCRAP_EXEC;
|
||||
auto pyarg_parsetuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple;
|
||||
auto py_initmodule = (t_Py_InitModule)P_Py_InitModule;
|
||||
auto PyArg_ParseTuple = (t_PyArg_ParseTuple)P_PyArg_ParseTuple;
|
||||
auto Py_BuildValue = (t_Py_BuildValue)P_PyBuildValue;
|
||||
auto Py_InitModule = (t_Py_InitModule)P_Py_InitModule;
|
||||
|
||||
int scrap_log(unsigned int color,const char* msg) {
|
||||
return ((t_scrap_log)P_SCRAP_LOG)(color,msg);
|
||||
|
@ -58,7 +71,6 @@ int scrap_log(uint8_t r,uint8_t g,uint8_t b,const char* msg) {
|
|||
return ((t_scrap_log)P_SCRAP_LOG)(scrap_RGB(r,g,b),msg);
|
||||
}
|
||||
|
||||
|
||||
int scrap_log(unsigned int color,string msg) {
|
||||
return ((t_scrap_log)P_SCRAP_LOG)(color,msg.c_str());
|
||||
}
|
||||
|
@ -67,6 +79,12 @@ int scrap_log(uint8_t r,uint8_t g,uint8_t b,string msg) {
|
|||
return ((t_scrap_log)P_SCRAP_LOG)(scrap_RGB(r,g,b),msg.c_str());
|
||||
}
|
||||
|
||||
int scrap_err() {
|
||||
string err("Error: ");
|
||||
err+=GetLastErrorAsString();
|
||||
err+="\n";
|
||||
return scrap_log(ERR_COLOR,err);
|
||||
}
|
||||
|
||||
size_t size_ht(HashTable<EntityList> *ht) {
|
||||
size_t cnt = 0;
|
||||
|
|
|
@ -26,7 +26,7 @@ string GetLastErrorAsString() {
|
|||
size_t m_size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
NULL, errorMessageID, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
||||
(LPSTR)&messageBuffer, 0, NULL);
|
||||
string message(messageBuffer, m_size);
|
||||
LocalFree(messageBuffer);
|
||||
|
|
|
@ -12,9 +12,10 @@ HANDLE hThread = INVALID_HANDLE_VALUE;
|
|||
bool loaded = false;
|
||||
HMODULE mod = nullptr;
|
||||
|
||||
DLL_EXPORT void initScrapHack() {
|
||||
DLL_EXPORT void init_ScrapHack() {
|
||||
DllPreInit();
|
||||
if (!loaded) {
|
||||
Sleep(1000);
|
||||
hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)DllInit, mod,
|
||||
0, 0);
|
||||
CloseHandle(hThread);
|
||||
|
|
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 Menu
|
||||
import sys
|
||||
|
||||
QC = quickconsole
|
||||
MF = MissionsFuncs
|
||||
last_frame = None
|
||||
|
||||
level = 3
|
||||
initialized = 0
|
||||
sys.path.append(".\\pylib\\Lib")
|
||||
sys.path.append(".\\pylib\\Libs")
|
||||
sys.path.append(".\\pylib")
|
||||
|
@ -16,11 +18,18 @@ sys.path.append(".\\pylib")
|
|||
|
||||
def reload():
|
||||
sys.settrace(None)
|
||||
sys.modules['__builtin__'].reload(sys.modules['dbg'])
|
||||
sys.modules['__builtin__'].reload(sys.modules[__name__])
|
||||
|
||||
|
||||
def dgb_info():
|
||||
SScorer.SetLabelText(`last_frame`, Scrap.GetTime() + 0.1)
|
||||
if me:
|
||||
try:
|
||||
dbg_text = str(SVec.Mod(me.Vel))
|
||||
except:
|
||||
dbg_text=""
|
||||
else:
|
||||
dbg_text = ""
|
||||
SScorer.SetLabelText(dbg_text, Scrap.GetTime() + 0.1)
|
||||
Scrap.DeleteScheduledFuncs("dbg.dbg_info")
|
||||
Scrap.DeleteScheduledFuncs("dbg.dbg_info")
|
||||
Scrap.AddScheduledFunc(Scrap.GetTime()+0.1, dgb_info, (), "dbg.dbg_info")
|
||||
|
@ -197,12 +206,11 @@ def helplib():
|
|||
logfile_name = None
|
||||
print "Done!"
|
||||
|
||||
|
||||
def enable_all_conv():
|
||||
try:
|
||||
import CharConversor
|
||||
except ImportError:
|
||||
print("CharConversor not available")
|
||||
# print("CharConversor not available")
|
||||
return
|
||||
CharConversor.ConversionChars = list(CharConversor.ConversionChars)
|
||||
E = Scrap.GetFirst()
|
||||
|
@ -244,20 +252,6 @@ def nuke():
|
|||
E = Scrap.GetEntity(E.NextInSlot)
|
||||
|
||||
|
||||
def test_func():
|
||||
E = Scrap.GetFirst()
|
||||
me = Scrap.UsrEntity(0)
|
||||
while E:
|
||||
if E.Name == me.Name:
|
||||
E = Scrap.GetEntity(E.NextInSlot)
|
||||
try:
|
||||
E.Money=1024*1024*1024
|
||||
# SAI.SetStateVehicle(8,me.Name,E.Name)
|
||||
except:
|
||||
pass
|
||||
E = Scrap.GetEntity(E.NextInSlot)
|
||||
|
||||
|
||||
def become(name):
|
||||
import CharConversor
|
||||
enable_all_conv()
|
||||
|
@ -299,7 +293,7 @@ def getall():
|
|||
me = Scrap.UsrEntity(0)
|
||||
while E:
|
||||
try:
|
||||
E.Pos = me.Pos
|
||||
E.Descriptor = "HAXX!"
|
||||
except:
|
||||
pass
|
||||
E = Scrap.GetEntity(E.NextInSlot)
|
||||
|
@ -308,6 +302,11 @@ def getall():
|
|||
def god(e=None):
|
||||
if e == None:
|
||||
e = Scrap.UsrEntity(0)
|
||||
if e:
|
||||
try:
|
||||
e.IsType("Car")
|
||||
except:
|
||||
return
|
||||
if e:
|
||||
if e.IsType("Car"):
|
||||
e.Ammo00 = SWeap.GetFAmmo(0, "Max")
|
||||
|
@ -324,9 +323,18 @@ def god(e=None):
|
|||
elif e.IsType("WalkChar"):
|
||||
e.Energy = 1
|
||||
e.Invulnerable = 1
|
||||
e.TimeSpeed = 2.0
|
||||
e.Mass = 100
|
||||
# Scrap.SetAlarm(0.0)
|
||||
Scrap.SetAlarmGrow(-0.5)
|
||||
Scrap.DeleteScheduledFuncs("dbg.god")
|
||||
Scrap.DeleteScheduledFuncs("dbg.god")
|
||||
Scrap.AddScheduledFunc(Scrap.GetTime(), god, (e,), "dbg.god")
|
||||
Scrap.AddScheduledFunc(Scrap.GetTime() + 0.01, god, (e,), "dbg.god")
|
||||
|
||||
|
||||
def ungod():
|
||||
for _ in range(1024):
|
||||
Scrap.DeleteScheduledFuncs("dbg.god")
|
||||
|
||||
|
||||
def ultranuke():
|
||||
|
@ -337,11 +345,92 @@ def ultranuke():
|
|||
(), "dbg.ultranuke")
|
||||
|
||||
|
||||
def freeze(_=None):
|
||||
QC.freeze()
|
||||
Scrap.DeleteScheduledFuncs("dbg.freeze")
|
||||
Scrap.DeleteScheduledFuncs("dbg.freeze")
|
||||
Scrap.AddScheduledFunc(Scrap.GetTime()+0.1, freeze, (None,), "dbg.freeze")
|
||||
|
||||
|
||||
def unfreeze(_):
|
||||
Scrap.DeleteScheduledFuncs("dbg.freeze")
|
||||
Scrap.DeleteScheduledFuncs("dbg.freeze")
|
||||
QC.unfreeze()
|
||||
|
||||
|
||||
def brake():
|
||||
if me:
|
||||
me.Vel = (0, 0, 0)
|
||||
|
||||
|
||||
weaps_hacked = {
|
||||
"Laser": {
|
||||
"AmmoCost": 0,
|
||||
"TimeDelay": 0,
|
||||
},
|
||||
"Vulcan": {
|
||||
"TimeDelay": 0.01,
|
||||
"TimeDelayUPG": 0.01,
|
||||
"AmmoCost": 0
|
||||
},
|
||||
"Devastator": {
|
||||
"AmmoCost": 0,
|
||||
"RechargeTime": 0,
|
||||
"SpreadAngle": 0,
|
||||
},
|
||||
"Tesla": {
|
||||
"AmmoCost": 0,
|
||||
},
|
||||
"ATPC": {
|
||||
"AmmoCost": 0,
|
||||
"UpgradeDelay": 0,
|
||||
"Delay": 0,
|
||||
},
|
||||
"Swarm": {
|
||||
"AmmoCost1": 0,
|
||||
"AmmoCost2": 0,
|
||||
"AmmoCost3": 0,
|
||||
"AmmoCost4": 0,
|
||||
"Number1": 20,
|
||||
"Number2": 20,
|
||||
"Number3": 20,
|
||||
"Number4": 20,
|
||||
"TurnSpeed": 360000,
|
||||
"TurnSpeedUPG": 360000,
|
||||
"TimeDelay": 1.0,
|
||||
},
|
||||
"Inferno": {
|
||||
"AmmoCost": 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def weaphacks():
|
||||
for weapon, properties in weaps_hacked.items():
|
||||
for prop, value in properties.items():
|
||||
Scrap.Set(weapon+prop, value)
|
||||
|
||||
|
||||
def unweaphacks():
|
||||
for weapon, properties in weaps_hacked.items():
|
||||
for prop, value in properties.items():
|
||||
Scrap.Set(weapon+prop, Scrap.Def(weapon+prop))
|
||||
|
||||
|
||||
def test_func():
|
||||
E = Scrap.GetFirst()
|
||||
me = Scrap.UsrEntity(0)
|
||||
while E:
|
||||
if E.Name == me.Name:
|
||||
E = Scrap.GetEntity(E.NextInSlot)
|
||||
try:
|
||||
E.Money = 1024*1024*1024
|
||||
# SAI.SetStateVehicle(8,me.Name,E.Name)
|
||||
except:
|
||||
pass
|
||||
E = Scrap.GetEntity(E.NextInSlot)
|
||||
|
||||
|
||||
for _ in range(1024):
|
||||
Scrap.DeleteScheduledFuncs("dbg.dbg_info")
|
||||
Scrap.DeleteScheduledFuncs("dbg.god")
|
||||
|
@ -354,18 +443,31 @@ for module in sys.builtin_module_names:
|
|||
exec("import " + module)
|
||||
|
||||
sys.settrace(None)
|
||||
|
||||
me = Scrap.UsrEntity(0)
|
||||
notrace()
|
||||
helplib()
|
||||
# settrace()
|
||||
dgb_info()
|
||||
enable_all_conv()
|
||||
god()
|
||||
Scrap.Set("debug", 3)
|
||||
Scrap.Set("ShowConsoleLog", 1)
|
||||
Scrap.Set("AlwaysFlushLog", 1)
|
||||
Scrap.Set("PythonExecute", "import dbg")
|
||||
exec("import QuickConsole;QuickConsole.debug=sys.modules['dbg']")
|
||||
|
||||
print "Debug Module loaded"
|
||||
|
||||
def init():
|
||||
global me
|
||||
global initialized
|
||||
if initialized == 0:
|
||||
from ScrapHack import Mem, asm
|
||||
sys.modules[__name__].mem = Mem
|
||||
sys.modules[__name__].asm = asm
|
||||
me = Scrap.UsrEntity(0)
|
||||
dgb_info()
|
||||
enable_all_conv()
|
||||
god()
|
||||
Scrap.Set("debug", level)
|
||||
Scrap.Set("ShowConsoleLog", 1)
|
||||
Scrap.Set("AlwaysFlushLog", 1)
|
||||
Scrap.Set("PythonExecute", "import dbg;dbg.init()")
|
||||
Scrap.DeleteScheduledFuncs("dbg_init")
|
||||
Scrap.DeleteScheduledFuncs("dbg_init")
|
||||
Scrap.AddScheduledFunc(Scrap.GetTime()+1, init, (), "dbg_init")
|
||||
initialized = 1
|
||||
|
||||
|
||||
exec("import QuickConsole;QuickConsole.dbg=sys.modules['dbg']")
|
||||
print "Debug Module loaded use /dbg.init to initialize"
|
443
config.yml
443
config.yml
|
@ -1,15 +1,26 @@
|
|||
notes: |
|
||||
0x7faa4c: temp storage?
|
||||
0x7d2094: some reference count
|
||||
0x4039b0: fcn.handle_cli_opts?
|
||||
0x668007: ?
|
||||
|
||||
comments:
|
||||
0x6113f9: Check if Window exists
|
||||
|
||||
flags:
|
||||
0x7fbfa0: P_HT_SaveVars
|
||||
0x7fbe50: P_HT_Eng_Vars
|
||||
0x8c8d60: P_Addr_master
|
||||
0x8c8d50: P_Addr_client
|
||||
0x7fa748: P_Socket
|
||||
0x8045dc: P_Socket_Server
|
||||
0x7FE944: P_World
|
||||
0x792618: P_Eng3d_ver
|
||||
0x853a24: P_gWorld
|
||||
0x7FBE4C: P_Vars
|
||||
0x79C698: Py_Mods
|
||||
0x852914: P_D3D8_Dev
|
||||
0x850258: P_D3D8_ZBuffer
|
||||
0x850408: P_D3D8_BackBuffer
|
||||
0x7FCC00: N_Paks_opened
|
||||
0x7fcbec: Hash_Index_Size
|
||||
0x7fcbf0: P_Hash_Index
|
||||
|
@ -36,7 +47,27 @@ flags:
|
|||
0x7fadd8: is_python
|
||||
0x7fc084: pak_lock
|
||||
0x7fbe7c: current_language
|
||||
0x7d2094: py_refcnt_unk
|
||||
0x7d2094: refcnt_Py_None
|
||||
0x7fa830: P_Window
|
||||
0x7fadd0: P_PyExecute
|
||||
0x84d3ec: Py_Initialized
|
||||
0x8c8f10: Py_Debug
|
||||
0x84d3e8: Py_Verbose
|
||||
0x84db38: Py_Optimize
|
||||
0x84dd60: Py_interpr
|
||||
0x7fae38: Debug_Level
|
||||
0x7fae40: Console_Out_Buffer_132_23
|
||||
0x7fbe20: Console_Curr_Line
|
||||
0x84db30: Py_Dummy
|
||||
0x8ca2c4: cmdline
|
||||
0x8c6350: module_filename
|
||||
0x8c6140: P_module_filename
|
||||
0x853954: P_D3DApp
|
||||
0x853091: N_Uniones
|
||||
|
||||
# 0x7fbe24:
|
||||
# 0x7fa778:
|
||||
# 0x8c8d78:
|
||||
|
||||
VMTs:
|
||||
0x78d4d8: Py_entity
|
||||
|
@ -56,133 +87,309 @@ VMTs:
|
|||
0x7933ac: 3d_Gfx
|
||||
0x7933a0: NodeFX
|
||||
|
||||
classes:
|
||||
World:
|
||||
|
||||
types:
|
||||
- "struct PyMethodDef { char *ml_name; void *ml_meth; int ml_flags; char *ml_doc;};"
|
||||
- "struct PyMethodDef { char* ml_name; void* ml_meth; int ml_flags; char* ml_doc;};"
|
||||
- "struct GameVar { struct GameVar* next; const char* name; const char* desc; uint64_t d_type; void* value; void* def_value; };"
|
||||
- "struct HT_Entry { void* data; const char* key; struct HT_Entry* next;};"
|
||||
- "struct PakEntry { unsigned char* filename; bool locked; void* data; uint32_t seek;};"
|
||||
- "struct HashIndexEntry { uint32_t offset; uint32_t size; uint32_t status; const char* name; struct HashIndexEntry* next; };"
|
||||
- "struct HashIndex { uint32_t size; struct HashIndexEntry** data; };"
|
||||
- "struct HashTableEntry { void* data; const char *key; struct HashTableEntry* next; };"
|
||||
- "struct HashTableEntry { void* data; const char* key; struct HashTableEntry* next; };"
|
||||
- "struct HashTable { uint32_t size; struct HashTableEntry** data; };"
|
||||
|
||||
function_signatures:
|
||||
0x5A8390: "int PyRun_SimpleString(const char* command);"
|
||||
0x5BB9D0: "int PyArg_ParseTuple(void* PyObj, char* format, ...);"
|
||||
0x413ee0: "int dbg_log(const char* fmt,...);"
|
||||
0x4134C0: "int write_log(unsigned int color, const char* msg);"
|
||||
0x47C1E0: "int ht_hash_ent_list(const char* str);"
|
||||
0x404BB0: "int ht_hash_ent(const char* str);"
|
||||
0x4016F0: "int reg_get_val(const char* value);"
|
||||
0x414280: "int prepare_html_log(const char* filename);"
|
||||
0x6597d0: "bool read_ini_entry(void* dest,const char* key, const char* section);"
|
||||
0x5A8FB0: "void* Py_InitModule(const char* name,void* methods);"
|
||||
0x5E3800: "int fopen_from_pak(const char* filename);"
|
||||
0x419950: "int fopen_2(const char* filename);"
|
||||
0x41AB50: "int open_pak(const char* filename, int unk_1,void* unk_ptr);"
|
||||
0x404460: "int register_c_callback(const char* name,void* func);"
|
||||
0x414070: "void throw_assertion_2(const char* check,const char* file,const char* date, unsigned int line);"
|
||||
0x5FBC50: "void throw_assertion_1(const char* check,const char* file, unsigned int line);"
|
||||
0x5BC140: "static char* convertsimple1(void *arg, char **p_format, void *p_va);"
|
||||
0x5E3800: "int32_t fopen_from_pak(const char* filename,const char* mode);"
|
||||
0x5a90f0: "void* Py_BuildValue(const char* format, ...);"
|
||||
0x5B9E70: "void* PyObject_GetAttrString(void* obj, const char* attr);"
|
||||
|
||||
- "struct va_list { unsigned int gp_offset; unsigned int fp_offset; void *overflow_arg_area; void *reg_save_area; };"
|
||||
functions:
|
||||
0x6B1C70: strcmp
|
||||
0x5BB9D0: PyArg_ParseTuple
|
||||
0x5DD510: init_engine_3d
|
||||
0x401180: create_window
|
||||
0x401240: create_main_window
|
||||
0x4016F0: reg_get_val
|
||||
0x4134C0: write_log
|
||||
0x414280: prepare_html_log
|
||||
0x418220: get_version_info
|
||||
0x4137E0: write_html_log
|
||||
0x402190: handle_console_input
|
||||
0x5F9520: handle_render_console_input
|
||||
0x404A50: find_entity
|
||||
0x47C1E0: ht_hash_ent_list
|
||||
0x404BB0: ht_hash_ent
|
||||
0x404460: register_c_callback
|
||||
0x417470: load_game
|
||||
0x5E3800: fopen_from_pak
|
||||
0x5e3500: fopen
|
||||
0x403370: init_debug
|
||||
0x401770: init
|
||||
0x4026D0: init_py
|
||||
0x405B40: init_py_sub
|
||||
0x5A8FB0: Py_InitModule
|
||||
0x41AB50: open_pak
|
||||
0x5A8390: PyRun_SimpleString
|
||||
0x414570: setup_game_vars
|
||||
0x5FBC50: throw_assertion_1
|
||||
0x414070: throw_assertion_2
|
||||
0x5F7000: read_ini
|
||||
0x650F80: load_sm3
|
||||
0x6665A0: load_m3d_1
|
||||
0x666900: load_m3d_2
|
||||
0x479B20: world_constructor
|
||||
0x479B40: init_world
|
||||
0x402510: deinit_world
|
||||
0x479870: make_world
|
||||
0x602A70: render_frame
|
||||
0x6B738C: handle_exception
|
||||
0x5B9E70: PyObject_GetAttrString
|
||||
0x413ee0: dbg_log
|
||||
0x5f75e0: init_d3d
|
||||
0x63a2f0: gdi_draw_line
|
||||
0x5e3250: read_stream
|
||||
0x5e3bb0: read_stream_wrapper
|
||||
0x50b9b0: init_scorer
|
||||
0x582e10: init_action_class_list
|
||||
0x528910: init_sound_sys
|
||||
0x5268d0: try_init_sound_sys
|
||||
0x404280: cPyFunction_set_func
|
||||
0x414680: load_config
|
||||
0x414810: save_config
|
||||
0x4f42a0: close_server_socket
|
||||
0x4f4d10: close_server
|
||||
0x4f48e0: close_client
|
||||
0x4f4fb0: is_server
|
||||
0x4f4a10: is_client
|
||||
0x4fac50: is_master
|
||||
0x526910: close_sound_sys
|
||||
0x526520: shutdown_sound_sys
|
||||
0x5dd700: close_3d_engine
|
||||
0x5a7320: close_window
|
||||
0x5dff20: set_exception_handler
|
||||
0x5a7f20: get_console_wnd
|
||||
0x5a73a0: show_console
|
||||
0x666c60: read_m3d
|
||||
0x417df0: snprintf
|
||||
0x5fc930: printf
|
||||
0x6597d0: read_ini_entry
|
||||
0x5fc0a0: engine_debug_log
|
||||
0x5a7440: create_console_window
|
||||
0x6114e0: setup_window
|
||||
0x404420: clear_functions
|
||||
0x405ca0: close_py_subsys
|
||||
0x50bcb0: close_scorer
|
||||
0x479b20: close_world
|
||||
0x582e70: close_action_class
|
||||
0x50b6a0: get_scorer
|
||||
0x50ea20: scorer_parse_type
|
||||
0x636580: list_models
|
||||
0x5a90f0: Py_BuildValue
|
||||
0x41c5a0: has_lst_file
|
||||
0x5a8e90: py_error
|
||||
0x5a9890: get_module_dict
|
||||
0x5c7bb0: get_current_thread
|
||||
0x5aa140: preload_lib
|
||||
0x413c10: sprintf
|
||||
0x405850: check_is_python
|
||||
0x47bf90: setup_ent_list
|
||||
0x474f80: ent_list_get_set
|
||||
0x6283a0:
|
||||
name: load_emi
|
||||
0x4fa9f0:
|
||||
name: send_pkt
|
||||
0x5ca9e0:
|
||||
signature: void* PyFrame_New(void* thread_state, void* code_object,void* globals, void* locals)
|
||||
name: PyFrame_New
|
||||
0x5bcae0:
|
||||
signature: void PyErr_SetString(void* obj, const char* err_msg);
|
||||
name: PyErr_SetString
|
||||
0x5cb040:
|
||||
signature: void* eval_code2(void* dict, const char* key, void* item);
|
||||
name: eval_code2
|
||||
0x5e3c50:
|
||||
convention: cdecl-thiscall-ms
|
||||
name: read_int
|
||||
0x5e3b50:
|
||||
convention: cdecl-thiscall-ms
|
||||
name: read_block_header
|
||||
0x5c66d0:
|
||||
signature: void initerrors(void* dict);
|
||||
name: initerrors
|
||||
0x5bb370:
|
||||
signature: int PyDict_SetItemString(void* dict, const char* key, void* item);
|
||||
name: PyDict_SetItemString
|
||||
0x5b9960:
|
||||
signature: void* PyObject_NEW(void* type, void* typeobj);
|
||||
name: PyObject_NEW
|
||||
0x4145e0:
|
||||
convention: cdecl-thiscall-ms
|
||||
signature: bool get_config_var(char* name);
|
||||
name: get_config_var
|
||||
0x413470:
|
||||
signature: void init_logging();
|
||||
name: init_logging
|
||||
0x5a8040:
|
||||
signature: void Py_Initialize();
|
||||
name: Py_Initialize
|
||||
0x5bb4e0:
|
||||
name: PyModule_GetDict
|
||||
signature: void* PyModule_GetDict(void*);
|
||||
0x5c6610:
|
||||
name: _PyBuiltin_Init_1
|
||||
signature: void* _PyBuiltin_Init_1();
|
||||
0x5b5db0:
|
||||
name: PyString_FromString
|
||||
signature: void* PyString_FromString(const char*);
|
||||
0x5ba3a0:
|
||||
name: PyDict_New
|
||||
signature: void* PyDict_New();
|
||||
0x5c7bd0:
|
||||
name: PyThreadState_Swap
|
||||
signature: void* PyThreadState_Swap(void* new);
|
||||
0x5c7870:
|
||||
name: PyInterpreterState_New
|
||||
signature: void* PyInterpreterState_New();
|
||||
0x5c79b0:
|
||||
name: PyThreadState_New
|
||||
signature: void* PyThreadState_New(void* interp);
|
||||
0x6ad1e9:
|
||||
name: getenv
|
||||
signature: char* getenv(char* var);
|
||||
0x401180:
|
||||
name: create_window
|
||||
0x401240:
|
||||
name: create_main_window
|
||||
0x4016f0:
|
||||
name: reg_get_val
|
||||
signature: int reg_get_val(const char* value);
|
||||
0x401770:
|
||||
name: init
|
||||
0x402190:
|
||||
name: handle_console_input
|
||||
signature: int handle_console_input(const char* input);
|
||||
0x402510:
|
||||
name: deinit_world
|
||||
0x4026d0:
|
||||
name: init_py
|
||||
0x403370:
|
||||
name: init_engine
|
||||
0x4037e0:
|
||||
name: init_debug
|
||||
0x404280:
|
||||
name: cPyFunction_set_func
|
||||
0x404420:
|
||||
name: clear_functions
|
||||
0x404460:
|
||||
name: register_c_callback
|
||||
signature: int register_c_callback(const char* name,void* func);
|
||||
0x404a50:
|
||||
name: find_entity
|
||||
0x404bb0:
|
||||
name: ht_hash_ent
|
||||
signature: int ht_hash_ent(const char* str);
|
||||
0x405850:
|
||||
name: check_is_python
|
||||
0x405b40:
|
||||
name: init_py_sub
|
||||
0x405ca0:
|
||||
name: close_py_subsys
|
||||
0x4134c0:
|
||||
signature: int write_log(unsigned int color, const char* msg);
|
||||
name: write_log
|
||||
0x4137e0:
|
||||
signature: void write_html_log(const char* fmt,...);
|
||||
name: write_html_log
|
||||
0x413c10:
|
||||
name: sprintf
|
||||
0x413ee0:
|
||||
name: dbg_log
|
||||
signature: int dbg_log(const char* fmt,...);
|
||||
0x414070:
|
||||
name: throw_assertion_2
|
||||
signature: void throw_assertion_2(const char* check,const char* file,const char* date, unsigned int line);
|
||||
0x414280:
|
||||
name: prepare_html_log
|
||||
signature: int prepare_html_log(const char* filename);
|
||||
0x414570:
|
||||
name: setup_game_vars
|
||||
0x414680:
|
||||
name: load_config
|
||||
0x414810:
|
||||
name: save_config
|
||||
0x417470:
|
||||
name: load_game
|
||||
0x417df0:
|
||||
name: snprintf_1
|
||||
0x417d80:
|
||||
name: snprintf_2
|
||||
0x418220:
|
||||
name: get_version_info
|
||||
0x419950:
|
||||
name: fopen_2
|
||||
signature: int fopen_2(const char* filename);
|
||||
0x41ab50:
|
||||
name: open_pak
|
||||
signature: int open_pak(const char* filename, int unk_1,void* unk_ptr);
|
||||
0x41c5a0:
|
||||
name: has_lst_file
|
||||
signature: int has_lst_file(void* unk_ptr);
|
||||
0x474f80:
|
||||
name: ent_list_get_set
|
||||
signature: bool ent_list_get_set(const char* name);
|
||||
0x479870:
|
||||
name: make_world
|
||||
0x479b20:
|
||||
name: close_world
|
||||
0x479b40:
|
||||
name: init_world
|
||||
0x47bf90:
|
||||
convention: cdecl-thiscall-ms
|
||||
name: create_ent_list
|
||||
signature: bool create_ent_list(const char* name);
|
||||
0x47c1e0:
|
||||
name: ht_hash_ent_list
|
||||
signature: int ht_hash_ent_list(const char* str);
|
||||
0x4f42a0:
|
||||
name: close_server_socket
|
||||
0x4f48e0:
|
||||
name: close_client
|
||||
0x4f4a10:
|
||||
name: is_client
|
||||
0x4f4d10:
|
||||
name: close_server
|
||||
0x4f4fb0:
|
||||
name: is_server
|
||||
0x4fac50:
|
||||
name: is_master
|
||||
0x50b6a0:
|
||||
name: get_scorer
|
||||
0x50b9b0:
|
||||
name: init_scorer
|
||||
0x50bcb0:
|
||||
name: close_scorer
|
||||
0x50ea20:
|
||||
name: scorer_parse_type
|
||||
0x526520:
|
||||
name: shutdown_sound_sys
|
||||
0x5268d0:
|
||||
name: try_init_sound_sys
|
||||
0x526910:
|
||||
name: close_sound_sys
|
||||
0x528910:
|
||||
name: init_sound_sys
|
||||
0x582e10:
|
||||
name: init_action_class_list
|
||||
0x582e70:
|
||||
name: close_action_class
|
||||
0x5a7320:
|
||||
name: close_window
|
||||
0x5a73a0:
|
||||
name: show_console
|
||||
0x5a7440:
|
||||
name: create_console_window
|
||||
0x5a7f20:
|
||||
name: get_console_wnd
|
||||
0x5a8390:
|
||||
name: PyRun_SimpleString
|
||||
signature: int PyRun_SimpleString(const char* command);
|
||||
0x5a8e90:
|
||||
name: Py_FatalError
|
||||
signature: void Py_FatalError(const char* msg);
|
||||
0x5a8fb0:
|
||||
name: Py_InitModule
|
||||
signature: void* Py_InitModule(const char* name,void* methods);
|
||||
0x5a90f0:
|
||||
name: Py_BuildValue
|
||||
signature: void* Py_BuildValue(const char* format, ...);
|
||||
0x5a9890:
|
||||
name: PyImport_GetModuleDict
|
||||
signature: void* PyImport_GetModuleDict();
|
||||
0x5aa140:
|
||||
name: preload_lib
|
||||
0x5b9e70:
|
||||
name: PyObject_GetAttrString
|
||||
signature: void* PyObject_GetAttrString(void* obj, const char* attr);
|
||||
0x5bb9d0:
|
||||
name: PyArg_ParseTuple
|
||||
signature: int PyArg_ParseTuple(void* PyObj, char* format, ...);
|
||||
0x5bc140:
|
||||
name: convertsimple1
|
||||
signature: static char* convertsimple1(void* arg, char** p_format, void* va_list);
|
||||
0x5bc0f0:
|
||||
name: convertsimple
|
||||
signature: static char* convertsimple(void* arg, char** p_format, char* msgbuf);
|
||||
0x5bbf60:
|
||||
name: converttuple
|
||||
signature: static char* converttuple(void* arg, char** p_format, void* va_list, int* levels, char* msgbuf, int toplevel);
|
||||
0x5bbee0:
|
||||
name: convertitem
|
||||
signature: static char* convertitem(void* arg, char** p_format, void* va_list, int* levels, char* msgbuf);
|
||||
0x5c7bb0:
|
||||
name: PyThreadState_Get
|
||||
signature: void* PyThreadState_Get();
|
||||
0x5dd510:
|
||||
name: init_engine_3d
|
||||
0x5dd700:
|
||||
name: close_3d_engine
|
||||
0x5dff20:
|
||||
name: set_exception_handler
|
||||
0x5e3250:
|
||||
name: read_stream
|
||||
0x5e3500:
|
||||
name: fopen
|
||||
0x5e3800:
|
||||
name: fopen_from_pak
|
||||
signature: int fopen_from_pak(const char* filename,const char* mode);
|
||||
0x5e3bb0:
|
||||
name: read_stream_wrapper
|
||||
0x5f7000:
|
||||
name: read_ini
|
||||
0x5f75e0:
|
||||
name: init_d3d
|
||||
0x5f9520:
|
||||
name: handle_render_console_input
|
||||
0x5fbc50:
|
||||
name: throw_assertion_1
|
||||
signature: void throw_assertion_1(const char* check,const char* file, unsigned int line);
|
||||
0x5fc0a0:
|
||||
name: engine_debug_log
|
||||
0x5fc930:
|
||||
name: printf
|
||||
0x602a70:
|
||||
name: render_frame
|
||||
0x6114e0:
|
||||
name: setup_window
|
||||
0x636580:
|
||||
name: list_models
|
||||
0x63a2f0:
|
||||
name: gdi_draw_line
|
||||
0x650f80:
|
||||
name: load_sm3
|
||||
0x6597d0:
|
||||
name: read_ini_entry
|
||||
signature: bool read_ini_entry(void* dest,const char* key, const char* section);
|
||||
0x6665a0:
|
||||
name: load_m3d_1
|
||||
0x666900:
|
||||
name: load_m3d_2
|
||||
0x666c60:
|
||||
name: read_m3d
|
||||
0x6b1c70:
|
||||
name: strcmp
|
||||
signature: bool strcmp(const char* s1,const char* s2);
|
||||
0x6b738c:
|
||||
name: handle_exception
|
||||
|
||||
script: |
|
||||
e asm.cmt.right = true
|
||||
e cmd.stack = true
|
||||
e scr.utf8 = true
|
||||
e asm.describe = false
|
||||
e graph.cmtright = true
|
||||
|
|
22
file_formats/ai_path.md
Normal file
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))
|
||||
|
154
r2_analyze.py
154
r2_analyze.py
|
@ -4,19 +4,19 @@ import json
|
|||
from datetime import datetime
|
||||
import subprocess as SP
|
||||
from tqdm import tqdm
|
||||
from pprint import pprint
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
tqdm_ascii = False
|
||||
|
||||
r2cmds = []
|
||||
x64_dbg_script=[]
|
||||
x64_dbg_script = []
|
||||
script_path = os.path.dirname(os.path.abspath(__file__))
|
||||
scrap_exe = os.path.abspath(sys.argv[1])
|
||||
scrapland_folder = os.path.abspath(os.path.dirname(scrap_exe))
|
||||
r2_script_path=os.path.join(scrapland_folder, "scrap_dissect.r2")
|
||||
x64_dbg_script_path=os.path.join(scrapland_folder, "scrap_dissect.x32dbg.txt")
|
||||
json_path=os.path.join(scrapland_folder, "scrap_dissect.json")
|
||||
r2_script_path = os.path.join(scrapland_folder, "scrap_dissect.r2")
|
||||
x64_dbg_script_path = os.path.join(scrapland_folder, "scrap_dissect.x32dbg.txt")
|
||||
json_path = os.path.join(scrapland_folder, "scrap_dissect.json")
|
||||
|
||||
assert os.path.isfile(scrap_exe), "File not found!"
|
||||
r2 = r2pipe.open(scrap_exe)
|
||||
|
@ -24,19 +24,22 @@ file_hashes = r2.cmdj("itj")
|
|||
target_hashes = {
|
||||
"sha1": "d2dde960e8eca69d60c2e39a439088b75f0c89fa",
|
||||
"md5": "a934c85dca5ab1c32f05c0977f62e186",
|
||||
"sha256": "24ef449322f28f87b702834f1a1aac003f885db6d68757ff29fad3ddba6c7b88",
|
||||
}
|
||||
|
||||
assert file_hashes == target_hashes, "Hash mismatch"
|
||||
|
||||
def x64_dbg_label(addr,name,prefix=None):
|
||||
|
||||
def x64_dbg_label(addr, name, prefix=None):
|
||||
global x64_dbg_script
|
||||
if isinstance(addr,int):
|
||||
addr=hex(addr)
|
||||
if isinstance(addr, int):
|
||||
addr = hex(addr)
|
||||
if prefix:
|
||||
x64_dbg_script.append(f'lbl {addr},"{prefix}.{name}"')
|
||||
else:
|
||||
x64_dbg_script.append(f'lbl {addr},"{name}"')
|
||||
|
||||
|
||||
def r2_cmd(cmd):
|
||||
global r2, r2cmds
|
||||
r2cmds.append(cmd)
|
||||
|
@ -54,13 +57,15 @@ def r2_cmdJ(cmd):
|
|||
r2cmds.append(cmd)
|
||||
return r2.cmdJ(cmd)
|
||||
|
||||
t_start=datetime.today()
|
||||
|
||||
t_start = datetime.today()
|
||||
|
||||
|
||||
def analysis(full=False):
|
||||
print("[*] Running analysis")
|
||||
steps=[]
|
||||
steps = []
|
||||
if full:
|
||||
steps=[
|
||||
steps = [
|
||||
"e anal.dataref = true",
|
||||
# "e anal.esil = true",
|
||||
"e anal.jmp.after = true",
|
||||
|
@ -72,52 +77,58 @@ def analysis(full=False):
|
|||
"e anal.vinfun = true",
|
||||
"e asm.anal = true",
|
||||
]
|
||||
steps+=["aaaaa"]
|
||||
if full:
|
||||
steps += ["aaaa"]
|
||||
else:
|
||||
steps += ["aaa"]
|
||||
for ac in steps:
|
||||
print(f"[*] Running '{ac}'")
|
||||
r2_cmd(f"{ac} 2>NUL")
|
||||
|
||||
with open(os.path.join(script_path,"config.yml")) as cfg:
|
||||
|
||||
with open(os.path.join(script_path, "config.yml")) as cfg:
|
||||
print("[*] Loading config")
|
||||
config = type("Config",(object,),yaml.load(cfg,Loader=yaml.SafeLoader))
|
||||
config = type("Config", (object,), yaml.load(cfg, Loader=yaml.SafeLoader))
|
||||
|
||||
for line in config.script.strip().splitlines():
|
||||
r2_cmd(line)
|
||||
|
||||
analysis(False)
|
||||
|
||||
for addr,comment in config.comments.items():
|
||||
for addr, comment in config.comments.items():
|
||||
r2_cmd(f"CC {comment} @ {hex(addr)}")
|
||||
|
||||
for t in config.types:
|
||||
r2_cmd(f'"td {t}"')
|
||||
|
||||
for addr, name in config.flags.items():
|
||||
x64_dbg_label(addr,name,"loc")
|
||||
x64_dbg_label(addr, name, "loc")
|
||||
r2_cmd(f"f loc.{name} 4 {hex(addr)}")
|
||||
|
||||
|
||||
for addr, name in config.functions.items():
|
||||
x64_dbg_label(addr,name,"fcn")
|
||||
r2_cmd(f"afr fcn.{name} {hex(addr)}")
|
||||
|
||||
for addr,sig in config.function_signatures.items():
|
||||
r2_cmd(f'"afs {config.function_signatures[addr]}" @{hex(addr)}')
|
||||
|
||||
for addr, func in config.functions.items():
|
||||
name, sig = func.get("name"), func.get("signature")
|
||||
if name:
|
||||
x64_dbg_label(addr, name, "fcn")
|
||||
r2_cmd(f"afr fcn.{name} {hex(addr)}")
|
||||
r2_cmd(f"afn fcn.{name} {hex(addr)}")
|
||||
if sig:
|
||||
sig = sig.replace(name, "fcn." + name)
|
||||
r2_cmd(f'"afs {sig}" @{hex(addr)}')
|
||||
|
||||
|
||||
def vtables():
|
||||
ret = {}
|
||||
print("[*] Analyzing VTables")
|
||||
vtables = r2_cmdJ("avj")
|
||||
for c in tqdm(vtables, ascii=True):
|
||||
for c in tqdm(vtables, ascii=tqdm_ascii):
|
||||
methods = []
|
||||
name=config.VMTs.get(c.offset,f"{c.offset:08x}")
|
||||
x64_dbg_label(c.offset,name,"vmt")
|
||||
name = config.VMTs.get(c.offset, f"{c.offset:08x}")
|
||||
x64_dbg_label(c.offset, name, "vmt")
|
||||
r2_cmd(f"f vmt.{name} 4 {hex(c.offset)}")
|
||||
for idx,m in enumerate(tqdm(c.methods, ascii=True, leave=False)):
|
||||
for idx, m in enumerate(tqdm(c.methods, ascii=tqdm_ascii, leave=False)):
|
||||
methods.append(hex(m.offset))
|
||||
x64_dbg_label(m.offset,f"{name}.{idx}","fcn.vmt")
|
||||
x64_dbg_label(m.offset, f"{name}.{idx}", "fcn.vmt")
|
||||
r2_cmd(f"afr fcn.vmt.{name}.{idx} {hex(m.offset)} 2>NUL")
|
||||
ret[hex(c.offset)] = methods
|
||||
return ret
|
||||
|
@ -127,14 +138,14 @@ def c_callbacks():
|
|||
print("[*] Parsing C Callbacks")
|
||||
funcs = {}
|
||||
res = r2_cmd("/r fcn.register_c_callback ~CALL[1]").splitlines()
|
||||
for addr in tqdm(res, ascii=True):
|
||||
for addr in tqdm(res, ascii=tqdm_ascii):
|
||||
r2_cmd(f"s {addr}")
|
||||
r2_cmd(f"so -3")
|
||||
func, name = r2_cmdJ(f"pdj 2")
|
||||
func = func.refs[0].addr
|
||||
name = r2_cmd(f"psz @{hex(name.refs[0].addr)}").strip()
|
||||
r2_cmd(f"afr fcn.callbacks.{name} {hex(func)} 2>NUL")
|
||||
x64_dbg_label(func,f"{name}","fcn.callbacks")
|
||||
x64_dbg_label(func, f"{name}", "fcn.callbacks")
|
||||
funcs[name] = hex(func)
|
||||
return funcs
|
||||
|
||||
|
@ -142,22 +153,22 @@ def c_callbacks():
|
|||
def assertions():
|
||||
assertions = {}
|
||||
for (n_args, a_addr) in [
|
||||
(4, "fcn.throw_assertion_1"),
|
||||
(3, "fcn.throw_assertion_2"),
|
||||
(3, "fcn.throw_assertion_1"),
|
||||
(4, "fcn.throw_assertion_2"),
|
||||
]:
|
||||
print(f"[*] Parsing C assertions for {a_addr}")
|
||||
res = r2_cmd(f"/r {a_addr} ~CALL[1]").splitlines()
|
||||
print()
|
||||
for line in tqdm(res, ascii=True):
|
||||
for line in tqdm(res, ascii=tqdm_ascii):
|
||||
addr = line.strip()
|
||||
r2_cmd(f"s {addr}")
|
||||
r2_cmd(f"so -{n_args}")
|
||||
dis=r2_cmdJ(f"pij {n_args}")
|
||||
dis = r2_cmdJ(f"pij {n_args}")
|
||||
if n_args == 4:
|
||||
file, msg, date, line = dis
|
||||
line, date, file, msg = dis
|
||||
elif n_args == 3:
|
||||
date = None
|
||||
file, msg, line = dis
|
||||
line, file, msg = dis
|
||||
try:
|
||||
file = r2_cmd(f"psz @{file.refs[0].addr}").strip()
|
||||
msg = r2_cmd(f"psz @{msg.refs[0].addr}").strip()
|
||||
|
@ -180,32 +191,35 @@ def bb_refs(addr):
|
|||
ret = {}
|
||||
res = r2_cmd(f"/r {addr} ~fcn[0,1]").splitlines()
|
||||
print()
|
||||
for ent in res:
|
||||
for ent in tqdm(res, ascii=tqdm_ascii):
|
||||
func, hit = ent.split()
|
||||
ret[hit] = {"asm": [], "func": func}
|
||||
for ins in r2_cmdJ(f"pdbj @{hit}"):
|
||||
ret[hit]["asm"].append(ins.disasm)
|
||||
return ret
|
||||
|
||||
|
||||
def world():
|
||||
print("[*] Parsing World offsets")
|
||||
return bb_refs("loc.P_World")
|
||||
|
||||
|
||||
def render():
|
||||
print("[*] Parsing D3D_Device offsets")
|
||||
return bb_refs("loc.P_D3D8_Dev")
|
||||
|
||||
|
||||
def py_mods():
|
||||
print("[*] Parsing Python modules")
|
||||
res = r2_cmd("/r fcn.Py_InitModule ~CALL[1]").splitlines()
|
||||
print()
|
||||
py_mods = {}
|
||||
for call_loc in tqdm(res, ascii=True):
|
||||
for call_loc in tqdm(res, ascii=tqdm_ascii):
|
||||
r2_cmd(f"s {call_loc}")
|
||||
r2_cmd(f"so -3")
|
||||
args = r2_cmdJ("pdj 3")
|
||||
refs = []
|
||||
if not all([arg.type == "push" for arg in args]):
|
||||
if not all(arg.type == "push" for arg in args):
|
||||
continue
|
||||
for arg in args:
|
||||
refs.append(hex(arg.val))
|
||||
|
@ -214,7 +228,7 @@ def py_mods():
|
|||
name = r2_cmd(f"psz @{name}").strip()
|
||||
r2_cmd(f"s {methods}")
|
||||
r2_cmd(f"f py.{name} 4 {methods}")
|
||||
x64_dbg_label(methods,f"{name}","py")
|
||||
x64_dbg_label(methods, f"{name}", "py")
|
||||
py_mods[name] = {"methods_addr": methods, "doc": doc, "methods": {}}
|
||||
while True:
|
||||
m_name, m_func, _, m_doc = [v.value for v in r2_cmdJ(f"pfj xxxx")]
|
||||
|
@ -223,14 +237,14 @@ def py_mods():
|
|||
m_name, m_func, m_doc = map(hex, (m_name, m_func, m_doc))
|
||||
m_name = r2_cmd(f"psz @{m_name}").strip()
|
||||
r2_cmd(f"f py.{name}.{m_name}.__doc__ 4 {m_doc}")
|
||||
if int(m_doc,16)!=0:
|
||||
x64_dbg_label(m_doc,f"{name}.{m_name}.__doc__","py")
|
||||
if int(m_doc, 16) != 0:
|
||||
x64_dbg_label(m_doc, f"{name}.{m_name}.__doc__", "py")
|
||||
m_doc = r2_cmd(f"psz @{m_doc}").strip()
|
||||
else:
|
||||
m_doc=None
|
||||
m_doc = None
|
||||
py_mods[name]["methods"][m_name] = {"addr": m_func, "doc": m_doc}
|
||||
r2_cmd(f"afr py.{name}.{m_name} {m_func} 2>NUL")
|
||||
x64_dbg_label(m_func,f"{name}.{m_name}","fcn.py")
|
||||
x64_dbg_label(m_func, f"{name}.{m_name}", "fcn.py")
|
||||
r2_cmd("s +16")
|
||||
return py_mods
|
||||
|
||||
|
@ -240,7 +254,7 @@ def game_vars():
|
|||
print("[*] Parsing Game variables")
|
||||
res = r2_cmd("/r fcn.setup_game_vars ~CALL[1]").splitlines()
|
||||
print()
|
||||
for line in tqdm(res, ascii=True):
|
||||
for line in tqdm(res, ascii=tqdm_ascii):
|
||||
addr = line.strip()
|
||||
r2_cmd(f"s {addr}")
|
||||
args = r2_cmd("pdj -5") # seek and print disassembly
|
||||
|
@ -259,27 +273,22 @@ def game_vars():
|
|||
break
|
||||
if len(args_a) != 4:
|
||||
continue
|
||||
if not all(["val" in v for v in args_a]):
|
||||
if not all("val" in v for v in args_a):
|
||||
continue
|
||||
addr, name, _, desc = [v["val"] for v in args_a]
|
||||
name = r2_cmd(f"psz @{hex(name)}").strip()
|
||||
desc = r2_cmd(f"psz @{hex(desc)}").strip()
|
||||
addr = hex(addr)
|
||||
r2_cmd(f"f loc.gvar.{name} 4 {addr}")
|
||||
x64_dbg_label(addr,f"{name}","loc.gvar")
|
||||
x64_dbg_label(addr, f"{name}", "loc.gvar")
|
||||
ret[addr] = {"name": name, "desc": desc}
|
||||
return ret
|
||||
|
||||
|
||||
ret = dict(
|
||||
game_vars=game_vars(),
|
||||
c_callbacks=c_callbacks(),
|
||||
py_mods=py_mods(),
|
||||
assertions=assertions(),
|
||||
vtables=vtables(),
|
||||
world=world(),
|
||||
render=render(),
|
||||
)
|
||||
ret = {}
|
||||
# world, render
|
||||
for func in ["game_vars", "c_callbacks", "py_mods", "assertions", "vtables"]:
|
||||
ret[func] = globals()[func]()
|
||||
|
||||
analysis(True)
|
||||
|
||||
|
@ -288,7 +297,7 @@ with open(json_path, "w") as of:
|
|||
|
||||
print("[+] Wrote scrap_dissect.json")
|
||||
|
||||
with open(x64_dbg_script_path,"w") as of:
|
||||
with open(x64_dbg_script_path, "w") as of:
|
||||
of.write("\n".join(x64_dbg_script))
|
||||
|
||||
print("[+] Wrote scrap_dissect.x32dbg.txt")
|
||||
|
@ -296,30 +305,37 @@ print("[+] Wrote scrap_dissect.x32dbg.txt")
|
|||
with open(r2_script_path, "w") as of:
|
||||
wcmds = []
|
||||
for cmd in r2cmds:
|
||||
record=True
|
||||
for start in ["p","/","s"]:
|
||||
if cmd == "avj":
|
||||
continue
|
||||
record = True
|
||||
for start in ["p", "/", "s"]:
|
||||
if cmd.strip('"').startswith(start):
|
||||
record=False
|
||||
record = False
|
||||
if record:
|
||||
wcmds.append(cmd)
|
||||
of.write("\n".join(wcmds))
|
||||
|
||||
print("[+] Wrote scrap_dissect.r2")
|
||||
|
||||
r2.quit()
|
||||
|
||||
def start_program(cmdl,**kwargs):
|
||||
if os.name=='nt':
|
||||
return SP.Popen(['cmd','/c','start']+cmdl,**kwargs)
|
||||
def start_program(cmdl, **kwargs):
|
||||
if os.name == "nt":
|
||||
return SP.Popen(["cmd", "/c", "start"] + cmdl, **kwargs)
|
||||
else:
|
||||
return SP.Popen(cmdl,**kwargs)
|
||||
return SP.Popen(cmdl, **kwargs)
|
||||
|
||||
print("[+] Analysis took:",datetime.today()-t_start)
|
||||
|
||||
print("[+] Analysis took:", datetime.today() - t_start)
|
||||
|
||||
print("[+] Executing Cutter")
|
||||
try:
|
||||
start_program(['cutter','-A','0','-i',r2_script_path,scrap_exe],cwd=scrapland_folder,shell=False)
|
||||
start_program(
|
||||
["cutter", "-A", "0", "-i", r2_script_path, scrap_exe],
|
||||
cwd=scrapland_folder,
|
||||
shell=False,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
print("[-] cutter not installed, falling back to r2")
|
||||
start_program(['r2','-i',r2_script_path,scrap_exe],cwd=scrapland_folder,shell=False)
|
||||
start_program(
|
||||
["r2", "-i", r2_script_path, scrap_exe], cwd=scrapland_folder, shell=False
|
||||
)
|
||||
|
|
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 os
|
||||
import shutil
|
||||
from construct import *
|
||||
from construct import (
|
||||
Struct,
|
||||
PascalString,
|
||||
Int32ul,
|
||||
Lazy,
|
||||
Pointer,
|
||||
Bytes,
|
||||
this,
|
||||
PrefixedArray,
|
||||
Const,
|
||||
Debugger
|
||||
)
|
||||
from tqdm import tqdm
|
||||
|
||||
setglobalstringencoding(None)
|
||||
|
||||
ScrapFile = Struct(
|
||||
"path" / PascalString(Int32ul),
|
||||
"path" / PascalString(Int32ul, encoding="ascii"),
|
||||
"size" / Int32ul,
|
||||
"offset" / Int32ul,
|
||||
"data" / OnDemandPointer(this.offset, Bytes(this.size)),
|
||||
"data" / Lazy(Pointer(this.offset, Bytes(this.size))),
|
||||
)
|
||||
DummyFile = Struct(
|
||||
"path" / PascalString(Int32ul, encoding="u8"), "size" / Int32ul, "offset" / Int32ul
|
||||
)
|
||||
DummyFile = Struct("path" / PascalString(Int32ul), "size" / Int32ul, "offset" / Int32ul)
|
||||
|
||||
PackedHeader = Struct(
|
||||
Const(b"BFPK"), Const(b"\0\0\0\0"), "files" / PrefixedArray(Int32ul, ScrapFile)
|
45
tools/server.py
Normal file
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