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;