Add initial working wrapper

This commit is contained in:
Ted John 2018-01-06 23:42:48 +00:00
parent 4b05449c7c
commit da918e59da
6 changed files with 690 additions and 0 deletions

108
openloco.common.props Normal file
View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)'==''">..\..\</SolutionDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup>
<!-- Allow any version of VS and Windows SDK -->
<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
<TargetPlatformVersion>10.0.14393.0</TargetPlatformVersion>
<CharacterSet>MultiByte</CharacterSet>
<OutDir>$(SolutionDir)bin\</OutDir>
<IntDir>$(SolutionDir)obj\$(ProjectName)\$(Configuration)_$(Platform)\</IntDir>
<TargetName>$(ProjectName)</TargetName>
<ShowAllFiles>true</ShowAllFiles>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<DisableSpecificWarnings>4068;4091;4100;4132;4200;4201;4204;4206;4221;4244;4245;%(DisableSpecificWarnings)</DisableSpecificWarnings>
<!-- Warnings:
C4068: unknown pragma
C4091: 'keyword': ignored on left of 'type' when no variable is declared
C4100: 'identifier': unreferenced formal parameter
C4132: 'identifier': const object should be initialized
C4200: nonstandard extension used: zero-sized array in struct/union
C4201: nonstandard extension used: nameless struct/union
C4204: nonstandard extension used: non-constant aggregate initializer
C4206: nonstandard extension used: translation unit is empty
C4221: nonstandard extension used: 'identifier': cannot be initialized using address of automatic variable 'identifier'
C4244: 'conversion_type': conversion from 'type1' to 'type2', possible loss of data
C4245: 'conversion_type': conversion from 'type1' to 'type2', signed/unsigned mismatch
-->
<TreatSpecificWarningsAsErrors>4263;4265;4548;4549;4555</TreatSpecificWarningsAsErrors>
<!-- Warnings, that have to be enabled manually:
C4263: 'function': member function does not override any base class virtual member function
C4265: 'class': class has virtual functions, but destructor is not virtual
C4548: expression before comma has no effect; expected expression with side-effect
C4549: 'operator': operator before comma has no effect; did you intend 'operator'?
C4555: expression has no effect; expected expression with side-effect
-->
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<TreatWarningAsError>true</TreatWarningAsError>
<AdditionalOptions>/utf-8 /std:c++latest /permissive-</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalDependencies>imm32.lib;version.lib;winmm.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalOptions>/OPT:NOLBR /ignore:4099 %(AdditionalOptions)</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>false</MinimalRebuild>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<LinkTimeCodeGeneration>UseFastLinkTimeCodeGeneration</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<Optimization>Full</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck />
<OmitFramePointers />
<BufferSecurityCheck>false</BufferSecurityCheck>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<IncludePath>$(SolutionDir)src;$(SolutionDir)lib\include;$(SolutionDir)lib\include\sdl2;$(IncludePath)</IncludePath>
<LibraryPath>$(SolutionDir)lib;$(LibraryPath)</LibraryPath>
<LinkIncremental />
</PropertyGroup>
<ItemDefinitionGroup>
<Manifest>
<EnableDpiAwareness>PerMonitorHighDPIAware</EnableDpiAwareness>
</Manifest>
</ItemDefinitionGroup>
</Project>

30
openloco.sln Normal file
View File

@ -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

View File

@ -0,0 +1,266 @@
#include <memory>
#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 &registers)
{
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 &registers)
{
return LOCO_CALLFUNC_X(
address,
&registers.eax,
&registers.ebx,
&registers.ecx,
&registers.edx,
&registers.esi,
&registers.edi,
&registers.ebp);
}

View File

@ -0,0 +1,132 @@
#pragma once
#include <cstdint>
#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 &registers);
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 &registers);
template<typename T, uint32_t TAddress>
struct loco_global
{
void operator=(T rhs)
{
LOCO_GLOBAL(TAddress, T) = rhs;
}
operator T()
{
return LOCO_GLOBAL(TAddress, T);
}
};

111
src/openloco/openloco.cpp Normal file
View File

@ -0,0 +1,111 @@
#include <string>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "interop\interop.hpp"
namespace openloco
{
constexpr auto WINDOW_CLASS_NAME = "Chris Sawyer's Locomotion";
constexpr auto WINDOW_TITLE = "OpenLoco";
loco_global<HINSTANCE, 0x0113E0B4> ghInstance;
loco_global<LPSTR, 0x00525348> glpCmdLine;
loco_global<HWND, 0x00525320> 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;
}
}

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)'==''">..\..\</SolutionDir>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClCompile Include="interop\interop.cpp" />
<ClCompile Include="openloco.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="interop\interop.hpp" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{42A6B551-4EC5-4B66-A130-628622CD98C4}</ProjectGuid>
<RootNamespace>openloco</RootNamespace>
<ProjectName>openloco</ProjectName>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
</PropertyGroup>
<Import Project="..\..\openloco.common.props" />
<ItemDefinitionGroup>
<ClCompile>
<ObjectFileName>$(IntDir)\%(RelativeDir)</ObjectFileName>
<AdditionalOptions>$(OPENLOCO_CL_ADDITIONALOPTIONS) %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Lib>
<TargetMachine Condition="'$(Platform)'=='Win32'">MachineX86</TargetMachine>
<TargetMachine Condition="'$(Platform)'=='x64'">MachineX64</TargetMachine>
</Lib>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>