898 lines
22 KiB
C++
Executable File
898 lines
22 KiB
C++
Executable File
#include <cassert>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <system_error>
|
|
#ifndef _WIN32
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
#include "../Console.h"
|
|
#include "../Environment.h"
|
|
#include "../GameCommands.h"
|
|
#include "../Gui.h"
|
|
#include "../Input.h"
|
|
#include "../Station.h"
|
|
#include "../Title.h"
|
|
#include "../Tutorial.h"
|
|
#include "../Ui.h"
|
|
#include "../ViewportManager.h"
|
|
#include "../audio/audio.h"
|
|
#include "../core/FileSystem.hpp"
|
|
#include "../graphics/colours.h"
|
|
#include "../graphics/gfx.h"
|
|
#include "../map/tile.h"
|
|
#include "../platform/platform.h"
|
|
#include "../things/vehicle.h"
|
|
#include "../ui/WindowManager.h"
|
|
#include "../utility/string.hpp"
|
|
#include "interop.hpp"
|
|
|
|
using namespace openloco;
|
|
|
|
#define STUB() console::logVerbose(__FUNCTION__)
|
|
|
|
#ifdef _MSC_VER
|
|
#define STDCALL __stdcall
|
|
#define CDECL __cdecl
|
|
#elif defined(__GNUC__)
|
|
#define STDCALL __attribute__((stdcall))
|
|
#define CDECL __attribute__((cdecl))
|
|
#else
|
|
#error Unknown compiler, please define STDCALL and CDECL
|
|
#endif
|
|
|
|
#pragma warning(push)
|
|
// MSVC ignores C++17's [[maybe_unused]] attribute on functions, so just disable the warning
|
|
#pragma warning(disable : 4505) // unreferenced local function has been removed.
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static int32_t CDECL audioLoadChannel(int a0, const char* a1, int a2, int a3, int a4)
|
|
{
|
|
return audio::loadChannel((audio::channel_id)a0, a1, a2) ? 1 : 0;
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static int32_t CDECL audioPlayChannel(int a0, int a1, int a2, int a3, int a4)
|
|
{
|
|
return audio::playChannel((audio::channel_id)a0, a1, a2, a3, a4) ? 1 : 0;
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static void CDECL audioStopChannel(int a0, int a1, int a2, int a3, int a4)
|
|
{
|
|
audio::stopChannel((audio::channel_id)a0);
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static void CDECL audioSetChannelVolume(int a0, int a1)
|
|
{
|
|
audio::setChannelVolume((audio::channel_id)a0, a1);
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static int32_t CDECL audioIsChannelPlaying(int a0)
|
|
{
|
|
return audio::isChannelPlaying((audio::channel_id)a0) ? 1 : 0;
|
|
}
|
|
|
|
#ifdef _NO_LOCO_WIN32_
|
|
static void STDCALL fn_40447f()
|
|
{
|
|
STUB();
|
|
return;
|
|
}
|
|
|
|
static void STDCALL fn_404b68(int a0, int a1, int a2, int a3)
|
|
{
|
|
STUB();
|
|
return;
|
|
}
|
|
|
|
static int STDCALL getNumDSoundDevices()
|
|
{
|
|
STUB();
|
|
return 0;
|
|
}
|
|
#endif // _NO_LOCO_WIN32_
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
struct palette_entry_t
|
|
{
|
|
uint8_t b, g, r, a;
|
|
};
|
|
#pragma pack(pop)
|
|
using set_palette_func = void (*)(const palette_entry_t* palette, int32_t index, int32_t count);
|
|
static interop::loco_global<set_palette_func, 0x0052524C> set_palette_callback;
|
|
|
|
#ifdef _NO_LOCO_WIN32_
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static void CDECL fn_4054a3(const palette_entry_t* palette, int32_t index, int32_t count)
|
|
{
|
|
(*set_palette_callback)(palette, index, count);
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static bool STDCALL fn_4054b9()
|
|
{
|
|
STUB();
|
|
return true;
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static uint32_t STDCALL lib_timeGetTime()
|
|
{
|
|
return platform::getTime();
|
|
}
|
|
|
|
//typedef bool (CALLBACK *LPDSENUMCALLBACKA)(LPGUID, char*, char*, void*);
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static long STDCALL fn_DirectSoundEnumerateA(void* pDSEnumCallback, void* pContext)
|
|
{
|
|
STUB();
|
|
return 0;
|
|
}
|
|
|
|
static void STDCALL fn_4078be()
|
|
{
|
|
STUB();
|
|
return;
|
|
}
|
|
|
|
static void STDCALL fn_4078fe()
|
|
{
|
|
STUB();
|
|
return;
|
|
}
|
|
|
|
static void STDCALL fn_407b26()
|
|
{
|
|
STUB();
|
|
return;
|
|
}
|
|
|
|
///region Progress bar
|
|
|
|
static void CDECL fn_4080bb(char* lpWindowName, uint32_t a1)
|
|
{
|
|
console::log("Create progress bar");
|
|
}
|
|
|
|
static void CDECL fn_408163()
|
|
{
|
|
console::log("Destroy progress bar");
|
|
}
|
|
|
|
static void CDECL fn_40817b(uint16_t arg0)
|
|
{
|
|
console::log("SendMessage(PBM_SETRANGE, %d)", arg0);
|
|
console::log("SendMessage(PBM_SETSTEP, %d)", 1);
|
|
}
|
|
|
|
static void CDECL fn_4081ad(int32_t wParam)
|
|
{
|
|
console::log("SendMessage(PBM_SETPOS, %d)", wParam);
|
|
}
|
|
|
|
///endregion
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static uint32_t CDECL fn_FileSeekSet(FILE* a0, int32_t distance)
|
|
{
|
|
console::logVerbose("seek %d bytes from start", distance);
|
|
fseek(a0, distance, SEEK_SET);
|
|
return ftell(a0);
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static uint32_t CDECL fn_FileSeekFromCurrent(FILE* a0, int32_t distance)
|
|
{
|
|
console::logVerbose("seek %d bytes from current", distance);
|
|
fseek(a0, distance, SEEK_CUR);
|
|
return ftell(a0);
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static uint32_t CDECL fn_FileSeekFromEnd(FILE* a0, int32_t distance)
|
|
{
|
|
console::logVerbose("seek %d bytes from end", distance);
|
|
fseek(a0, distance, SEEK_END);
|
|
return ftell(a0);
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static int32_t CDECL fn_FileRead(FILE* a0, char* buffer, int32_t size)
|
|
{
|
|
console::logVerbose("read %d bytes (%d)", size, fileno(a0));
|
|
size = fread(buffer, 1, size, a0);
|
|
|
|
return size;
|
|
}
|
|
|
|
typedef struct FindFileData
|
|
{
|
|
uint32_t dwFileAttributes;
|
|
uint32_t ftCreationTime[2];
|
|
uint32_t ftLastAccessTime[2];
|
|
uint32_t ftLastWriteTime[2];
|
|
uint32_t nFileSizeHigh;
|
|
uint32_t nFileSizeLow;
|
|
uint32_t r0;
|
|
uint32_t r1;
|
|
char cFilename[260];
|
|
char cAlternateFileName[14];
|
|
} FindFileData;
|
|
|
|
class Session
|
|
{
|
|
public:
|
|
std::vector<fs::path> fileList;
|
|
};
|
|
|
|
#define FILE_ATTRIBUTE_DIRECTORY 0x10
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static Session* CDECL fn_FindFirstFile(char* lpFileName, FindFileData* out)
|
|
{
|
|
console::logVerbose("%s (%s)", __FUNCTION__, lpFileName);
|
|
|
|
Session* data = new Session;
|
|
|
|
fs::path path = lpFileName;
|
|
path.remove_filename();
|
|
|
|
fs::directory_iterator iter(path), end;
|
|
|
|
while (iter != end)
|
|
{
|
|
data->fileList.push_back(iter->path());
|
|
++iter;
|
|
}
|
|
|
|
utility::strcpy_safe(out->cFilename, data->fileList[0].filename().u8string().c_str());
|
|
if (fs::is_directory(data->fileList[0]))
|
|
{
|
|
out->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
|
|
}
|
|
else
|
|
{
|
|
out->dwFileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
|
|
}
|
|
|
|
data->fileList.erase(data->fileList.begin());
|
|
return data;
|
|
}
|
|
|
|
static bool CDECL fn_FindNextFile(Session* data, FindFileData* out)
|
|
{
|
|
STUB();
|
|
|
|
if (data->fileList.size() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
utility::strcpy_safe(out->cFilename, data->fileList[0].filename().u8string().c_str());
|
|
if (fs::is_directory(data->fileList[0]))
|
|
{
|
|
out->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
|
|
}
|
|
else
|
|
{
|
|
out->dwFileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
|
|
}
|
|
|
|
data->fileList.erase(data->fileList.begin());
|
|
|
|
return true;
|
|
}
|
|
|
|
static void CDECL fn_FindClose(Session* data)
|
|
{
|
|
STUB();
|
|
|
|
delete data;
|
|
}
|
|
#endif // _NO_LOCO_WIN32_
|
|
|
|
[[maybe_unused]] static void CDECL fnc0(void)
|
|
{
|
|
STUB();
|
|
}
|
|
|
|
[[maybe_unused]] static void CDECL fnc1(int i1)
|
|
{
|
|
STUB();
|
|
}
|
|
|
|
[[maybe_unused]] static void CDECL fnc2(int i1, int i2)
|
|
{
|
|
STUB();
|
|
}
|
|
|
|
[[maybe_unused]] static void STDCALL fn0()
|
|
{
|
|
return;
|
|
}
|
|
|
|
[[maybe_unused]] static void STDCALL fn1(int i1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
[[maybe_unused]] static void STDCALL fn2(int i1, int i2)
|
|
{
|
|
STUB();
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static void* CDECL fn_malloc(uint32_t size)
|
|
{
|
|
return malloc(size);
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static void* CDECL fn_realloc(void* block, uint32_t size)
|
|
{
|
|
return realloc(block, size);
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static void CDECL fn_free(void* block)
|
|
{
|
|
return free(block);
|
|
}
|
|
|
|
#ifdef _NO_LOCO_WIN32_
|
|
static void STDCALL fn_dump(uint32_t address)
|
|
{
|
|
console::log("Missing hook: 0x%x", address);
|
|
}
|
|
|
|
enum
|
|
{
|
|
DS_OK = 0,
|
|
DSERR_NODRIVER = 0x88780078,
|
|
};
|
|
|
|
static uint32_t STDCALL lib_DirectSoundCreate(void* lpGuid, void* ppDS, void* pUnkOuter)
|
|
{
|
|
console::log("lib_DirectSoundCreate(%lx, %lx, %lx)", (uintptr_t)lpGuid, (uintptr_t)ppDS, (uintptr_t)pUnkOuter);
|
|
|
|
return DSERR_NODRIVER;
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static uint32_t STDCALL lib_CreateRectRgn(int x1, int y1, int x2, int y2)
|
|
{
|
|
console::log("CreateRectRgn(%d, %d, %d, %d)", x1, y1, x2, y2);
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t STDCALL lib_GetUpdateRgn(uintptr_t hWnd, uintptr_t hRgn, bool bErase)
|
|
{
|
|
console::log("GetUpdateRgn(%lx, %lx, %d)", hWnd, hRgn, bErase);
|
|
return 0;
|
|
}
|
|
|
|
static void* STDCALL lib_OpenMutexA(uint32_t dwDesiredAccess, bool bInheritHandle, char* lpName)
|
|
{
|
|
console::log("OpenMutexA(0x%x, %d, %s)", dwDesiredAccess, bInheritHandle, lpName);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static bool STDCALL lib_DeleteFileA(char* lpFileName)
|
|
{
|
|
console::log("DeleteFileA(%s)", lpFileName);
|
|
|
|
return false;
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static bool STDCALL lib_WriteFile(
|
|
FILE* hFile,
|
|
char* buffer,
|
|
size_t nNumberOfBytesToWrite,
|
|
uint32_t* lpNumberOfBytesWritten,
|
|
uintptr_t lpOverlapped)
|
|
{
|
|
*lpNumberOfBytesWritten = fwrite(buffer, 1, nNumberOfBytesToWrite, hFile);
|
|
console::logVerbose("WriteFile(%s)", buffer);
|
|
|
|
return true;
|
|
}
|
|
|
|
#define GENERIC_READ 0x80000000
|
|
#define GENERIC_WRITE 0x40000000
|
|
|
|
#define CREATE_NEW 1
|
|
#define CREATE_ALWAYS 2
|
|
#define OPEN_EXISTING 3
|
|
#define OPEN_ALWAYS 4
|
|
#define TRUNCATE_EXISTING 5
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static int32_t STDCALL lib_CreateFileA(
|
|
char* lpFileName,
|
|
uint32_t dwDesiredAccess,
|
|
uint32_t dwShareMode,
|
|
uintptr_t lpSecurityAttributes,
|
|
uint32_t dwCreationDisposition,
|
|
uint32_t dwFlagsAndAttributes,
|
|
uintptr_t hTemplateFile)
|
|
{
|
|
console::logVerbose("CreateFile(%s, 0x%x, 0x%x)", lpFileName, dwDesiredAccess, dwCreationDisposition);
|
|
|
|
FILE* pFILE = nullptr;
|
|
if (dwDesiredAccess == GENERIC_READ && dwCreationDisposition == OPEN_EXISTING)
|
|
{
|
|
pFILE = fopen(lpFileName, "r");
|
|
}
|
|
else if (dwDesiredAccess == GENERIC_WRITE && dwCreationDisposition == CREATE_ALWAYS)
|
|
{
|
|
pFILE = fopen(lpFileName, "w");
|
|
}
|
|
else
|
|
{
|
|
assert(false);
|
|
}
|
|
|
|
if (pFILE == nullptr)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return (int32_t)pFILE;
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static bool STDCALL lib_SetFileAttributesA(char* lpFileName, uint32_t dwFileAttributes)
|
|
{
|
|
// FILE_ATTRIBUTE_NORMAL = 0x80
|
|
assert(dwFileAttributes == 0x80);
|
|
console::log("SetFileAttributes(%s, %x)", lpFileName, dwFileAttributes);
|
|
|
|
std::error_code ec;
|
|
auto path = fs::path(lpFileName);
|
|
auto perms = fs::status(path, ec).permissions();
|
|
if (!ec)
|
|
{
|
|
lib_CreateFileA(lpFileName, dwFileAttributes, 0, 0, 0, 0, 0);
|
|
}
|
|
fs::permissions(path, fs::perms::owner_read | fs::perms::owner_write | perms, ec);
|
|
return !ec;
|
|
}
|
|
|
|
static void* STDCALL lib_CreateMutexA(uintptr_t lmMutexAttributes, bool bInitialOwner, char* lpName)
|
|
{
|
|
console::log("CreateMutexA(0x%lx, %d, %s)", lmMutexAttributes, bInitialOwner, lpName);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static bool STDCALL lib_CloseHandle(void* hObject)
|
|
{
|
|
auto file = (FILE*)hObject;
|
|
|
|
return fclose(file) == 0;
|
|
}
|
|
|
|
FORCE_ALIGN_ARG_POINTER
|
|
static void STDCALL lib_PostQuitMessage(int32_t exitCode)
|
|
{
|
|
console::log("lib_PostQuitMessage(%d)", exitCode);
|
|
exit(exitCode);
|
|
}
|
|
#endif // _NO_LOCO_WIN32_
|
|
#pragma warning(pop)
|
|
|
|
static void registerMemoryHooks()
|
|
{
|
|
using namespace openloco::interop;
|
|
|
|
// Hook Locomotion's alloc / free routines so that we don't
|
|
// allocate a block in one module and freeing it in another.
|
|
writeJmp(0x4d1401, (void*)&fn_malloc);
|
|
writeJmp(0x4D1B28, (void*)&fn_realloc);
|
|
writeJmp(0x4D1355, (void*)&fn_free);
|
|
}
|
|
|
|
#ifdef _NO_LOCO_WIN32_
|
|
static void registerNoWin32Hooks()
|
|
{
|
|
using namespace openloco::interop;
|
|
|
|
writeJmp(0x40447f, (void*)&fn_40447f);
|
|
writeJmp(0x404b68, (void*)&fn_404b68);
|
|
writeJmp(0x404e8c, (void*)&getNumDSoundDevices);
|
|
writeJmp(0x4054b9, (void*)&fn_4054b9);
|
|
writeJmp(0x4064fa, (void*)&fn0);
|
|
writeJmp(0x4054a3, (void*)&fn_4054a3);
|
|
writeJmp(0x4072ec, (void*)&fn0);
|
|
writeJmp(0x4078be, (void*)&fn_4078be);
|
|
writeJmp(0x4078fe, (void*)&fn_4078fe);
|
|
writeJmp(0x407b26, (void*)&fn_407b26);
|
|
writeJmp(0x4080bb, (void*)&fn_4080bb);
|
|
writeJmp(0x408163, (void*)&fn_408163);
|
|
writeJmp(0x40817b, (void*)&fn_40817b);
|
|
writeJmp(0x4081ad, (void*)&fn_4081ad);
|
|
writeJmp(0x4081c5, (void*)&fn_FileSeekSet);
|
|
writeJmp(0x4081d8, (void*)&fn_FileSeekFromCurrent);
|
|
writeJmp(0x4081eb, (void*)&fn_FileSeekFromEnd);
|
|
writeJmp(0x4081fe, (void*)&fn_FileRead);
|
|
writeJmp(0x40830e, (void*)&fn_FindFirstFile);
|
|
writeJmp(0x40831d, (void*)&fn_FindNextFile);
|
|
writeJmp(0x40832c, (void*)&fn_FindClose);
|
|
writeJmp(0x4d0fac, (void*)&fn_DirectSoundEnumerateA);
|
|
|
|
// fill DLL hooks for ease of debugging
|
|
for (int i = 0x4d7000; i <= 0x4d72d8; i += 4)
|
|
{
|
|
hookDump(i, (void*)&fn_dump);
|
|
}
|
|
|
|
// dsound.dll
|
|
hookLib(0x4d7024, (void*)&lib_DirectSoundCreate);
|
|
|
|
// gdi32.dll
|
|
hookLib(0x4d7078, (void*)&lib_CreateRectRgn);
|
|
|
|
// kernel32.dll
|
|
hookLib(0x4d70e0, (void*)&lib_CreateMutexA);
|
|
hookLib(0x4d70e4, (void*)&lib_OpenMutexA);
|
|
hookLib(0x4d70f0, (void*)&lib_WriteFile);
|
|
hookLib(0x4d70f4, (void*)&lib_DeleteFileA);
|
|
hookLib(0x4d70f8, (void*)&lib_SetFileAttributesA);
|
|
hookLib(0x4d70fC, (void*)&lib_CreateFileA);
|
|
|
|
// user32.dll
|
|
hookLib(0x4d71e8, (void*)&lib_PostQuitMessage);
|
|
hookLib(0x4d714c, (void*)&lib_CloseHandle);
|
|
hookLib(0x4d7248, (void*)&lib_GetUpdateRgn);
|
|
hookLib(0x4d72b0, (void*)&lib_timeGetTime);
|
|
}
|
|
#endif // _NO_LOCO_WIN32_
|
|
|
|
void openloco::interop::loadSections()
|
|
{
|
|
#ifndef _WIN32
|
|
int32_t err = mprotect((void*)0x401000, 0x4d7000 - 0x401000, PROT_READ | PROT_WRITE | PROT_EXEC);
|
|
if (err != 0)
|
|
{
|
|
perror("mprotect");
|
|
}
|
|
|
|
err = mprotect((void*)0x4d7000, 0x1162000 - 0x4d7000, PROT_READ | PROT_WRITE);
|
|
if (err != 0)
|
|
{
|
|
perror("mprotect");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void registerAudioHooks()
|
|
{
|
|
using namespace openloco::interop;
|
|
|
|
writeJmp(0x0040194E, (void*)&audioLoadChannel);
|
|
writeJmp(0x00401999, (void*)&audioPlayChannel);
|
|
writeJmp(0x00401A05, (void*)&audioStopChannel);
|
|
writeJmp(0x00401AD3, (void*)&audioSetChannelVolume);
|
|
writeJmp(0x00401B10, (void*)&audioIsChannelPlaying);
|
|
|
|
writeRet(0x0048AB36);
|
|
writeRet(0x00404B40);
|
|
registerHook(
|
|
0x0048A18C,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
audio::updateSounds();
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x00489C6A,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
audio::stopVehicleNoise();
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x0048A4BF,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
audio::playSound((vehicle_26*)regs.esi);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x00489CB5,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
audio::playSound((audio::sound_id)regs.eax, { regs.cx, regs.dx, regs.bp }, regs.ebx);
|
|
return 0;
|
|
});
|
|
registerHook(
|
|
0x00489F1B,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
audio::playSound((audio::sound_id)regs.eax, { regs.cx, regs.dx, regs.bp }, regs.edi, regs.ebx);
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
void openloco::interop::registerHooks()
|
|
{
|
|
using namespace openloco::ui::windows;
|
|
|
|
registerMemoryHooks();
|
|
|
|
#ifdef _NO_LOCO_WIN32_
|
|
registerNoWin32Hooks();
|
|
#endif // _NO_LOCO_WIN32_
|
|
|
|
registerAudioHooks();
|
|
|
|
registerHook(
|
|
0x00431695,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
|
|
openloco::sub_431695(0);
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004416B5,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
using namespace openloco::environment;
|
|
|
|
auto buffer = (char*)0x009D0D72;
|
|
auto path = getPath((path_id)regs.ebx);
|
|
|
|
// TODO: use utility::strlcpy with the buffer size instead of std::strcpy, if possible
|
|
std::strcpy(buffer, path.make_preferred().u8string().c_str());
|
|
|
|
regs.ebx = (int32_t)buffer;
|
|
return 0;
|
|
});
|
|
|
|
// Replace ui::update() with our own
|
|
registerHook(
|
|
0x004524C1,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
ui::update();
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x00407BA3,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
auto cursor = (ui::cursor_id)regs.edx;
|
|
ui::setCursor(cursor);
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x00446F6B,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
auto result = promptOkCancel(regs.eax);
|
|
regs.eax = result ? 1 : 0;
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x00407231,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
openloco::input::sub_407231();
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x00451025,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
auto pos = gfx::drawString((gfx::drawpixelinfo_t*)regs.edi, regs.cx, regs.dx, regs.al, (uint8_t*)regs.esi);
|
|
regs = backup;
|
|
regs.cx = pos.x;
|
|
regs.dx = pos.y;
|
|
|
|
return 0;
|
|
});
|
|
|
|
// Until handling of input_state::viewport_left has been implemented in mouse_input...
|
|
registerHook(
|
|
0x00490F6C,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
ui::windows::station_list::open(regs.ax);
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004958C6,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
char* buffer = stringmgr::formatString((char*)regs.edi, regs.eax, (void*)regs.ecx);
|
|
regs = backup;
|
|
regs.edi = (uint32_t)buffer;
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004BA8D4,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
auto v = (openloco::vehicle_head*)regs.esi;
|
|
v->sub_4BA8D4();
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x00438A6C,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
gui::init();
|
|
return 0;
|
|
});
|
|
|
|
/* This can be removed after implementing signal place and remove game commands.
|
|
* It fixes an original bug with those two game commands.
|
|
*/
|
|
registerHook(
|
|
0x004A2AD7,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
addr<0x001135F88, uint16_t>() = 0;
|
|
regs.esi = 0x004A2AF0;
|
|
regs.edi = 0x004A2CE7;
|
|
call(0x004A2E46, regs);
|
|
|
|
// Set ebp register to a nice large non-negative number.
|
|
// This fixes exorbitant prices for signals on Linux and macOS.
|
|
// Value must be greater than the cost for placing a signal.
|
|
regs.ebp = 0xC0FFEE;
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004CA4DF,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
auto window = (ui::window*)regs.esi;
|
|
auto dpi = (gfx::drawpixelinfo_t*)regs.edi;
|
|
window->draw(dpi);
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004CF63B,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
gfx::render();
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
|
|
ui::prompt_browse::registerHooks();
|
|
ui::textinput::registerHooks();
|
|
ui::tooltip::registerHooks();
|
|
ui::vehicle::registerHooks();
|
|
ui::BuildVehicle::registerHooks();
|
|
ui::windows::terraform::registerHooks();
|
|
ui::windows::error::registerHooks();
|
|
ui::windows::construction::registerHooks();
|
|
ui::WindowManager::registerHooks();
|
|
ui::viewportmgr::registerHooks();
|
|
game_commands::registerHooks();
|
|
title::registerHooks();
|
|
tutorial::registerHooks();
|
|
|
|
// Part of 0x004691FA
|
|
registerHook(
|
|
0x0046959C,
|
|
[](registers& regs) -> uint8_t {
|
|
registers backup = regs;
|
|
int16_t x = regs.eax;
|
|
int16_t i = regs.ebx / 6;
|
|
int16_t y = regs.ecx;
|
|
openloco::map::surface_element* surface = (openloco::map::surface_element*)regs.esi;
|
|
|
|
surface->createWave(x, y, i);
|
|
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004AB655,
|
|
[](registers& regs) -> uint8_t {
|
|
auto v = (openloco::vehicle*)regs.esi;
|
|
v->asVehicleBody()->secondaryAnimationUpdate();
|
|
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004392BD,
|
|
[](registers& regs) -> uint8_t {
|
|
gui::resize();
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004C6456,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
auto window = (ui::window*)regs.esi;
|
|
window->viewportsUpdatePosition();
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004C9513,
|
|
[](registers& regs) -> uint8_t {
|
|
registers backup = regs;
|
|
auto window = (ui::window*)regs.esi;
|
|
int16_t x = regs.ax;
|
|
int16_t y = regs.bx;
|
|
|
|
auto widgetIndex = window->findWidgetAt(x, y);
|
|
|
|
regs = backup;
|
|
regs.edx = widgetIndex;
|
|
if (widgetIndex == -1)
|
|
{
|
|
regs.edi = (uintptr_t)&window->widgets[0];
|
|
}
|
|
else
|
|
{
|
|
regs.edi = (uintptr_t)&window->widgets[widgetIndex];
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004CA115,
|
|
[](registers& regs) -> uint8_t {
|
|
registers backup = regs;
|
|
auto window = (ui::window*)regs.esi;
|
|
window->updateScrollWidgets();
|
|
regs = backup;
|
|
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004CA17F,
|
|
[](registers& regs) -> uint8_t {
|
|
registers backup = regs;
|
|
auto window = (ui::window*)regs.esi;
|
|
window->initScrollWidgets();
|
|
regs = backup;
|
|
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004C57C0,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
|
|
initialiseViewports();
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
|
|
registerHook(
|
|
0x004C5DD5,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
registers backup = regs;
|
|
|
|
gfx::redrawScreenRect(regs.ax, regs.bx, regs.dx, regs.bp);
|
|
|
|
regs = backup;
|
|
return 0;
|
|
});
|
|
|
|
// Remove check for is road in use when removing roads. It is
|
|
// quite annoying when it's sometimes only the player's own
|
|
// vehicles that are using it.
|
|
writeNop(0x004776DD, 6);
|
|
}
|