diff --git a/projects/openrct2.vcxproj.user b/projects/openrct2.vcxproj.user index 2b27afde74..1846f60e7b 100644 --- a/projects/openrct2.vcxproj.user +++ b/projects/openrct2.vcxproj.user @@ -4,8 +4,7 @@ $(TargetDir) WindowsLocalDebugger $(TargetDir)\openrct2.exe - - + screenshot "C:\Program Files (x86)\Infogrames\RollerCoaster Tycoon 2\Scenarios\Six Flags Magic Mountain.SC6" "test.png" 1920 1080 false diff --git a/src/cmdline.c b/src/cmdline.c index 6779415e3c..013af496f2 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -22,6 +22,7 @@ #include #include "addresses.h" #include "cmdline.h" +#include "interface/screenshot.h" #include "openrct2.h" #include "platform/platform.h" #include "util/util.h" @@ -138,7 +139,8 @@ static int cmdline_for_none(const char **argv, int argc) struct { const char *firstArg; cmdline_action action; } cmdline_table[] = { { "intro", cmdline_for_intro }, { "edit", cmdline_for_edit }, - { "sprite", cmdline_for_sprite } + { "sprite", cmdline_for_sprite }, + { "screenshot", cmdline_for_screenshot } }; static int cmdline_call_action(const char **argv, int argc) diff --git a/src/interface/screenshot.c b/src/interface/screenshot.c index 13c2c1a571..21674bc123 100644 --- a/src/interface/screenshot.c +++ b/src/interface/screenshot.c @@ -25,9 +25,11 @@ #include "../drawing/drawing.h" #include "../game.h" #include "../localisation/localisation.h" +#include "../openrct2.h" #include "../platform/platform.h" #include "../windows/error.h" #include "screenshot.h" +#include "viewport.h" static const char *_screenshot_format_extension[] = { ".bmp", ".png" }; @@ -273,4 +275,173 @@ int screenshot_dump_png() free(png); return index; +} + +bool screenshot_write_png(rct_drawpixelinfo *dpi, const char *path) +{ + unsigned int error; + unsigned char* png; + size_t pngSize; + LodePNGState state; + + lodepng_state_init(&state); + state.info_raw.colortype = LCT_PALETTE; + + // Get image size + int stride = (dpi->width + 3) & ~3; + + for (int i = 0; i < 256; i++) { + unsigned char r, g, b, a = 255; + + b = RCT2_ADDRESS(0x01424680, uint8)[i * 4 + 0]; + g = RCT2_ADDRESS(0x01424680, uint8)[i * 4 + 1]; + r = RCT2_ADDRESS(0x01424680, uint8)[i * 4 + 2]; + + lodepng_palette_add(&state.info_raw, r, g, b, a); + } + + error = lodepng_encode(&png, &pngSize, dpi->bits, stride, dpi->height, &state); + if (error != 0) { + free(png); + return false; + } else { + error = lodepng_save_file(png, pngSize, path); + if (error != 0) { + free(png); + return false; + } + } + + free(png); + return true; +} + +int cmdline_for_screenshot(const char **argv, int argc) +{ + bool giantScreenshot = argc == 5 && _stricmp(argv[2], "giant") == 0; + if (argc != 4 && argc != 8 && !giantScreenshot) { + printf("Usage: openrct2 screenshot [ ]\n"); + printf("Usage: openrct2 screenshot giant \n"); + return -1; + } + + bool customLocation = false; + bool centreMapX = false; + bool centreMapY = false; + int resolutionWidth, resolutionHeight, customX, customY, customZoom, customRotation; + + const char *inputPath = argv[0]; + const char *outputPath = argv[1]; + if (giantScreenshot) { + resolutionWidth = 0; + resolutionHeight = 0; + customLocation = true; + centreMapX = true; + centreMapY = true; + customZoom = atoi(argv[3]); + customRotation = atoi(argv[4]) & 3; + } else { + resolutionWidth = atoi(argv[2]); + resolutionHeight = atoi(argv[3]); + if (argc == 8) { + customLocation = true; + if (argv[4][0] == 'c') + centreMapX = true; + else + customX = atoi(argv[4]); + if (argv[5][0] == 'c') + centreMapY = true; + else + customY = atoi(argv[5]); + + customZoom = atoi(argv[6]); + customRotation = atoi(argv[7]) & 3; + } + } + + gOpenRCT2Headless = true; + if (openrct2_initialise()) { + rct2_open_file(inputPath); + + RCT2_GLOBAL(RCT2_ADDRESS_RUN_INTRO_TICK_PART, uint8) = 0; + RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) = SCREEN_FLAGS_PLAYING; + + int mapSize = RCT2_GLOBAL(RCT2_ADDRESS_MAP_SIZE, uint16); + if (resolutionWidth == 0 || resolutionHeight == 0) { + resolutionWidth = (mapSize * 32 * 2) >> customZoom; + resolutionHeight = (mapSize * 32 * 1) >> customZoom; + + resolutionWidth += 8; + resolutionHeight += 128; + } + + rct_viewport viewport; + viewport.x = 0; + viewport.y = 0; + viewport.width = resolutionWidth; + viewport.height = resolutionHeight; + viewport.view_width = viewport.width; + viewport.view_height = viewport.height; + viewport.var_11 = 0; + viewport.flags = 0; + + if (customLocation) { + if (centreMapX) + customX = (mapSize / 2) * 32 + 16; + if (centreMapY) + customY = (mapSize / 2) * 32 + 16; + + int x, y; + int z = map_element_height(customX, customY); + switch (customRotation) { + case 0: + x = customY - customX; + y = ((customX + customY) / 2) - z; + break; + case 1: + x = -customY - customX; + y = ((-customX + customY) / 2) - z; + break; + case 2: + x = -customY + customX; + y = ((-customX - customY) / 2) - z; + break; + case 3: + x = customY + customX; + y = ((customX - customY) / 2) - z; + break; + } + + viewport.view_x = x - ((viewport.view_width << customZoom) / 2); + viewport.view_y = y - ((viewport.view_height << customZoom) / 2); + viewport.zoom = customZoom; + + RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_ROTATION, uint8) = customRotation; + } else { + viewport.view_x = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_X, sint16) - (viewport.view_width / 2); + viewport.view_y = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_Y, sint16) - (viewport.view_height / 2); + viewport.zoom = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_ZOOM_AND_ROTATION, uint16) & 0xFF; + + RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_ROTATION, uint8) = RCT2_GLOBAL(RCT2_ADDRESS_SAVED_VIEW_ZOOM_AND_ROTATION, uint16) >> 8; + } + + sub_69E9A7(); + + rct_drawpixelinfo dpi; + dpi.x = 0; + dpi.y = 0; + dpi.width = resolutionWidth; + dpi.height = resolutionHeight; + dpi.pitch = 0; + dpi.zoom_level = 0; + dpi.bits = malloc(dpi.width * dpi.height); + + viewport_render(&dpi, &viewport, 0, 0, viewport.width, viewport.height); + + screenshot_write_png(&dpi, outputPath); + + free(dpi.bits); + } + openrct2_dispose(); + return 1; } \ No newline at end of file diff --git a/src/interface/screenshot.h b/src/interface/screenshot.h index 2515ebe79d..3b2c8ae6fa 100644 --- a/src/interface/screenshot.h +++ b/src/interface/screenshot.h @@ -24,4 +24,6 @@ void screenshot_check(); int screenshot_dump(); +int cmdline_for_screenshot(const char **argv, int argc); + #endif \ No newline at end of file diff --git a/src/openrct2.c b/src/openrct2.c index c4457a2c09..301ff317a4 100644 --- a/src/openrct2.c +++ b/src/openrct2.c @@ -34,6 +34,9 @@ int gOpenRCT2StartupAction = STARTUP_ACTION_TITLE; char gOpenRCT2StartupActionPath[512] = { 0 }; +// This should probably be changed later and allow a custom selection of things to initialise like SDL_INIT +bool gOpenRCT2Headless = false; + /** If set, will end the OpenRCT2 game loop. Intentially private to this module so that the flag can not be set back to 0. */ int _finished; @@ -108,82 +111,92 @@ static void openrct2_copy_original_user_files_over() openrct2_copy_files_over((char*)RCT2_ADDRESS_LANDSCAPES_PATH, path, ".sc6"); } -/** - * Launches the game, after command line arguments have been parsed and processed. - */ -void openrct2_launch() +bool openrct2_initialise() { char userPath[MAX_PATH]; platform_get_user_directory(userPath, NULL); if (!platform_ensure_directory_exists(userPath)) { log_fatal("Could not create user directory (do you have write access to your documents folder?)"); - return; + return false; } config_set_defaults(); if (!config_open_default()) { if (!config_find_or_browse_install_directory()) { log_fatal("An RCT2 install directory must be specified!"); - return; + return false; } } config_save_default(); // TODO add configuration option to allow multiple instances - if (!platform_lock_single_instance()) { - fprintf(stderr, "OpenRCT2 is already running.\n"); - return; + if (!gOpenRCT2Headless && !platform_lock_single_instance()) { + log_fatal("OpenRCT2 is already running."); + return false; } get_system_info(); - audio_init(); - audio_get_devices(); - get_dsound_devices(); + if (!gOpenRCT2Headless) { + audio_init(); + audio_get_devices(); + get_dsound_devices(); + } language_open(gConfigGeneral.language); http_init(); if (!rct2_init()) - return; + return false; openrct2_copy_original_user_files_over(); Mixer_Init(NULL); + return true; +} - switch (gOpenRCT2StartupAction) { - case STARTUP_ACTION_INTRO: - RCT2_GLOBAL(RCT2_ADDRESS_RUN_INTRO_TICK_PART, uint8) = 8; - break; - case STARTUP_ACTION_TITLE: - RCT2_GLOBAL(RCT2_ADDRESS_RUN_INTRO_TICK_PART, uint8) = 0; - RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) = SCREEN_FLAGS_TITLE_DEMO; - break; - case STARTUP_ACTION_OPEN: - assert(gOpenRCT2StartupActionPath != NULL); - rct2_open_file(gOpenRCT2StartupActionPath); +/** + * Launches the game, after command line arguments have been parsed and processed. + */ +void openrct2_launch() +{ + if (openrct2_initialise()) { + switch (gOpenRCT2StartupAction) { + case STARTUP_ACTION_INTRO: + RCT2_GLOBAL(RCT2_ADDRESS_RUN_INTRO_TICK_PART, uint8) = 8; + break; + case STARTUP_ACTION_TITLE: + RCT2_GLOBAL(RCT2_ADDRESS_RUN_INTRO_TICK_PART, uint8) = 0; + RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) = SCREEN_FLAGS_TITLE_DEMO; + break; + case STARTUP_ACTION_OPEN: + assert(gOpenRCT2StartupActionPath != NULL); + rct2_open_file(gOpenRCT2StartupActionPath); - RCT2_GLOBAL(RCT2_ADDRESS_RUN_INTRO_TICK_PART, uint8) = 0; - RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) = SCREEN_FLAGS_PLAYING; - break; - case STARTUP_ACTION_EDIT: - if (strlen(gOpenRCT2StartupActionPath) == 0) { - editor_load(); - } else { - editor_load_landscape(gOpenRCT2StartupActionPath); + RCT2_GLOBAL(RCT2_ADDRESS_RUN_INTRO_TICK_PART, uint8) = 0; + RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) = SCREEN_FLAGS_PLAYING; + break; + case STARTUP_ACTION_EDIT: + if (strlen(gOpenRCT2StartupActionPath) == 0) { + editor_load(); + } else { + editor_load_landscape(gOpenRCT2StartupActionPath); + } + break; } - break; + openrct2_loop(); } - - log_verbose("begin openrct2 loop"); - openrct2_loop(); - - http_dispose(); - platform_free(); + openrct2_dispose(); // HACK Some threads are still running which causes the game to not terminate. Investigation required! exit(gExitCode); } +void openrct2_dispose() +{ + http_dispose(); + platform_free(); +} + /** * Run the main game loop until the finished flag is set at 40fps (25ms interval). */ @@ -191,6 +204,8 @@ static void openrct2_loop() { uint32 currentTick, ticksElapsed, lastTick = 0; + log_verbose("begin openrct2 loop"); + _finished = 0; do { currentTick = SDL_GetTicks(); diff --git a/src/openrct2.h b/src/openrct2.h index f265c97833..d2452d5389 100644 --- a/src/openrct2.h +++ b/src/openrct2.h @@ -32,8 +32,11 @@ enum { extern int gOpenRCT2StartupAction; extern char gOpenRCT2StartupActionPath[512]; +extern bool gOpenRCT2Headless; +bool openrct2_initialise(); void openrct2_launch(); +void openrct2_dispose(); void openrct2_finish(); #endif \ No newline at end of file diff --git a/src/platform/shared.c b/src/platform/shared.c index c6f356b473..fcd34e4027 100644 --- a/src/platform/shared.c +++ b/src/platform/shared.c @@ -28,6 +28,7 @@ #include "../interface/keyboard_shortcut.h" #include "../interface/window.h" #include "../input.h" +#include "../openrct2.h" #include "platform.h" typedef void(*update_palette_func)(char*, int, int); @@ -275,7 +276,7 @@ void platform_update_palette(char* colours, int start_index, int num_colours) colours += 4; } - if (!gConfigGeneral.hardware_display) { + if (!gOpenRCT2Headless && !gConfigGeneral.hardware_display) { surface = SDL_GetWindowSurface(gWindow); if (!surface) { log_fatal("SDL_GetWindowSurface failed %s", SDL_GetError()); diff --git a/src/platform/windows.c b/src/platform/windows.c index 8ce7bedd9b..eed058f77b 100644 --- a/src/platform/windows.c +++ b/src/platform/windows.c @@ -80,6 +80,7 @@ __declspec(dllexport) int StartOpenRCT(HINSTANCE hInstance, HINSTANCE hPrevInsta if (runGame) openrct2_launch(); + exit(gExitCode); return gExitCode; } diff --git a/src/rct2.c b/src/rct2.c index 81dc31e8b5..e900020c60 100644 --- a/src/rct2.c +++ b/src/rct2.c @@ -96,8 +96,10 @@ int rct2_init() gfx_load_g1(); gfx_load_g2(); gfx_load_character_widths(); - platform_init(); - audio_init1(); + if (!gOpenRCT2Headless) { + platform_init(); + audio_init1(); + } viewport_init_all(); news_item_init_queue(); get_local_time(); @@ -109,7 +111,8 @@ int rct2_init() sub_6BD3A4(); map_init(150); park_init(); - window_title_menu_open(); + if (!gOpenRCT2Headless) + window_title_menu_open(); date_reset(); climate_reset(CLIMATE_COOL_AND_WET); scenery_set_default_placement_configuration(); @@ -117,10 +120,12 @@ int rct2_init() window_guest_list_init_vars_b(); window_staff_list_init_vars(); - title_load(); + if (!gOpenRCT2Headless) { + title_load(); - gfx_clear(RCT2_ADDRESS(RCT2_ADDRESS_SCREEN_DPI, rct_drawpixelinfo), 10); - RCT2_GLOBAL(RCT2_ADDRESS_RUN_INTRO_TICK_PART, uint8) = gConfigGeneral.play_intro ? 8 : 255; + gfx_clear(RCT2_ADDRESS(RCT2_ADDRESS_SCREEN_DPI, rct_drawpixelinfo), 10); + RCT2_GLOBAL(RCT2_ADDRESS_RUN_INTRO_TICK_PART, uint8) = gConfigGeneral.play_intro ? 8 : 255; + } log_verbose("initialising game finished"); return 1;