ScrapHacks in action

Intro

ScrapHacks is a modding/hacking framework for American McGee’s Scrapland.

I’ve been working on it for… i don’t remember how long, the first git commit in the repository was in October 2017 and the first commit involving ScrapHacks was in February 2019.

It works by injecting a DLL into the Scrap.exe process and modifying its memory to redirect program execution.

Scrapland itself uses Python 1.5.2 as a scripting language for the game engine

Components

Injector

The injector is responsible for loading the ScrapHacks DLL into Scrap.exe.

It works by first spawning the Scrapland executable (Scrap.exe not Scrapland.exe, the latter is just a launcher for the former) in a suspended state and forcibly loading a DLL into the address-space of the process using OpenProcess, VirtualAllocEx, WriteProcessMemory and CreateRemoteThread.

After the DLL is loaded and initialized it resumes execution of the process.

The rough outline of the injector code looks like this:

#include <Windows.h>
#define DLL_NAME "ScrapHack.dll"
void InjectDll(DWORD PID,LP)
{
    hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, PID);
    GetFullPathNameA(dll_name, MAX_PATH, dll_full_path, 0);

    // ... Get SE_DEBUG_NAME privileges
    OpenProcessToken(...);
    LookupPrivilegeValue(...);
    AdjustTokenPrivileges(...);
    CloseHandle(...);

    // Get Address of LoadLibraryA in kernel32.dll
    HINSTANCE hK32 = LoadLibraryA("kernel32");
    LPVOID LoadLibrary_Address = (LPVOID)GetProcAddress(hK32, "LoadLibraryA");
    FreeLibrary(hK32);

    // Write DLL-Path to process
    LPVOID mem = VirtualAllocEx(hProc, NULL, strlen(dll_full_path), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProc, mem, dll_full_path, strlen(dll_full_path), 0);

    // Create thread to load DLL
    hRemThread = CreateRemoteThread(hProc, 0, 0, (LPTHREAD_START_ROUTINE)LoadLibrary_Address, mem, 0, 0);
    WaitForSingleObject(hRemThread, INFINITE);
    CloseHandle(hRemThread);
    CloseHandle(hProc);
    return;
}

ScrapHacks.dll

The ScrapHacks DLL contains the majority of the code for this project, when it is first loaded it runs a pre-initialization routine and spawns a new thread to initialize itself fully (because you can’t do much in DllMain without freezing the process your DLL was injected into).

The pre-initialization routine checks if the DLL was loaded via the Injector (by looking for a Environment Variable containing the Injector’s process id) and if so it hooks some DirectX8 functions of the game and the in game debug console handler. Finally it starts another thread to execute its main loop which handles keyboard input and then runs a second loop to handle operating system messages.

The basic structure looks as follows:

void MainLoop(HMODULE mod)
{
    while (running)
    {
        Sleep(100);
        handle_keyboard();
    }
}

void DllPreInit(HMODULE _mod)
{
    InitConsole();
    hook_console();
    hook_d3d8();
}

void DllInit(HMODULE _mod)
{
    CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MainLoop, mod, 0, 0);
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD ul_reason_for_call,
                      LPVOID lpReserved)
{
  HANDLE hThread = INVALID_HANDLE_VALUE;
  switch (ul_reason_for_call)
  {
  case DLL_PROCESS_ATTACH:
    DisableThreadLibraryCalls(hModule);
    DllPreInit(hModule);
    hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)DllInit, hModule, 0, 0);
    break;
  case DLL_PROCESS_DETACH:
    DllUnload(hModule);
    break;
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
    break;
  }
  return TRUE;
}

dbg.py

dbg.py is a python library containing various utility functions for interacting with the game world, for example:

  • Generating documentation for all functions found in the game’s native python modules (docstring are in Spanish for some reason)
  • tracing python code execution in the game engine
  • modifying weapon properties
  • and other more or less useful things

DirectX 8-Hooking

The goal of DirectX 8-Hooking is (in this case) to hook the EndScene of LPDIRECT3DDEVICE8 so we can render our own objects on top of the current frame. To accomplish this ScrapHacks hooks a chain of methods with one method hooking the next one. The chain is a follows:

  1. Direct3DCreate8 in d3d8.dll
  2. LPDIRECT3D8.CreateDevice (VMT index 15)
  3. LPDIRECT3DDEVICE8.EndScene (VMT index 35)

Each hook removes the previous one in the chain as the goal is to hook EndScene and nothing else.

Game function hooking

The only game function that gets hooked by default are the Scrap.Exit method of the game’s python library at 0x4010c0 (this prevents a crash/hang when quitting the game by closing the game window) and the game’s debug console input handler at 0x402190, this allows the addition of custom commands to the be called from the in game console (for example to read and write memory or assemble code in the fly).

.packed format

The game’s assets are packaged in a archive format with the .packed extension, this format is structured as follows:

  • constant string BFPK followed by four null bytes (maybe a version number?)
  • u32 for the number of files in the archive
  • for each file:
    • u32 containing the length of the file path
    • char-array containing the file path
    • u32 containing the size of the file
    • u32containing the offset of the file
  • u8-array containing the concatenated data of all files

… well that’s it for my first proper post, I’ll keep updating it with new findings :)