Skip to content

Commit

Permalink
Introduce dllbuild
Browse files Browse the repository at this point in the history
  • Loading branch information
roblabla committed Dec 21, 2023
1 parent 5a409fb commit 969c505
Show file tree
Hide file tree
Showing 4 changed files with 305 additions and 5 deletions.
13 changes: 11 additions & 2 deletions scripts/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
def main():
parser = argparse.ArgumentParser("th06-build")
parser.add_argument(
"--build-type", choices=["normal", "diffbuild"], default="normal"
"--build-type", choices=["normal", "diffbuild", "dllbuild"], default="normal"
)
args = parser.parse_args()

Expand All @@ -19,13 +19,22 @@ def main():
build_type = BuildType.NORMAL
elif args.build_type == "diffbuild":
build_type = BuildType.DIFFBUILD
elif args.build_type == "dllbuild":
build_type = BuildType.DLLBUILD
configure(build_type)

ninja_args = []

if args.build_type == "dllbuild":
ninja_args += ["build/th06e.dll"]
else:
ninja_args += ["build/th06e.exe"]

# Then, run the build. We use run_windows_program to automatically go through
# wine if running on linux/macos. scripts/th06run.bat will setup PATH and other
# environment variables for the MSVC toolchain to work before calling ninja.
run_windows_program(
[str(SCRIPTS_DIR / "th06run.bat"), "ninja", "build/th06e.exe"],
[str(SCRIPTS_DIR / "th06run.bat"), "ninja"] + ninja_args,
cwd=str(SCRIPTS_DIR.parent),
)

Expand Down
43 changes: 40 additions & 3 deletions scripts/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
class BuildType(Enum):
NORMAL = 1
DIFFBUILD = 2
DLLBUILD = 3


def configure(build_type):
Expand All @@ -21,15 +22,15 @@ def configure(build_type):
writer.variable("cl", "cl.exe")
writer.variable(
"cl_common_flags",
"/MT /EHsc /G5 /Gs /DNDEBUG /Zi /I $builddir/autogenerated /I src /I src/pbg3",
"/MT /EHsc /G5 /Gs /DNDEBUG /Zi /I $builddir/autogenerated /I src /I src/pbg3 /I Detours/src",
)
writer.variable("cl_flags", "$cl_common_flags /Od /Oi /Ob1 /Op")
writer.variable("cl_flags_pbg3", "$cl_common_flags /O2")
writer.variable(
"cl_flags_detours",
"/W4 /WX /we4777 /we4800 /Zi /MT /Gy /Gm- /Zl /Od /DDETOUR_DEBUG=0 /DWIN32_LEAN_AND_MEAN /D_WIN32_WINNT=0x501",
)
if build_type == BuildType.DIFFBUILD:
if build_type in [BuildType.DIFFBUILD, BuildType.DLLBUILD]:
writer.variable("cl_flags", "$cl_flags /DDIFFBUILD")
writer.variable("cl_flags_pbg3", "$cl_flags_pbg3 /DDIFFBUILD")
writer.variable("gas", "as.exe")
Expand Down Expand Up @@ -74,6 +75,10 @@ def configure(build_type):
"genstubs",
"python scripts/generate_stubs.py --output $out",
)
writer.rule(
"gendetours",
"python scripts/generate_detours.py --input-def $builddir/th06.def --output $out",
)
writer.rule(
"gendef",
"python scripts/gendef.py --output $out $in",
Expand Down Expand Up @@ -156,6 +161,25 @@ def configure(build_type):
"$builddir/autogenerated/i18n.hpp",
],
)
writer.build(
"$builddir/autogenerated/detouring.cpp",
"gendetours",
implicit=[
"config/implemented.csv",
"config/mapping.csv",
"scripts/generate_detours.py",
"$builddir/th06.def",
],
)
writer.build(
"$builddir/dllbuild.obj",
"cc",
"src/dllbuild.cpp",
implicit=[
"$builddir/autogenerated/detouring.cpp",
"$builddir/autogenerated/i18n.hpp",
],
)

writer.build("$builddir/globals.obj", "as", inputs="src/globals.asm")
writer.build("$builddir/autogenerated/i18n.hpp", "geni18n", "src/i18n.tpl")
Expand All @@ -180,8 +204,9 @@ def configure(build_type):
+ ["$builddir/" + src + ".obj" for src in pbg3_sources]
+ ["$builddir/th06.res", "$builddir/stubs.obj"]
)
if build_type == BuildType.DIFFBUILD:
if build_type in [BuildType.DIFFBUILD, BuildType.DLLBUILD]:
objfiles += ["$builddir/globals.obj"]

