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< |