Make it work on Linux

Right now the project is decompiled to the point where it is feasible to
try porting it to another platform. It doesn't work 100% correctly, but
it's nearing this state.

To port it to Linux I mmapped the openrct2.exe into expected places,
disabled two offending calls (RCT2_CALLPROC_EBPSAFE(0x0040701D) and
RCT2_CALLPROC_X(0x006E7FF3…)), replaced memory management routines
with generic ones and removed all the function-pointer calls.

A basic, non-exhaustive check is included to verify that memory is
loaded correctly in place.

That last bit is probably the most intrusive one, but had to be done, as
the calling convention on Linux differs from the one on Windows. It
could possibly be emulated (just like RCT2_CALLFUNC_X) until dependency
on exe is dropped.

It is possible to completely remove calls out to original code by
commenting out contents of RCT2_CALLFUNC_X, right now this will yield
working UI, but no rendering of peeps, rides or rest of world. This can
be used as a benchmark or test platform for correctness of
implementation. The data sections will still be required for now.

Assets are expected to be in specific places, so to launch it, following
needs to satisified:
* $build/data/ has to have contents of $RCT2/Data/
* $build/data/ (same as above) has to have contents of $repo/data/
* $build/ObjData/ has to have contents of $RCT2/ObjData/
* $build/../openrct2.exe has to be $repo/openrct2.exe (as of 976ea0d)
Keep in mind you can symlink stuff and that filesystems are case
sensitive!
You can copy more of required data to possibly improve your experience.

Pretty much all of this commit will possibly have to be reverted by the
time OpenRCT2 gains independence.

Remember to build with -DDISABLE_NETWORK=ON -DDISABLE_HTTP_TWITCH=ON
This commit is contained in:
Michał Janiszewski 2015-09-22 23:36:05 +02:00
parent a5d85cd15f
commit 1bd8e11c0f
8 changed files with 128 additions and 2 deletions

View File

@ -27,6 +27,7 @@
#define RCT2_ADDRESS(address, type) ((type*)(address))
#define RCT2_GLOBAL(address, type) (*((type*)(address)))
#ifdef _WIN32
#define RCT2_CALLPROC(address) (((void(*)())(address))())
#define RCT2_CALLFUNC(address, returnType) (((returnType(*)())(address))())
@ -36,6 +37,16 @@
#define RCT2_CALLFUNC_4(address, returnType, a1, a2, a3, a4, v1, v2, v3, v4) (((returnType(*)(a1, a2, a3, a4))(address))(v1, v2, v3, v4))
#define RCT2_CALLFUNC_5(address, returnType, a1, a2, a3, a4, a5, v1, v2, v3, v4, v5) (((returnType(*)(a1, a2, a3, a4, a5))(address))(v1, v2, v3, v4, v5))
#define RCT2_CALLFUNC_6(address, returnType, a1, a2, a3, a4, a5, a6, v1, v2, v3, v4, v5, v6) (((returnType(*)(a1, a2, a3, a4, a5, a6))(address))(v1, v2, v3, v4, v5, v6))
#else
#define RCT2_CALLPROC(address)
#define RCT2_CALLFUNC(address, returnType)
#define RCT2_CALLFUNC_1(address, returnType, a1, v1)
#define RCT2_CALLFUNC_2(address, returnType, a1, a2, v1, v2)
#define RCT2_CALLFUNC_3(address, returnType, a1, a2, a3, v1, v2, v3)
#define RCT2_CALLFUNC_4(address, returnType, a1, a2, a3, a4, v1, v2, v3, v4)
#define RCT2_CALLFUNC_5(address, returnType, a1, a2, a3, a4, a5, v1, v2, v3, v4, v5)
#define RCT2_CALLFUNC_6(address, returnType, a1, a2, a3, a4, a5, a6, v1, v2, v3, v4, v5, v6)
#endif // _WIN32
#define RCT2_CALLPROC_1(address, a1, v1) RCT2_CALLFUNC_1(address, void, a1, v1)
#define RCT2_CALLPROC_2(address, a1, a2, v1, v2) RCT2_CALLFUNC_2(address, void, a1, a2, v1, v2)

