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