th06_link_libs = "dxguid.lib d3dx8.lib d3d8.lib winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib"
writer.build(
"$builddir/th06e.exe",
Expand All @@ -192,6 +217,18 @@ def configure(build_type):
"link_flags": "$th06_link_flags /debug /pdb:$builddir/th06e.pdb",
},
)

writer.build(
"$builddir/th06e.dll",
"link",
inputs=objfiles + ["$builddir/dllbuild.obj", "$builddir/detours.lib"],
implicit=["$builddir/th06.def"],
variables={
"link_libs": th06_link_libs,
"link_flags": "/DLL /debug /pdb:$builddir/th06e.pdb /export:DetourFinishHelperProcess,@1,NONAME /def:$builddir/th06.def /export:Direct3DCreate8 /export:malloc /export:calloc /export:realloc /export:??2@YAPAXI@Z /export:free /export:_msize",
},
)

writer.build(
"$builddir/detours.lib",
"link",
Expand Down
127 changes: 127 additions & 0 deletions scripts/generate_detours.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import argparse
import csv
import sys

parser = argparse.ArgumentParser(
prog="generate_detours", description="Generate stubs based on the stubs.csv file."
)
parser.add_argument(
"-o", "--output", action="store", help="File to store the generated stubs in"
)
parser.add_argument(
"-i", "--input-def", action="store", help="Def file to find mangled symbols in"
)
args = parser.parse_args()


def get_path_of_mangled_symbol(symbol):
if symbol[0] == "?":
cpp_symbol = symbol[1:]
path = cpp_symbol.split("@")
last = next((idx for idx, x in enumerate(path) if x == ""), None)
path = path[0:last]

first_elem = path[0]
if first_elem[0] == "?":
if first_elem[1] == "0":
cls = first_elem[2:]
path[0] = cls + "::" + cls
elif first_elem[1] == "1":
cls = first_elem[2:]
path[0] = cls + "::~" + cls
elif first_elem[1:3] == "_H":
return None
else:
print("WARNING: Unknown special symbol " + symbol)

return "::".join(reversed(path))
elif symbol[0] == "_":
return symbol[1:].split("@", 1)[0]
else:
raise Exception("Unknown symbol kind " + symbol)


output = sys.stdout
if args.output:
output = open(args.output, "w")

fun_to_mangled_map = {}
with open(args.input_def) as f:
for line in f:
if len(line.strip()) == 0:
continue

if line.strip() == "EXPORTS":
continue

mangled_symbol = line.rsplit(" ", 1)[0].strip()
fun_path = get_path_of_mangled_symbol(mangled_symbol)
if fun_path is None:
continue
print(fun_path)
if fun_path in fun_to_mangled_map:
raise Exception("Overload detected, two functions patch " + fun_path)
fun_to_mangled_map[fun_path] = mangled_symbol

fun_to_mangled_map["operator_new"] = "??2@YAPAXI@Z"
fun_to_mangled_map["_malloc"] = "malloc"
fun_to_mangled_map["_calloc"] = "calloc"
fun_to_mangled_map["_realloc"] = "realloc"
fun_to_mangled_map["_free"] = "free"
fun_to_mangled_map["__msize"] = "_msize"

with open("config/mapping.csv") as f:
mapping_csv = csv.reader(f)
mapping_obj = {}
for func in mapping_csv:
fun_name = func[0]
fun_addr = int(func[1], 16)
mapping_obj[fun_name] = fun_addr

f = open("config/implemented.csv")
implemented_csv = csv.reader(f)

print("Detouring detours[] = {", file=output)
first = True
for implemented in implemented_csv:
if not first:
print(",", file=output)

fun_name = implemented[0]
fun_mangled_name = fun_to_mangled_map[fun_name]
fun_addr = mapping_obj[fun_name]
print(
" { " + hex(fun_addr) + ', "' + fun_mangled_name + '", FALSE }',
end="",
file=output,
)
first = False

f = open("config/stubbed.csv")
stubbed_csv = csv.reader(f)
first = True
for implemented in stubbed_csv:
print(",", file=output)

fun_name = implemented[0]
fun_mangled_name = fun_to_mangled_map[fun_name]
fun_addr = mapping_obj[fun_name]
print(
" { " + hex(fun_addr) + ', "' + fun_mangled_name + '", TRUE }',
end="",
file=output,
)

# Add some necessary detouring to share MSVCRT heap with the main executable
for fun_name in ["_malloc", "_calloc", "_realloc", "operator_new", "_free", "__msize"]:
print(",", file=output)

fun_mangled_name = fun_to_mangled_map[fun_name]
fun_addr = mapping_obj[fun_name]
print(
" { " + hex(fun_addr) + ', "' + fun_mangled_name + '", FALSE }',
end="",
file=output,
)

print("\n};", file=output)
127 changes: 127 additions & 0 deletions src/dllbuild.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// DLLBUILD support
//
// The DLLBUILD is a special build of th06 meant to be injected into th06 1.02h,
// replacing the official functions with our own reimplementation. The primary
// purpose here is to validate that our reimplementations work even when they
// are not bit-accurate, but can also serve as a mechanism to enable modding
// before the reimplementation work is complete.
//
// The way it works is rather simple: We use the Detours library to hijack all
// the functions we have reimplemented from the original binary. Similarly, for
// the functions we want to call but have not yet reimplemented, we generate
// stub functions that simply forward the argument. Finally, to ensure the data
// is shared, we integrate the DIFFBUILD mechanism to store data at fixed
// locations in memory.
//
// The stubs are generated automatically by a script in
// scripts/generate_stubs.py, based on the information found in
// config/stubbed.csv. This generates a stubbed.cpp file that will be
// automatically compiled and linked.
//
// Meanwhile, the list of functions to detour is auto-generated by another
// script, scripts/generate_detours.py, based on the information found in
// config/implemented.csv.

#include "AnmManager.hpp"
#include "AsciiManager.hpp"
#include "Chain.hpp"
#include "detours.h"
#include <fstream>
#include <string.h>

struct Detouring
{
size_t addressInOriginalBinary;
char *nameInDllReplacement;
BOOL stub;

void *addrToReplace;
void *replaceWith;
};

#include "detouring.cpp"

// For now, always use D3D_WRAPPER. In the future, we may want to swap it out
// with another "host" dll.
#define D3D_WRAPPER 1
#ifdef D3D_WRAPPER
typedef IDirect3D8 *(WINAPI *Direct3DCreate8Proc)(UINT);
extern "C" IDirect3D8 *__stdcall Direct3DCreate8(UINT sdk_version)
{
char path[MAX_PATH + 1];
GetSystemDirectoryA(path, MAX_PATH);
strncat(path, "\\d3d8.dll", MAX_PATH - strlen(path));
HMODULE d3d8dll = LoadLibraryA(path);
if (d3d8dll == NULL)
{
return NULL;
}
Direct3DCreate8Proc realproc = (Direct3DCreate8Proc)GetProcAddress(d3d8dll, "Direct3DCreate8");
if (!realproc)
{
return NULL;
}

return realproc(sdk_version);
}
#endif

BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
if (DetourIsHelperProcess())
{
return TRUE;
}

if (dwReason == DLL_PROCESS_ATTACH)
{
DetourRestoreAfterWith();

if (DetourTransactionBegin() != 0)
{
// TODO: Show an error.
return TRUE;
}
if (DetourUpdateThread(GetCurrentThread()) != 0)
{
// TODO: Show an error
DetourTransactionAbort();
return TRUE;
}
for (size_t i = 0; i < sizeof(detours) / sizeof(detours[0]); i++)
{
if (!detours[i].stub)
{
detours[i].addrToReplace = (void *)detours[i].addressInOriginalBinary;
detours[i].replaceWith = (void *)GetProcAddress(hinst, detours[i].nameInDllReplacement);
}
else
{
detours[i].addrToReplace = (void *)GetProcAddress(hinst, detours[i].nameInDllReplacement);
detours[i].replaceWith = (void *)detours[i].addressInOriginalBinary;
}
if (DetourAttach(&detours[i].addrToReplace, detours[i].replaceWith) != 0)
{
// TODO: Show an error
DetourTransactionAbort();
return TRUE;
}
}
if (DetourTransactionCommit() != 0)
{
// TODO: Show an error
return TRUE;
}
}
else if (dwReason == DLL_PROCESS_DETACH)
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
for (size_t i = 0; i < sizeof(detours) / sizeof(detours[0]); i++)
{
DetourDetach(&detours[i].addrToReplace, detours[i].replaceWith);
}
DetourTransactionCommit();
}
return TRUE;
}

0 comments on commit 969c505

Please sign in to comment.