View File

@ -545,9 +545,13 @@ Channel* Mixer::Play(Source& source, int loop, bool deleteondone, bool deletesou
void Mixer::Stop(Channel& channel)
{
#ifdef _WIN32
Lock();
channel.stopping = true;
Unlock();
#else
#warning unimplemented
#endif // _WIN32
}
bool Mixer::LoadMusic(int pathid)

View File

@ -271,7 +271,11 @@ void sub_689174(sint16* x, sint16* y, sint16 *z)
void sub_6E7FF3(rct_window *w, rct_viewport *viewport, int x, int y)
{
#ifdef _WIN32
RCT2_CALLPROC_X(0x006E7FF3, 0, 0, 0, x, (int)viewport, (int)w, y);
#else
STUB();
#endif // _WIN32
// int zoom = 1 << viewport->zoom;
// if (w >= RCT2_GLOBAL(RCT2_ADDRESS_NEW_WINDOW_PTR, rct_window*)){

View File

@ -2420,7 +2420,11 @@ void textinput_cancel()
// The following code is only necessary for the old Windows text input dialog. In theory this isn't used anymore, but can
// still be triggered via original code paths.
#ifdef _WIN32
RCT2_CALLPROC_EBPSAFE(0x0040701D);
#else
log_warning("there should be something called here (0x0040701D)");
#endif // _WIN32
if (RCT2_GLOBAL(0x009DEB8C, uint8) != 255) {
RCT2_CALLPROC_EBPSAFE(0x006EE4E2);
w = window_find_by_number(

View File

@ -855,7 +855,7 @@ int win1252_to_utf8(utf8string dst, const char *src, int maxBufferLength)
MultiByteToWideChar(CP_ACP, 0, src, -1, intermediateBuffer, bufferCount);
int result = WideCharToMultiByte(CP_UTF8, 0, intermediateBuffer, -1, dst, maxBufferLength, NULL, NULL);
#else
STUB();
//STUB();
// we cannot walk past maxBufferLength, but in case we have still space left
// we need one byte for null terminator
int result = strnlen(src, maxBufferLength) + 1;

View File

@ -40,6 +40,15 @@
#include "util/util.h"
#include "world/mapgen.h"
#ifdef __linux__
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#endif // __linux__
int gOpenRCT2StartupAction = STARTUP_ACTION_TITLE;
utf8 gOpenRCT2StartupActionPath[512] = { 0 };
utf8 gExePath[MAX_PATH];
@ -175,6 +184,86 @@ bool openrct2_initialise()
return false;
}
#ifdef __linux__
#define DATA_OFFSET 0x004A4000
const char *exepath = "../openrct2.exe";
int fd = open(exepath, O_RDONLY);
if (fd < 0) {
log_fatal("failed to open %s, errno = %d", exepath, errno);
exit(1);
}
// Using PE-bear I was able to figure out all the needed addresses to be filled.
// There are three sections to be loaded: .rdata, .data and .text, plus another
// one to be mapped: DATASEG.
// Out of the three, two can simply be mmapped into memory, while the third one,
// .data has a virtual size which is much completely different to its file size
// (even when taking page-alignment into consideration)
//
// The sections are as follows (dump from gdb)
// [0] 0x401000->0x6f7000 at 0x00001000: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
// [1] 0x6f7000->0x8a325d at 0x002f7000: CODESEG ALLOC LOAD READONLY CODE HAS_CONTENTS
// [2] 0x8a4000->0x9a5894 at 0x004a4000: .rdata ALLOC LOAD DATA HAS_CONTENTS
// [3] 0x9a6000->0x9e2000 at 0x005a6000: .data ALLOC LOAD DATA HAS_CONTENTS
// [4] 0x1428000->0x14282bc at 0x005e2000: DATASEG ALLOC LOAD DATA HAS_CONTENTS
// [5] 0x1429000->0x1452000 at 0x005e3000: .cms_t ALLOC LOAD READONLY CODE HAS_CONTENTS
// [6] 0x1452000->0x14aaf3e at 0x0060c000: .cms_d ALLOC LOAD DATA HAS_CONTENTS
// [7] 0x14ab000->0x14ac58a at 0x00665000: .idata ALLOC LOAD READONLY DATA HAS_CONTENTS
// [8] 0x14ad000->0x14b512f at 0x00667000: .rsrc ALLOC LOAD DATA HAS_CONTENTS
//
// .data section, however, has virtual size of 0xA81C3C, and so
// 0x9a6000 + 0xA81C3C = 0x1427C3C, which after alignment to page size becomes
// 0x1428000, which can be seen as next section, DATASEG
//
// Since mmap does not provide a way to create a mapping with virtual size,
// I resorted to creating a one large map for data and memcpy'ing data where
// required.
// Another section is needed for .text, as it requires PROT_EXEC flag.
// TODO: UGLY, UGLY HACK!
off_t file_size = 6750208;
int len = 0x01429000 - 0x8a4000; // 0xB85000, 12079104 bytes or around 11.5MB
// section: rw data
void *base = mmap((void *)0x8a4000, len, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0);
log_warning("base = %x, 0x01423b40 >= base == %i, 0x01423b40 < base + len == %i", base, (void *)0x01423b40 >= base, (void *)0x01423b40 < base + len);
if (base == MAP_FAILED) {
log_warning("errno = %i", errno);
exit(1);
}
len = 0x004A3000;
// section: text
void *base2 = mmap((void *)(0x401000), len, PROT_EXEC | PROT_WRITE | PROT_READ, MAP_PRIVATE, fd, 0x1000);
if (base2 != (void *)(0x401000))
{
log_fatal("mmap failed to get required offset! got %p, expected %p, errno = %d", base2, (void *)(0x401000), errno);
exit(1);
}
void *fbase = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
int err = errno;
log_warning("mmapped file to %p", fbase);
if (fbase == MAP_FAILED)
{
log_fatal("mmap failed to get required offset! got %p, errno = %d", fbase, err);
exit(1);
}
// .rdata and real part of .data
// 0x9e2000 - 0x8a4000 = 0x13e000
memcpy(base, fbase + DATA_OFFSET, 0x13e000);
#endif // __linux__
const uint32 c1 = sawyercoding_calculate_checksum((void *)0x009ACFA4, 128);
const uint32 c2 = sawyercoding_calculate_checksum((void *)0x009ACFA4, 720 * 4);
const uint32 exp_c1 = 32640;
const uint32 exp_c2 = 734400;
log_warning("c1 = %u, expected %u, match %d", c1, exp_c1, c1 == exp_c1);
log_warning("c1 = %u, expected %u, match %d", c2, exp_c2, c2 == exp_c2);
if (c1 != exp_c1 || c2 != exp_c2)
{
exit(1);
}
openrct2_set_exe_path();
config_set_defaults();

View File

@ -517,7 +517,6 @@ void platform_show_cursor()
void platform_get_cursor_position(int *x, int *y)
{
STUB();
}

View File

@ -567,7 +567,12 @@ void get_local_time()
*/
void *rct2_malloc(size_t numBytes)
{
#ifdef _WIN32
return RCT2_CALLFUNC_1(0x004068B2, void*, size_t, numBytes);
#else
//log_warning("call rct's function");
return malloc(numBytes);
#endif // _WIN32
}
/**
@ -577,7 +582,12 @@ void *rct2_malloc(size_t numBytes)
*/
void *rct2_realloc(void *block, size_t numBytes)
{
#ifdef _WIN32
return RCT2_CALLFUNC_2(0x004068BD, void*, void*, size_t, block, numBytes);
#else
//log_warning("call rct's function");
return realloc(block, numBytes);
#endif // _WIN32
}
/**
@ -586,5 +596,10 @@ void *rct2_realloc(void *block, size_t numBytes)
*/
void rct2_free(void *block)
{
#ifdef _WIN32
RCT2_CALLPROC_1(0x004068CD, void*, block);
#else
//log_warning("call rct's function");
free(block);
#endif // _WIN32
}