tr2: ditch .dll; use own .exe

Resolves #1694.
This commit is contained in:
Marcin Kurczewski 2024-12-22 22:30:07 +01:00
parent 83ab279364
commit 27edc31c0a
19 changed files with 79 additions and 1701 deletions

Binary file not shown.

View file

@ -1,4 +1,5 @@
## [Unreleased](https://github.com/LostArtefacts/TRX/compare/tr2-0.7.1...develop) - ××××-××-××
- completed decompilation efforts  TR2X.dll is gone, Tomb2.exe no longer needed (#1694)
- added an option to use PS1 contrast levels, available under F8 (#1646)
- added an option to allow disabling the developer console (#2063)
- fixed Lara prioritising throwing a spent flare while mid-air, so to avoid missing ledge grabs (#1989)

View file

@ -58,27 +58,6 @@ typedef enum {
LA_VEHICLE_HIT_BACK = 14,
} LARA_ANIM_VEHICLE;
int32_t __stdcall WinMain(
HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine,
int32_t nShowCmd)
{
g_CmdLine = lpCmdLine;
HWND game_window = WinVidFindGameWindow();
if (game_window) {
SetForegroundWindow(game_window);
return 0;
}
g_AppResultCode = 0;
g_IsGameToExit = false;
Shell_Setup();
Shell_Main();
Shell_Shutdown();
cleanup:
return g_AppResultCode;
}
int16_t __cdecl TitleSequence(void)
{
GF_N_LoadStrings(-1);
@ -134,11 +113,6 @@ int16_t __cdecl TitleSequence(void)
return GFD_EXIT_GAME;
}
HWND __cdecl WinVidFindGameWindow(void)
{
return FindWindowA(CLASS_NAME, WINDOW_NAME);
}
void __cdecl Game_SetCutsceneTrack(const int32_t track)
{
g_CineTrackID = track;

View file

@ -3,18 +3,13 @@
#include "global/types.h"
#include <stdint.h>
#include <windows.h>
// TODO: This is a placeholder file containing functions that originated from
// the decompilation phase, and they are currently disorganized. Eventually,
// they'll need to be properly modularized. The same applies to all files
// within the decomp/ directory which are scheduled for extensive refactoring.
int32_t __stdcall WinMain(
HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine,
int32_t nShowCmd);
int16_t __cdecl TitleSequence(void);
HWND __cdecl WinVidFindGameWindow(void);
void __cdecl Game_SetCutsceneTrack(int32_t track);
void __cdecl CutscenePlayer_Control(int16_t item_num);
void __cdecl Lara_Control_Cutscene(int16_t item_num);

View file

@ -185,6 +185,10 @@ void __cdecl Effect_CreateBartoliLight(const int16_t item_num)
// clang-format on
}
void FX_Empty(ITEM *const item)
{
}
void __cdecl FX_AssaultStart(ITEM *const item)
{
g_SaveGame.statistics.timer = 0;

View file

@ -11,6 +11,7 @@ int16_t __cdecl Effect_MissileFlame(
void __cdecl Effect_CreateBartoliLight(int16_t item_num);
void FX_Empty(ITEM *item);
void __cdecl FX_AssaultStart(ITEM *item);
void __cdecl CreateBubble(const XYZ_32 *pos, int16_t room_num);

View file

@ -182,9 +182,7 @@ cleanup:
void Pickup_Setup(OBJECT *const obj)
{
// TODO: change this to Pickup_Collision after we decompile
// both comparisons in ExtractSaveGameInfo() and GetCarriedItems()
obj->collision = (void *)0x00437E70;
obj->collision = Pickup_Collision;
obj->draw_routine = Pickup_Draw;
obj->save_position = 1;
obj->save_flags = 1;

View file

@ -55,9 +55,7 @@ static void M_MarkDone(ITEM *const puzzle_hole_item)
void PuzzleHole_Setup(OBJECT *const obj, const bool done)
{
if (!done) {
// TODO: change this to PuzzleHole_Collision after we decompile
// the comparison in ExtractSaveGameInfo()
obj->collision = (void *)0x00438A80;
obj->collision = PuzzleHole_Collision;
}
obj->save_flags = 1;
}

View file

@ -1,5 +1,7 @@
#include "global/vars.h"
#include "decomp/effects.h"
#include <libtrx/game/sound/ids.h>
#ifndef MESON_BUILD
@ -23,7 +25,42 @@ int32_t g_CineTrackID = 1;
int32_t g_CineTickRate = 0x8000; // PHD_ONE/TICKS_PER_FRAME
int32_t g_FlipEffect = -1;
uint32_t g_AssaultBestTime = -1;
void (*g_EffectRoutines[32])(ITEM *item);
void (*g_EffectRoutines[])(ITEM *item) = {
FX_Turn180,
FX_FloorShake,
FX_LaraNormal,
FX_Bubbles,
FX_FinishLevel,
FX_Flood,
FX_Chandelier,
FX_Rubble,
FX_Piston,
FX_Curtain,
FX_SetChange,
FX_Explosion,
FX_LaraHandsFree,
FX_FlipMap,
FX_LaraDrawRightGun,
FX_LaraDrawLeftGun,
FX_Empty,
FX_Empty,
FX_SwapMeshesWithMeshSwap1,
FX_SwapMeshesWithMeshSwap2,
FX_SwapMeshesWithMeshSwap3,
FX_InvisibilityOn,
FX_InvisibilityOff,
FX_DynamicLightOn,
FX_DynamicLightOff,
FX_Statue,
FX_ResetHair,
FX_Boiler,
FX_AssaultReset,
FX_AssaultStop,
FX_AssaultStart,
FX_AssaultFinished,
};
int16_t g_CineTargetAngle = PHD_90;
int32_t g_OverlayStatus = 1;
int16_t g_Inv_MainObjectsCount = 8;

View file

@ -24,7 +24,7 @@ extern int32_t g_CineTrackID;
extern int32_t g_CineTickRate;
extern int32_t g_FlipEffect;
extern uint32_t g_AssaultBestTime;
extern void (*g_EffectRoutines[32])(ITEM *item);
extern void (*g_EffectRoutines[])(ITEM *item);
extern int16_t g_CineTargetAngle;
extern int32_t g_OverlayStatus;
extern int16_t g_Inv_MainObjectsCount;

File diff suppressed because it is too large Load diff

View file

@ -1,3 +0,0 @@
#pragma once
void Inject_Exec(void);

View file

@ -1,28 +0,0 @@
#include "inject_util.h"
#include <libtrx/log.h>
#include <windows.h>
void InjectImpl(bool enable, void (*from)(void), void (*to)(void))
{
if (from == to) {
return;
}
if (!enable) {
void (*aux)(void) = from;
from = to;
to = aux;
}
DWORD tmp;
LOG_DEBUG("Patching %p to %p", from, to);
VirtualProtect(from, sizeof(JMP), PAGE_EXECUTE_READWRITE, &tmp);
HANDLE hCurrentProcess = GetCurrentProcess();
JMP buf;
buf.opcode = 0xE9;
buf.offset = (DWORD)(to) - ((DWORD)(from) + sizeof(JMP));
WriteProcessMemory(hCurrentProcess, from, &buf, sizeof(JMP), &tmp);
CloseHandle(hCurrentProcess);
}

View file

@ -1,18 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#pragma pack(push, 1)
typedef struct {
uint8_t opcode; // must be 0xE9
uint32_t offset;
} JMP;
#pragma pack(pop)
void InjectImpl(bool enable, void (*from)(void), void (*to)(void));
#define INJECT(enable, from, to) \
{ \
InjectImpl(enable, (void (*)(void))from, (void (*)(void))to); \
}

24
src/tr2/main.c Normal file
View file

@ -0,0 +1,24 @@
#include "decomp/decomp.h"
#include "game/shell.h"
#include "global/vars.h"
#include <libtrx/filesystem.h>
#include <libtrx/log.h>
#include <windows.h>
int32_t __stdcall WinMain(
HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine,
int32_t nShowCmd)
{
Log_Init(File_GetFullPath("TR2X.log"));
g_CmdLine = lpCmdLine;
g_AppResultCode = 0;
g_IsGameToExit = false;
Shell_Setup();
Shell_Main();
Shell_Shutdown();
cleanup:
return g_AppResultCode;
}

View file

@ -1,31 +0,0 @@
#include "inject_exec.h"
#include <libtrx/filesystem.h>
#include <libtrx/log.h>
#include <SDL2/SDL.h>
#include <stdio.h>
#include <windows.h>
BOOL APIENTRY
DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
Log_Init(File_GetFullPath("TR2X.log"));
LOG_DEBUG("Injected\n");
Inject_Exec();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
LOG_DEBUG("Exiting\n");
break;
}
return TRUE;
}

View file

@ -1,160 +0,0 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <windows.h>
// The path to the legitimate host process
const char *m_HostProcessPath = "Tomb2.exe";
static bool M_FileExists(const char *path)
{
DWORD fileAttributes = GetFileAttributes(path);
if (fileAttributes != INVALID_FILE_ATTRIBUTES
&& !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
return true;
}
return false;
}
static bool M_InjectDLL(HANDLE process_handle, const char *dll_path)
{
bool success = false;
LPVOID load_library_addr =
(LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
fprintf(stderr, "Injecting %s\n", dll_path);
if (!M_FileExists(dll_path)) {
fprintf(stderr, "DLL does not exist.\n");
goto finish;
}
LPVOID dll_path_adr = VirtualAllocEx(
process_handle, NULL, strlen(dll_path) + 1, MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE);
if (!dll_path_adr) {
fprintf(stderr, "Failed to allocate remote memory.\n");
goto finish;
}
if (!WriteProcessMemory(
process_handle, dll_path_adr, dll_path, strlen(dll_path) + 1,
NULL)) {
fprintf(stderr, "Failed to write remote memory.\n");
goto finish;
}
HANDLE remote_thread_handle = CreateRemoteThread(
process_handle, NULL, 0, (LPTHREAD_START_ROUTINE)load_library_addr,
dll_path_adr, 0, NULL);
if (remote_thread_handle == INVALID_HANDLE_VALUE) {
fprintf(stderr, "Failed to create remote thread.\n");
goto finish;
}
WaitForSingleObject(remote_thread_handle, INFINITE);
VirtualFreeEx(
process_handle, dll_path_adr, strlen(dll_path) + 1, MEM_RELEASE);
CloseHandle(remote_thread_handle);
success = true;
finish:
return success;
}
const char *GetDLLPath(void)
{
static char dll_path[MAX_PATH];
GetModuleFileNameA(NULL, dll_path, MAX_PATH);
char *suffix = strstr(dll_path, ".exe");
if (suffix != NULL) {
strcpy(suffix, ".dll");
}
return dll_path;
}
char *GetHostProcessArguments(
const char *host_process_path, const int32_t argc, const char *const argv[])
{
size_t length = 1; // null terminator
for (int32_t i = 0; i < argc; i++) {
if (i > 0) {
length++;
}
const char *arg = i == 0 ? host_process_path : argv[i];
if (strchr(arg, ' ')) {
length += 1;
length += strlen(arg);
length += 1;
} else {
length += strlen(arg);
}
}
char *cmdline = malloc(length);
cmdline[0] = '\0';
for (int32_t i = 0; i < argc; i++) {
if (i > 0) {
strcat(cmdline, " ");
}
const char *arg = i == 0 ? host_process_path : argv[i];
if (strchr(arg, ' ')) {
strcat(cmdline, "\"");
strcat(cmdline, arg);
strcat(cmdline, "\"");
} else {
strcat(cmdline, arg);
}
}
return cmdline;
}
int32_t main(const int32_t argc, const char *const argv[])
{
bool success = false;
const char *dll_path = GetDLLPath();
char *cmdline = GetHostProcessArguments(m_HostProcessPath, argc, argv);
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcess(
m_HostProcessPath, cmdline, NULL, NULL, FALSE, CREATE_SUSPENDED,
NULL, NULL, &si, &pi)) {
fprintf(stderr, "Failed to create the process.\n");
goto finish;
}
if (!M_InjectDLL(pi.hProcess, dll_path)) {
fprintf(stderr, "Failed to inject the DLL.\n");
goto finish;
}
if (ResumeThread(pi.hThread) == (DWORD)-1) {
fprintf(stderr, "Failed to resume the execution of the process.\n");
goto finish;
}
success = true;
finish:
if (cmdline) {
free(cmdline);
cmdline = NULL;
}
if (pi.hThread) {
CloseHandle(pi.hThread);
}
if (pi.hProcess) {
CloseHandle(pi.hProcess);
}
return success ? 0 : 1;
}

View file

@ -31,8 +31,7 @@ dep_sdl2 = dependency('SDL2', static: staticdeps)
dep_mathlibrary = c_compiler.find_library('m', static: staticdeps, required : false)
# autogenerated files
exe_resources = []
dll_resources = []
resources = []
python3 = find_program('python3', required: true)
git = find_program('git', required: true)
@ -63,18 +62,12 @@ if host_machine.system() == 'windows'
version_resource = windows.compile_resources(version_rc)
icon_resource = windows.compile_resources(icon_rc)
exe_resources = [version_resource, icon_resource]
dll_resources = [version_resource]
resources = [version_resource, icon_resource]
link_args += ['-static']
endif
exe_sources = [
'main_exe.c',
exe_resources,
]
dll_sources = [
sources = [
init,
'config.c',
'config_map.c',
@ -283,13 +276,11 @@ dll_sources = [
'game/viewport.c',
'global/enum_map.c',
'global/vars.c',
'inject_exec.c',
'inject_util.c',
'main_dll.c',
dll_resources,
'main.c',
resources,
]
dll_dependencies = [
dependencies = [
dep_trx,
dep_sdl2,
dep_mathlibrary,
@ -297,16 +288,9 @@ dll_dependencies = [
executable(
'TR2X',
exe_sources,
sources,
name_prefix: '',
link_args: link_args,
dependencies: dependencies,
gui_app: true,
)
library(
'TR2X',
dll_sources,
name_prefix: '',
dependencies: dll_dependencies,
link_args: link_args,
)

View file

@ -1,185 +0,0 @@
#!/usr/bin/env python3
import re
from pathlib import Path
from shared.ida_progress import Symbol, parse_progress_file
from shared.paths import TR2Paths
FUNCS_H_FILE = TR2Paths.src_dir / "global/funcs.h"
VARS_H_FILE = TR2Paths.src_dir / "global/vars_decomp.h"
TYPES_H_FILE = TR2Paths.src_dir / "global/types_decomp.h"
COMMON_HEADER = [
"// This file is autogenerated. To update it, run tools/generate_funcs.",
"",
"#pragma once",
"",
]
FUNC_PTR_RE = re.compile(
r"^"
r"(?P<ret_type>.+?)\s*"
r"\("
r"\s*\*(?P<call_type>.*?\s)\s*(?P<func_name>\w+)"
r"(?P<array_def>\[[^\]]*?\]+)?"
r"\)\s*"
r"\((?P<args>.+)\)"
r";?$"
)
VAR_RE = re.compile(
r"^"
r"(?P<ret_type>.+?)\s*"
r"(?P<var_name>\b\w+)\s*"
r"(?P<array_def>(?:\[[^\]]*?\])*)"
r"(\s*=\s*(?P<value_def>.*?))?"
r";?"
r"(\s*\/\/\s*(?P<comment>.*))?"
r"$"
)
def update_file(path: Path, new_content: str) -> None:
if path.read_text() != new_content:
path.write_text(new_content)
def make_func_pointer_define(function: Symbol) -> str:
if match := re.match(
r"(?P<ret_type>.+?\W)(?P<func_name>\b\w+)\s*\((?P<args>.+)\);?",
function.signature,
):
ret_type = match.group("ret_type").strip()
func_name = match.group("func_name").strip()
args = match.group("args").strip()
return f"#define {func_name} (({ret_type} (*)({args})){function.offset_str})"
return ""
def make_var_pointer_define(variable: Symbol) -> str:
if match := FUNC_PTR_RE.match(variable.signature):
ret_type = match.group("ret_type")
call_type = match.group("call_type")
array_def = match.group("array_def")
func_name = match.group("func_name")
args = match.group("args")
if array_def:
return f"#define {func_name} (*(({ret_type}({call_type} *(*){array_def})({args})){variable.offset_str}))"
else:
return f"#define {func_name} (*({ret_type}({call_type}**)({args})){variable.offset_str})"
if match := VAR_RE.match(variable.signature):
ret_type = match.group("ret_type")
var_name = match.group("var_name")
array_def = match.group("array_def")
value_def = match.group("value_def")
comment = match.group("comment")
if not array_def and not value_def and comment == "no-dereferencing":
return f"#define {var_name} (({ret_type}){variable.offset_str})"
if array_def:
return f"#define {var_name} (*({ret_type}(*){array_def}){variable.offset_str})"
elif value_def:
return f"#define {var_name} (*({ret_type}*){variable.offset_str}) // = {value_def}"
else:
return f"#define {var_name} (*({ret_type}*){variable.offset_str})"
print("warn: unrecognized signature", variable.signature)
return ""
def make_funcs_h(functions: list[Symbol]) -> None:
header = [
*COMMON_HEADER,
'#include "global/types.h"',
"",
"// clang-format off",
]
footer = ["// clang-format on"]
defines = []
for function in functions:
if not function.is_decompiled and (
define := make_func_pointer_define(function)
):
defines.append(define)
update_file(FUNCS_H_FILE, "\n".join([*header, *defines, *footer]) + "\n")
def make_vars_h(variables: list[Symbol]) -> None:
header = [
*COMMON_HEADER,
'#include "global/types.h"',
'#include "inject_util.h"',
"",
"// clang-format off",
]
footer = [
"",
"// clang-format on",
]
defines = []
for variable in sorted(variables, key=lambda symbol: symbol.offset):
if not variable.is_decompiled and (
define := make_var_pointer_define(variable)
):
defines.append(define)
update_file(VARS_H_FILE, "\n".join([*header, *defines, *footer]) + "\n")
def make_types_h(types: list[str]) -> None:
header = [
*COMMON_HEADER,
'#include "const.h"',
"",
"#include <libtrx/game/items.h>",
"#include <libtrx/game/math.h>",
"#include <libtrx/game/rooms/types.h>",
"#include <libtrx/game/text.h>",
"",
"#include <stdbool.h>",
"#include <stdint.h>",
"",
"#pragma pack(push, 1)",
"",
"// clang-format off",
]
footer = [
"",
"// clang-format on",
"",
"#pragma pack(pop)",
]
update_file(
TYPES_H_FILE,
"\n".join(
[
*header,
"\n\n".join(
[
definition.strip()
for definition in types
if "// decompiled" not in definition.strip()
]
),
*footer,
]
)
+ "\n",
)
def main() -> None:
progress_file = parse_progress_file(TR2Paths.progress_file)
make_funcs_h(progress_file.functions)
make_vars_h(progress_file.variables)
make_types_h(progress_file.types)
if __name__ == "__main__":
main()