diff --git a/openloco.common.props b/openloco.common.props new file mode 100644 index 00000000..5418c30c --- /dev/null +++ b/openloco.common.props @@ -0,0 +1,108 @@ + + + + ..\..\ + + + + + + + $(DefaultPlatformToolset) + 10.0.14393.0 + + MultiByte + + $(SolutionDir)bin\ + $(SolutionDir)obj\$(ProjectName)\$(Configuration)_$(Platform)\ + $(ProjectName) + true + + + true + + + false + true + + + + + Level4 + 4068;4091;4100;4132;4200;4201;4204;4206;4221;4244;4245;%(DisableSpecificWarnings) + + 4263;4265;4548;4549;4555 + + _CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreaded + true + true + /utf-8 /std:c++latest /permissive- + + + imm32.lib;version.lib;winmm.lib;%(AdditionalDependencies) + /OPT:NOLBR /ignore:4099 %(AdditionalOptions) + + + + + Disabled + true + DEBUG;%(PreprocessorDefinitions) + false + + + true + UseFastLinkTimeCodeGeneration + + + + + Full + true + true + + + false + NDEBUG;%(PreprocessorDefinitions) + Speed + + + true + true + true + + + + + + + $(SolutionDir)src;$(SolutionDir)lib\include;$(SolutionDir)lib\include\sdl2;$(IncludePath) + $(SolutionDir)lib;$(LibraryPath) + + + + + + PerMonitorHighDPIAware + + + diff --git a/openloco.sln b/openloco.sln new file mode 100644 index 00000000..883b23bf --- /dev/null +++ b/openloco.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2010 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openloco", "src\openloco\openloco.vcxproj", "{42A6B551-4EC5-4B66-A130-628622CD98C4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D35A17D9-494B-43DB-8008-B47DC58227DD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {42A6B551-4EC5-4B66-A130-628622CD98C4}.Debug|x86.ActiveCfg = Debug|Win32 + {42A6B551-4EC5-4B66-A130-628622CD98C4}.Debug|x86.Build.0 = Debug|Win32 + {42A6B551-4EC5-4B66-A130-628622CD98C4}.Release|x86.ActiveCfg = Release|Win32 + {42A6B551-4EC5-4B66-A130-628622CD98C4}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {42A6B551-4EC5-4B66-A130-628622CD98C4} = {D35A17D9-494B-43DB-8008-B47DC58227DD} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DBA01ADD-F7E5-4C06-AA84-168F663E5A81} + EndGlobalSection +EndGlobal diff --git a/src/openloco/interop/interop.cpp b/src/openloco/interop/interop.cpp new file mode 100644 index 00000000..33722dfa --- /dev/null +++ b/src/openloco/interop/interop.cpp @@ -0,0 +1,266 @@ +#include +#include "interop.hpp" + +registers::registers() +{ + // We set registers to known undefined values so we are easily aware when + // code is attempting to use undefined registers. + std::memset(this, 0xCC, sizeof(registers)); +} + +#pragma warning(disable : 4731) // frame pointer register 'ebp' modified by inline assembly code +#define PLATFORM_X86 + +#if defined(__GNUC__) +#ifdef __clang__ +#define DISABLE_OPT __attribute__((noinline,optnone)) +#else +#define DISABLE_OPT __attribute__((noinline,optimize("O0"))) +#endif // __clang__ +#else +#define DISABLE_OPT +#endif // defined(__GNUC__) + +// This variable serves a purpose of identifying a crash if it has happened inside original code. +// When switching to original code, stack frame pointer is modified and prevents breakpad from providing stack trace. +volatile int32_t _originalAddress = 0; + +int32_t DISABLE_OPT LOCO_CALLPROC_X(int32_t address, int32_t _eax, int32_t _ebx, int32_t _ecx, int32_t _edx, int32_t _esi, int32_t _edi, int32_t _ebp) +{ + int32_t result = 0; + _originalAddress = address; +#if defined(PLATFORM_X86) +#ifdef _MSC_VER + __asm { + push ebp + push address + mov eax, _eax + mov ebx, _ebx + mov ecx, _ecx + mov edx, _edx + mov esi, _esi + mov edi, _edi + mov ebp, _ebp + call[esp] + lahf + pop ebp + pop ebp + /* Load result with flags */ + mov result, eax + } +#else + __asm__ volatile ("\ + \n\ + push %%ebx \n\ + push %%ebp \n\ + push %[address] \n\ + mov %[eax], %%eax \n\ + mov %[ebx], %%ebx \n\ + mov %[ecx], %%ecx \n\ + mov %[edx], %%edx \n\ + mov %[esi], %%esi \n\ + mov %[edi], %%edi \n\ + mov %[ebp], %%ebp \n\ + call *(%%esp) \n\ + lahf \n\ + add $4, %%esp \n\ + pop %%ebp \n\ + pop %%ebx \n\ + /* Load result with flags */ \n\ + mov %%eax, %[result] \n\ + " : [address] "+m" (address), [eax] "+m" (_eax), [ebx] "+m" (_ebx), [ecx] "+m" (_ecx), [edx] "+m" (_edx), [esi] "+m" (_esi), [edi] "+m" (_edi), [ebp] "+m" (_ebp), [result] "+m" (result) + : + : "eax", "ecx", "edx", "esi", "edi", "memory" + ); +#endif +#endif // PLATFORM_X86 + _originalAddress = 0; + // lahf only modifies ah, zero out the rest + return result & 0xFF00; +} + +int32_t DISABLE_OPT LOCO_CALLFUNC_X(int32_t address, int32_t *_eax, int32_t *_ebx, int32_t *_ecx, int32_t *_edx, int32_t *_esi, int32_t *_edi, int32_t *_ebp) +{ + int32_t result = 0; + _originalAddress = address; +#if defined(PLATFORM_X86) +#ifdef _MSC_VER + __asm { + // Store C's base pointer + push ebp + push ebx + // Store address to call + push address + + // Set all registers to the input values + mov eax, [_eax] + mov eax, [eax] + mov ebx, [_ebx] + mov ebx, [ebx] + mov ecx, [_ecx] + mov ecx, [ecx] + mov edx, [_edx] + mov edx, [edx] + mov esi, [_esi] + mov esi, [esi] + mov edi, [_edi] + mov edi, [edi] + mov ebp, [_ebp] + mov ebp, [ebp] + + // Call function + call[esp] + + // Store output eax + push eax + push ebp + push ebx + mov ebp, [esp + 20] + mov ebx, [esp + 16] + + // Get resulting ecx, edx, esi, edi registers + + mov eax, [_edi] + mov[eax], edi + mov eax, [_esi] + mov[eax], esi + mov eax, [_edx] + mov[eax], edx + mov eax, [_ecx] + mov[eax], ecx + + // Pop ebx reg into ecx + pop ecx + mov eax, [_ebx] + mov[eax], ecx + + // Pop ebp reg into ecx + pop ecx + mov eax, [_ebp] + mov[eax], ecx + + pop eax + // Get resulting eax register + mov ecx, [_eax] + mov[ecx], eax + + // Save flags as return in eax + lahf + // Pop address + pop ebp + + pop ebx + pop ebp + /* Load result with flags */ + mov result, eax + } +#else + __asm__ volatile ("\ + \n\ + /* Store C's base pointer*/ \n\ + push %%ebp \n\ + push %%ebx \n\ + \n\ + /* Store %[address] to call*/ \n\ + push %[address] \n\ + \n\ + /* Set all registers to the input values*/ \n\ + mov %[_eax], %%eax \n\ + mov (%%eax), %%eax \n\ + mov %[_ebx], %%ebx \n\ + mov (%%ebx), %%ebx \n\ + mov %[_ecx], %%ecx \n\ + mov (%%ecx), %%ecx \n\ + mov %[_edx], %%edx \n\ + mov (%%edx), %%edx \n\ + mov %[_esi], %%esi \n\ + mov (%%esi), %%esi \n\ + mov %[_edi], %%edi \n\ + mov (%%edi), %%edi \n\ + mov %[_ebp], %%ebp \n\ + mov (%%ebp), %%ebp \n\ + \n\ + /* Call function*/ \n\ + call *(%%esp) \n\ + \n\ + /* Store output eax */ \n\ + push %%eax \n\ + push %%ebp \n\ + push %%ebx \n\ + mov 20(%%esp), %%ebp \n\ + mov 16(%%esp), %%ebx \n\ + /* Get resulting ecx, edx, esi, edi registers*/ \n\ + mov %[_edi], %%eax \n\ + mov %%edi, (%%eax) \n\ + mov %[_esi], %%eax \n\ + mov %%esi, (%%eax) \n\ + mov %[_edx], %%eax \n\ + mov %%edx, (%%eax) \n\ + mov %[_ecx], %%eax \n\ + mov %%ecx, (%%eax) \n\ + /* Pop ebx reg into ecx*/ \n\ + pop %%ecx \n\ + mov %[_ebx], %%eax \n\ + mov %%ecx, (%%eax) \n\ + \n\ + /* Pop ebp reg into ecx */\n\ + pop %%ecx \n\ + mov %[_ebp], %%eax \n\ + mov %%ecx, (%%eax) \n\ + \n\ + pop %%eax \n\ + /* Get resulting eax register*/ \n\ + mov %[_eax], %%ecx \n\ + mov %%eax, (%%ecx) \n\ + \n\ + /* Save flags as return in eax*/ \n\ + lahf \n\ + /* Pop address*/ \n\ + pop %%ebp \n\ + \n\ + pop %%ebx \n\ + pop %%ebp \n\ + /* Load result with flags */ \n\ + mov %%eax, %[result] \n\ + " : [address] "+m" (address), [_eax] "+m" (_eax), [_ebx] "+m" (_ebx), [_ecx] "+m" (_ecx), [_edx] "+m" (_edx), [_esi] "+m" (_esi), [_edi] "+m" (_edi), [_ebp] "+m" (_ebp), [result] "+m" (result) + + : + : "eax", "ecx", "edx", "esi", "edi", "memory" + ); +#endif +#endif // PLATFORM_X86 + _originalAddress = 0; + // lahf only modifies ah, zero out the rest + return result & 0xFF00; +} + +int32_t LOCO_CALLPROC_X(int32_t address, const registers ®isters) +{ + return LOCO_CALLPROC_X( + address, + registers.eax, + registers.ebx, + registers.ecx, + registers.edx, + registers.esi, + registers.edi, + registers.ebp); +} + +int32_t LOCO_CALLPROC_X(int32_t address) +{ + return LOCO_CALLPROC_X(address, registers()); +} + +int32_t LOCO_CALLFUNC_X(int32_t address, registers ®isters) +{ + return LOCO_CALLFUNC_X( + address, + ®isters.eax, + ®isters.ebx, + ®isters.ecx, + ®isters.edx, + ®isters.esi, + ®isters.edi, + ®isters.ebp); +} diff --git a/src/openloco/interop/interop.hpp b/src/openloco/interop/interop.hpp new file mode 100644 index 00000000..e0812a3d --- /dev/null +++ b/src/openloco/interop/interop.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include + +#define assert_struct_size(x, y) static_assert(sizeof(x) == (y), "Improper struct size") + +/** +* x86 register structure, only used for easy interop to Locomotion code. +*/ +#pragma pack(push, 1) +struct registers +{ + union + { + int32_t eax; + int16_t ax; + struct + { + int8_t al; + int8_t ah; + }; + }; + union + { + int32_t ebx; + int16_t bx; + struct + { + int8_t bl; + int8_t bh; + }; + }; + union + { + int32_t ecx; + int16_t cx; + struct + { + int8_t cl; + int8_t ch; + }; + }; + union + { + int32_t edx; + int16_t dx; + struct + { + int8_t dl; + int8_t dh; + }; + }; + union + { + int32_t esi; + int16_t si; + }; + union + { + int32_t edi; + int16_t di; + }; + union + { + int32_t ebp; + int16_t bp; + }; + + registers(); +}; +assert_struct_size(registers, 7 * 4); +#pragma pack(pop) + +#ifdef USE_MMAP + #if defined(PLATFORM_64BIT) + #define GOOD_PLACE_FOR_DATA_SEGMENT ((uintptr_t)0x200000000) + #elif defined(PLATFORM_32BIT) + #define GOOD_PLACE_FOR_DATA_SEGMENT ((uintptr_t)0x09000000) + #else + #error "Unknown platform" + #endif +#else + #define GOOD_PLACE_FOR_DATA_SEGMENT ((uintptr_t)0x8A4000) +#endif + +#define LOCO_ADDRESS(address, type) ((type*)(GOOD_PLACE_FOR_DATA_SEGMENT - 0x8A4000 + (address))) +#define LOCO_GLOBAL(address, type) (*((type*)(GOOD_PLACE_FOR_DATA_SEGMENT - 0x8A4000 + (address)))) + +/** +* Returns the flags register +* +* Flags register is as follows: +* 0bSZ0A_0P0C_0000_0000 +* S = Signed flag +* Z = Zero flag +* C = Carry flag +* A = Adjust flag +* P = Parity flag +* All other bits are undefined. +*/ +int32_t LOCO_CALLPROC_X(int32_t address, int32_t _eax, int32_t _ebx, int32_t _ecx, int32_t _edx, int32_t _esi, int32_t _edi, int32_t _ebp); +int32_t LOCO_CALLPROC_X(int32_t address, const registers ®isters); +int32_t LOCO_CALLPROC_X(int32_t address); + +/** + * Returns the flags register + * + * Flags register is as follows: + * 0bSZ0A_0P0C_0000_00000 + * S = Signed flag + * Z = Zero flag + * C = Carry flag + * A = Adjust flag + * P = Parity flag + * All other bits are undefined. + */ +int32_t LOCO_CALLFUNC_X(int32_t address, int32_t *_eax, int32_t *_ebx, int32_t *_ecx, int32_t *_edx, int32_t *_esi, int32_t *_edi, int32_t *_ebp); +int32_t LOCO_CALLFUNC_X(int32_t address, registers ®isters); + +template +struct loco_global +{ + void operator=(T rhs) + { + LOCO_GLOBAL(TAddress, T) = rhs; + } + + operator T() + { + return LOCO_GLOBAL(TAddress, T); + } +}; diff --git a/src/openloco/openloco.cpp b/src/openloco/openloco.cpp new file mode 100644 index 00000000..0f95405a --- /dev/null +++ b/src/openloco/openloco.cpp @@ -0,0 +1,111 @@ +#include +#define WIN32_LEAN_AND_MEAN +#include +#include "interop\interop.hpp" + +namespace openloco +{ + constexpr auto WINDOW_CLASS_NAME = "Chris Sawyer's Locomotion"; + constexpr auto WINDOW_TITLE = "OpenLoco"; + + loco_global ghInstance; + loco_global glpCmdLine; + loco_global gMainHWND; + + // 0x00405409 + HWND create_game_window() + { + return CreateWindowExA( + WS_EX_TOPMOST, + WINDOW_CLASS_NAME, + WINDOW_TITLE, + WS_POPUP | WS_VISIBLE | WS_SYSMENU | WS_CLIPCHILDREN | WS_MAXIMIZE | WS_CLIPSIBLINGS, + 0, + 0, + GetSystemMetrics(SM_CXSCREEN), + GetSystemMetrics(SM_CYSCREEN), + nullptr, + nullptr, + ghInstance, + nullptr); + } + + bool sub_4054B9() + { + registers regs; + LOCO_CALLFUNC_X(0x004054B9, regs); + return regs.eax != 0; + } + + /** + * Use this to allocate memory that will be freed in vanilla code or via loco_free. + */ + void * malloc(size_t size) + { + return ((void *(*)(size_t))0x004D1401)(size); + } + + /** + * Use this to reallocate memory that will be freed in vanilla code or via loco_free. + */ + void * realloc(void * address, size_t size) + { + return ((void *(*)(void *, size_t))0x004D1B28)(address, size); + } + + /** + * Use this to free up memory allocated in vanilla code or via loco_malloc / loco_realloc. + */ + void free(void * address) + { + ((void(*)(void *))0x004D1355)(address); + } + + void sub_404E58() + { + free(LOCO_GLOBAL(0x005251F4, void *)); + LOCO_GLOBAL(0x005251F4, void *) = nullptr; + LOCO_GLOBAL(0x005251F0, void *) = nullptr; + LOCO_CALLPROC_X(0x00404ACD); + LOCO_CALLPROC_X(0x00404B40); + } + + // 0x00406386 + void openloco_run() + { + LOCO_CALLPROC_X(0x00406386); + } + + // 0x00406D13 + void main() + { + if (sub_4054B9()) + { + gMainHWND = create_game_window(); + LOCO_CALLPROC_X(0x004078FE); + LOCO_CALLPROC_X(0x00407B26); + LOCO_CALLPROC_X(0x0040447F); + LOCO_CALLPROC_X(0x00404E53); + openloco_run(); + LOCO_CALLPROC_X(0x00404E58); + LOCO_CALLPROC_X(0x004045C2); + + // TODO extra clean up code + } + } +} + +extern "C" +{ + /** + * The function that is called directly from the host application (loco.exe)'s WinMain. This will be removed when OpenLoco can + * be built as a stand alone application. + */ + __declspec(dllexport) int StartOpenLoco(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) + { + openloco::glpCmdLine = lpCmdLine; + openloco::ghInstance = hInstance; + openloco::main(); + return 0; + } +} diff --git a/src/openloco/openloco.vcxproj b/src/openloco/openloco.vcxproj new file mode 100644 index 00000000..66247114 --- /dev/null +++ b/src/openloco/openloco.vcxproj @@ -0,0 +1,43 @@ + + + + ..\..\ + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + {42A6B551-4EC5-4B66-A130-628622CD98C4} + openloco + openloco + + + DynamicLibrary + + + + + $(IntDir)\%(RelativeDir) + $(OPENLOCO_CL_ADDITIONALOPTIONS) %(AdditionalOptions) + + + MachineX86 + MachineX64 + + + + \ No newline at end of file