diff --git a/CMakeLists.txt b/CMakeLists.txt index ad7363c22f..500481f1b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,12 +79,17 @@ option(STATIC "Create a static build.") option(FORCE64 "Force native (x86-64) build. Do not use, for experimental purposes only.") option(DISABLE_OPENGL "Disable OpenGL support.") option(DISABLE_RCT2 "WIP: Try building without using code and data segments from vanilla.") +option(USE_MMAP "Use mmap to try loading rct2's data segment into memory.") if (FORCE64) set(TARGET_M "-m64") set(OBJ_FORMAT "elf64-x86-64") set(LINKER_SCRIPT "ld_script_x86_64.xc") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=pointer-to-int-cast -Wno-error=int-to-pointer-cast") + if ((APPLE OR WIN32) AND NOT USE_MMAP) + message(WARNING "Building such configuration won't work. Enabling USE_MMAP.") + set(USE_MMAP ON) + endif() else () set(TARGET_M "-m32") set(OBJ_FORMAT "elf32-i386") @@ -98,6 +103,10 @@ else (DISABLE_OPENGL) add_definitions(-DOPENGL_NO_LINK) endif (DISABLE_OPENGL) +if (USE_MMAP) + add_definitions(-DUSE_MMAP) +endif (USE_MMAP) + if (DISABLE_NETWORK) add_definitions(-DDISABLE_NETWORK) else (DISABLE_NETWORK) @@ -146,35 +155,40 @@ if (UNIX) DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/openrct2.exe ) add_custom_target(segfiles DEPENDS openrct2_text openrct2_data) - if (APPLE) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -sectcreate rct2_text __text ${CMAKE_CURRENT_SOURCE_DIR}/build/openrct2_text -sectcreate rct2_data __data ${CMAKE_CURRENT_SOURCE_DIR}/build/openrct2_data -segaddr rct2_data 0x8a4000 -segprot rct2_data rwx rwx -segaddr rct2_text 0x401000 -segprot rct2_text rwx rwx -segaddr __TEXT 0x2000000 -fno-pie -read_only_relocs suppress") - else (APPLE) - # For Linux we have to use objcopy to wrap regular binaries into a linkable - # format. We use specific section names which are then referenced in a - # bespoke linker script so they can be placed at predefined VMAs. - add_custom_command( - OUTPUT openrct2_text_section.o - COMMAND objcopy --input binary --output ${OBJ_FORMAT} --binary-architecture i386 openrct2_text openrct2_text_section.o --rename-section .data=.rct2_text,contents,alloc,load,readonly,code - DEPENDS segfiles - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - add_custom_command( - OUTPUT openrct2_data_section.o - COMMAND objcopy --input binary --output ${OBJ_FORMAT} --binary-architecture i386 openrct2_data openrct2_data_section.o --rename-section .data=.rct2_data,contents,alloc,load,readonly,data - DEPENDS segfiles - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - ) - add_custom_target(linkable_sections DEPENDS openrct2_text_section.o openrct2_data_section.o) - SET_SOURCE_FILES_PROPERTIES( - openrct2_text_section.o openrct2_data_section.o - PROPERTIES - EXTERNAL_OBJECT true - GENERATED true - ) - # can't use GLOB here, as the files don't exist yet at cmake-time - set(RCT2_SECTIONS "${CMAKE_BINARY_DIR}/openrct2_data_section.o" "${CMAKE_BINARY_DIR}/openrct2_text_section.o") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-T,\"${CMAKE_CURRENT_SOURCE_DIR}/distribution/linux/${LINKER_SCRIPT}\"") - endif (APPLE) + if (NOT USE_MMAP) + if (APPLE) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -sectcreate rct2_text __text ${CMAKE_CURRENT_SOURCE_DIR}/build/openrct2_text -sectcreate rct2_data __data ${CMAKE_CURRENT_SOURCE_DIR}/build/openrct2_data -segaddr rct2_data 0x8a4000 -segprot rct2_data rwx rwx -segaddr rct2_text 0x401000 -segprot rct2_text rwx rwx -segaddr __TEXT 0x2000000 -fno-pie -read_only_relocs suppress") + else (APPLE) + # For Linux we have to use objcopy to wrap regular binaries into a linkable + # format. We use specific section names which are then referenced in a + # bespoke linker script so they can be placed at predefined VMAs. + add_custom_command( + OUTPUT openrct2_text_section.o + COMMAND objcopy --input binary --output ${OBJ_FORMAT} --binary-architecture i386 openrct2_text openrct2_text_section.o --rename-section .data=.rct2_text,contents,alloc,load,readonly,code + DEPENDS segfiles + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + add_custom_command( + OUTPUT openrct2_data_section.o + COMMAND objcopy --input binary --output ${OBJ_FORMAT} --binary-architecture i386 openrct2_data openrct2_data_section.o --rename-section .data=.rct2_data,contents,alloc,load,readonly,data + DEPENDS segfiles + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + add_custom_target(linkable_sections DEPENDS openrct2_text_section.o openrct2_data_section.o) + SET_SOURCE_FILES_PROPERTIES( + openrct2_text_section.o openrct2_data_section.o + PROPERTIES + EXTERNAL_OBJECT true + GENERATED true + ) + # can't use GLOB here, as the files don't exist yet at cmake-time + set(RCT2_SECTIONS "${CMAKE_BINARY_DIR}/openrct2_data_section.o" "${CMAKE_BINARY_DIR}/openrct2_text_section.o") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-T,\"${CMAKE_CURRENT_SOURCE_DIR}/distribution/linux/${LINKER_SCRIPT}\"") + endif (APPLE) + endif (NOT USE_MMAP) +elseif (USE_MMAP) + # No dd here, can't extract data segment + message(WARNING "Sorry, your platform is not supported, you have to extract data segment manually") endif (UNIX) set(DEBUG_LEVEL 0 CACHE STRING "Select debug level for compilation. Use value in range 0–3.") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDEBUG=${DEBUG_LEVEL}") @@ -268,7 +282,11 @@ endif (NOT DISABLE_RCT2) if (WIN32) # build as library for now, replace with add_executable - add_library(${PROJECT} SHARED ${ORCT2_SOURCES} ${SPEEX_SOURCES}) + if (USE_MMAP) + add_executable(${PROJECT} ${ORCT2_SOURCES} ${SPEEX_SOURCES}) + else () + add_library(${PROJECT} SHARED ${ORCT2_SOURCES} ${SPEEX_SOURCES}) + endif () else (WIN32) add_executable(${PROJECT} ${ORCT2_SOURCES} ${ORCT2_MM_SOURCES} ${RCT2_SECTIONS}) add_dependencies(${PROJECT} segfiles) diff --git a/src/addresses.h b/src/addresses.h index 98b02c8d69..658859b0ad 100644 --- a/src/addresses.h +++ b/src/addresses.h @@ -23,8 +23,14 @@ #pragma warning(disable : 4731) #endif -#define RCT2_ADDRESS(address, type) ((type*)(address)) -#define RCT2_GLOBAL(address, type) (*((type*)(address))) +#ifdef USE_MMAP + #define GOOD_PLACE_FOR_DATA_SEGMENT ((uintptr_t)0x200000000) +#else + #define GOOD_PLACE_FOR_DATA_SEGMENT ((uintptr_t)0x8a4000) +#endif + +#define RCT2_ADDRESS(address, type) ((type*)(GOOD_PLACE_FOR_DATA_SEGMENT - 0x8a4000 + (address))) +#define RCT2_GLOBAL(address, type) (*((type*)(GOOD_PLACE_FOR_DATA_SEGMENT - 0x8a4000 + (address)))) #pragma region Memory locations diff --git a/src/openrct2.c b/src/openrct2.c index f450fba13f..56316b3162 100644 --- a/src/openrct2.c +++ b/src/openrct2.c @@ -40,16 +40,18 @@ #include "version.h" #include "world/mapgen.h" -#if defined(__unix__) +#if defined(__unix__) || defined(__MACOSX__) #include #include #include #include #include #include -#endif // defined(__unix__) +#endif // defined(__unix__) || defined(__MACOSX__) int gExitCode; +int fdData; +void *segments = (void *)(GOOD_PLACE_FOR_DATA_SEGMENT); int gOpenRCT2StartupAction = STARTUP_ACTION_TITLE; utf8 gOpenRCT2StartupActionPath[512] = { 0 }; @@ -350,6 +352,10 @@ void openrct2_dispose() #ifndef DISABLE_NETWORK EVP_MD_CTX_destroy(gHashCTX); #endif // DISABLE_NETWORK +#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) + munmap(segments, 12079104); + close(fdData); +#endif platform_free(); } @@ -499,7 +505,10 @@ bool openrct2_setup_rct2_segment() { // OpenRCT2 on Linux and macOS is wired to have the original Windows PE sections loaded // necessary. Windows does not need to do this as OpenRCT2 runs as a DLL loaded from the Windows PE. -#if defined(__unix__) + int len = 0x01429000 - 0x8a4000; // 0xB85000, 12079104 bytes or around 11.5MB + int err; +#if defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) +#error #define RDATA_OFFSET 0x004A4000 #define DATASEG_OFFSET 0x005E2000 @@ -532,11 +541,27 @@ bool openrct2_setup_rct2_segment() // TODO: UGLY, UGLY HACK! //off_t file_size = 6750208; - int len = 0x01429000 - 0x8a4000; // 0xB85000, 12079104 bytes or around 11.5MB + fdData = open("openrct2_data", O_RDONLY); + if (fdData < 0) + { + log_fatal("failed to load openrct2_data"); + exit(1); + } + log_warning("%p", GOOD_PLACE_FOR_DATA_SEGMENT); + segments = mmap((void *)(GOOD_PLACE_FOR_DATA_SEGMENT), len, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE, fdData, 0); + log_warning("%p", segments); + if ((uintptr_t)segments != GOOD_PLACE_FOR_DATA_SEGMENT) { + perror("mmap"); + return false; + } +#endif // defined(USE_MMAP) && (defined(__unix__) || defined(__MACOSX__)) + +#if defined(__unix__) int pageSize = getpagesize(); int numPages = (len + pageSize - 1) / pageSize; unsigned char *dummy = malloc(numPages); - int err = mincore((void *)0x8a4000, len, dummy); + + err = mincore((void *)0x8a4000, len, dummy); bool pagesMissing = false; if (err != 0) { @@ -569,12 +594,14 @@ bool openrct2_setup_rct2_segment() { log_error("At least one of required pages was not found in memory. This can cause segfaults later on."); } +#if !defined(USE_MMAP) // section: text err = mprotect((void *)0x401000, 0x8a4000 - 0x401000, PROT_READ | PROT_EXEC); if (err != 0) { perror("mprotect"); } +#endif // !defined(USE_MMAP) // section: rw data err = mprotect((void *)0x8a4000, 0x01429000 - 0x8a4000, PROT_READ | PROT_WRITE); if (err != 0) @@ -583,12 +610,31 @@ bool openrct2_setup_rct2_segment() } #endif // defined(__unix__) -#if !defined(NO_RCT2) || !defined(__WINDOWS__) +#if defined(USE_MMAP) && defined(__WINDOWS__) + #error + segments = VirtualAlloc((void *)(GOOD_PLACE_FOR_DATA_SEGMENT), len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if ((uintptr_t)segments != GOOD_PLACE_FOR_DATA_SEGMENT) { + log_error("VirtualAlloc, segments = %p, GetLastError = 0x%x", segments, GetLastError()); + return false; + } + SDL_RWops * rw = SDL_RWFromFile("openrct2_data", "rb"); + if (rw == NULL) + { + log_error("failed to load file"); + return false; + } + if (SDL_RWread(rw, segments, len, 1) != 1) { + log_error("Unable to read chunk header!"); + return false; + } + SDL_RWclose(rw); +#endif // defined(USE_MMAP) && defined(__WINDOWS__) + // Check that the expected data is at various addresses. // Start at 0x9a6000, which is start of .data, to skip the region containing addresses to DLL // calls, which can be changed by windows/wine loader. - const uint32 c1 = sawyercoding_calculate_checksum((void *)0x009A6000, 0x009E0000 - 0x009A6000); - const uint32 c2 = sawyercoding_calculate_checksum((void *)0x01428000, 0x014282BC - 0x01428000); + const uint32 c1 = sawyercoding_calculate_checksum(segments + (uintptr_t)(0x009A6000 - 0x8a4000), 0x009E0000 - 0x009A6000); + const uint32 c2 = sawyercoding_calculate_checksum(segments + (uintptr_t)(0x01428000 - 0x8a4000), 0x014282BC - 0x01428000); const uint32 exp_c1 = 10114815; const uint32 exp_c2 = 23564; if (c1 != exp_c1 || c2 != exp_c2) { @@ -596,7 +642,6 @@ bool openrct2_setup_rct2_segment() log_warning("c2 = %u, expected %u, match %d", c2, exp_c2, c2 == exp_c2); return false; } -#endif return true; }