diff --git a/data/g2.dat b/data/g2.dat
new file mode 100644
index 0000000000..727bc37f26
Binary files /dev/null and b/data/g2.dat differ
diff --git a/projects/openrct2.vcxproj b/projects/openrct2.vcxproj
index 18c7dfc1dd..ca7c8e0c09 100644
--- a/projects/openrct2.vcxproj
+++ b/projects/openrct2.vcxproj
@@ -21,6 +21,7 @@
+
@@ -300,6 +301,9 @@
true
winmm.lib;sdl2.lib;Dsound.lib;%(AdditionalDependencies)
+
+ xcopy /Y "$(SolutionDir)\..\Data\*.*" "$(TargetDir)\Data\"
+
diff --git a/projects/openrct2.vcxproj.filters b/projects/openrct2.vcxproj.filters
index 0d27fea1b5..0d112d516f 100644
--- a/projects/openrct2.vcxproj.filters
+++ b/projects/openrct2.vcxproj.filters
@@ -433,6 +433,9 @@
Source\Windows
+
+ Source
+
Source\Windows
diff --git a/src/cmdline.c b/src/cmdline.c
index ad2a471211..62fc461592 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -24,14 +24,18 @@
#include "cmdline.h"
#include "openrct2.h"
#include "platform/platform.h"
+#include "util/util.h"
typedef struct tm tm_t;
typedef struct argparse_option argparse_option_t;
typedef struct argparse argparse_t;
+typedef int (*cmdline_action)(const char **argv, int argc);
+
int gExitCode = 0;
static void print_launch_information();
+static int cmdline_call_action(const char **argv, int argc);
static const char *const usage[] = {
"openrct2 [options] []",
@@ -73,21 +77,8 @@ int cmdline_run(const char **argv, int argc)
_log_levels[DIAGNOSTIC_LEVEL_VERBOSE] = 1;
if (argc != 0) {
- if (_stricmp(argv[0], "intro") == 0) {
- gOpenRCT2StartupAction = STARTUP_ACTION_INTRO;
- } else if (_stricmp(argv[0], "edit") == 0) {
- gOpenRCT2StartupAction = STARTUP_ACTION_EDIT;
- if (argc >= 2)
- strcpy(gOpenRCT2StartupActionPath, argv[1]);
- } else {
- if (platform_file_exists(argv[0])) {
- gOpenRCT2StartupAction = STARTUP_ACTION_OPEN;
- strcpy(gOpenRCT2StartupActionPath, argv[0]);
- } else {
- fprintf(stderr, "error: %s does not exist\n", argv[0]);
- return 0;
- }
- }
+ gExitCode = cmdline_call_action(argv, argc);
+ return 0;
}
print_launch_information();
@@ -113,3 +104,50 @@ static void print_launch_information()
// TODO Print other potential information (e.g. user, hardware)
}
+
+static int cmdline_for_intro(const char **argv, int argc)
+{
+ gOpenRCT2StartupAction = STARTUP_ACTION_INTRO;
+ return 0;
+}
+
+static int cmdline_for_edit(const char **argv, int argc)
+{
+ gOpenRCT2StartupAction = STARTUP_ACTION_EDIT;
+ if (argc >= 1)
+ strcpy(gOpenRCT2StartupActionPath, argv[1]);
+
+ return 0;
+}
+
+static int cmdline_for_none(const char **argv, int argc)
+{
+ assert(argc >= 1);
+
+ if (platform_file_exists(argv[0])) {
+ gOpenRCT2StartupAction = STARTUP_ACTION_OPEN;
+ strcpy(gOpenRCT2StartupActionPath, argv[0]);
+ return 0;
+ } else {
+ fprintf(stderr, "error: %s does not exist\n", argv[0]);
+ return -1;
+ }
+}
+
+struct { const char *firstArg; cmdline_action action; } cmdline_table[] = {
+ { "intro", cmdline_for_intro },
+ { "edit", cmdline_for_edit },
+ { "sprite", cmdline_for_sprite }
+};
+
+static int cmdline_call_action(const char **argv, int argc)
+{
+ for (int i = 0; i < countof(cmdline_table); i++) {
+ if (_stricmp(cmdline_table[i].firstArg, argv[0]) != 0)
+ continue;
+
+ return cmdline_table[i].action(argv + 1, argc - 1);
+ }
+
+ return cmdline_for_none(argv, argc);
+}
\ No newline at end of file
diff --git a/src/cmdline.h b/src/cmdline.h
index bb046008a0..dfb1d86b40 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -27,5 +27,6 @@
extern int gExitCode;
int cmdline_run(const char **argv, int argc);
+int cmdline_for_sprite(const char **argv, int argc);
#endif
\ No newline at end of file
diff --git a/src/cmdline_sprite.c b/src/cmdline_sprite.c
new file mode 100644
index 0000000000..d98fb2eb1b
--- /dev/null
+++ b/src/cmdline_sprite.c
@@ -0,0 +1,678 @@
+#include
+#include "cmdline.h"
+#include "drawing/drawing.h"
+#include "util/util.h"
+
+typedef struct {
+ uint32 num_entries;
+ uint32 total_size;
+} rct_sprite_file_header;
+
+typedef struct { uint8 b, g, r, a; } rct_sprite_file_palette_entry;
+
+rct_sprite_file_palette_entry spriteFilePalette[256];
+static rct_sprite_file_palette_entry _standardPalette[256];
+
+rct_sprite_file_header spriteFileHeader;
+rct_g1_element *spriteFileEntries;
+uint8 *spriteFileData;
+
+void sprite_file_load_palette(int spriteIndex)
+{
+ rct_g1_element *g1 = &spriteFileEntries[spriteIndex];
+ int numPaletteEntries = g1->width;
+ uint8* src = g1->offset;
+ rct_sprite_file_palette_entry *destPaletteEntry = &spriteFilePalette[g1->x_offset];
+ for (; numPaletteEntries > 0; numPaletteEntries--) {
+ destPaletteEntry->b = src[0];
+ destPaletteEntry->g = src[1];
+ destPaletteEntry->r = src[2];
+ src += 3;
+ destPaletteEntry++;
+ }
+}
+
+void sprite_entries_make_absolute()
+{
+ for (uint32 i = 0; i < spriteFileHeader.num_entries; i++)
+ spriteFileEntries[i].offset += (int)spriteFileData;
+}
+
+void sprite_entries_make_relative()
+{
+ for (uint32 i = 0; i < spriteFileHeader.num_entries; i++)
+ spriteFileEntries[i].offset -= (int)spriteFileData;
+}
+
+bool sprite_file_open(const char *path)
+{
+ FILE *file;
+
+ file = fopen(path, "rb");
+ if (file == NULL)
+ return false;
+
+ if (fread(&spriteFileHeader, sizeof(rct_sprite_file_header), 1, file) != 1) {
+ fclose(file);
+ return false;
+ }
+
+ if (spriteFileHeader.num_entries > 0) {
+ int entryTableSize = spriteFileHeader.num_entries * sizeof(rct_g1_element);
+
+ spriteFileEntries = malloc(entryTableSize);
+ if (fread(spriteFileEntries, entryTableSize, 1, file) != 1) {
+ fclose(file);
+ return false;
+ }
+
+ spriteFileData = malloc(spriteFileHeader.total_size);
+ if (fread(spriteFileData, spriteFileHeader.total_size, 1, file) != 1) {
+ fclose(file);
+ return false;
+ }
+
+ sprite_entries_make_absolute();
+ }
+
+ fclose(file);
+ return true;
+}
+
+bool sprite_file_save(const char *path)
+{
+ FILE *file = fopen(path, "wb");
+ if (file == NULL)
+ return false;
+
+ if (fwrite(&spriteFileHeader, sizeof(rct_sprite_file_header), 1, file) != 1) {
+ fclose(file);
+ return false;
+ }
+
+ if (spriteFileHeader.num_entries > 0) {
+ sprite_entries_make_relative();
+
+ int entryTableSize = spriteFileHeader.num_entries * sizeof(rct_g1_element);
+
+ if (fwrite(spriteFileEntries, entryTableSize, 1, file) != 1) {
+ sprite_entries_make_absolute();
+ fclose(file);
+ return false;
+ } else {
+ sprite_entries_make_absolute();
+ }
+
+ if (fwrite(spriteFileData, spriteFileHeader.total_size, 1, file) != 1) {
+ fclose(file);
+ return false;
+ }
+ }
+
+ fclose(file);
+ return true;
+}
+
+void sprite_file_close()
+{
+ free(spriteFileEntries);
+ free(spriteFileData);
+}
+
+bool sprite_file_export(int spriteIndex, const char *outPath)
+{
+ rct_g1_element *spriteHeader;
+ rct_drawpixelinfo dpi;
+ uint8 *pixels;
+ int pixelBufferSize;
+
+ spriteHeader = &spriteFileEntries[spriteIndex];
+ pixelBufferSize = spriteHeader->width * spriteHeader->height;
+ pixels = malloc(pixelBufferSize);
+ memset(pixels, 0, pixelBufferSize);
+
+ dpi.bits = pixels;
+ dpi.x = 0;
+ dpi.y = 0;
+ dpi.width = spriteHeader->width;
+ dpi.height = spriteHeader->height;
+ dpi.pitch = 0;
+ dpi.zoom_level = 0;
+
+ memcpy(spriteFilePalette, _standardPalette, 256 * 4);
+ gfx_rle_sprite_to_buffer(spriteHeader->offset, pixels, (uint8*)spriteFilePalette, &dpi, IMAGE_TYPE_NO_BACKGROUND, 0, spriteHeader->height, 0, spriteHeader->width);
+
+ LodePNGState pngState;
+ unsigned int pngError;
+ unsigned char* pngData;
+ size_t pngSize;
+
+ lodepng_state_init(&pngState);
+ pngState.info_raw.colortype = LCT_PALETTE;
+ lodepng_palette_add(&pngState.info_raw, 0, 0, 0, 0);
+ for (int i = 1; i < 256; i++) {
+ lodepng_palette_add(
+ &pngState.info_raw,
+ spriteFilePalette[i].r,
+ spriteFilePalette[i].g,
+ spriteFilePalette[i].b,
+ 255
+ );
+ }
+
+ pngError = lodepng_encode(&pngData, &pngSize, pixels, spriteHeader->width, spriteHeader->height, &pngState);
+ if (pngError != 0) {
+ fprintf(stderr, "Error creating PNG data, %u: %s", pngError, lodepng_error_text(pngError));
+ return false;
+ } else {
+ lodepng_save_file(pngData, pngSize, outPath);
+ free(pngData);
+ return true;
+ }
+}
+
+int get_palette_index(uint32 colour)
+{
+ uint8 *rgba = (uint8*)(&colour);
+
+ if (rgba[3] < 128)
+ return -1;
+
+ for (int i = 10; i < 246; i++) {
+ if (spriteFilePalette[i].r != rgba[0]) continue;
+ if (spriteFilePalette[i].g != rgba[1]) continue;
+ if (spriteFilePalette[i].b != rgba[2]) continue;
+ return i;
+ }
+
+ return -1;
+}
+
+typedef struct {
+ uint8 num_pixels;
+ uint8 offset_x;
+} rle_code;
+
+bool sprite_file_import(const char *path, rct_g1_element *outElement, uint8 **outBuffer, int *outBufferLength)
+{
+ unsigned char *pixels;
+ unsigned int width, height;
+ unsigned int pngError;
+
+ memcpy(spriteFilePalette, _standardPalette, 256 * 4);
+
+ pngError = lodepng_decode_file(&pixels, &width, &height, path, LCT_RGBA, 8);
+ if (pngError != 0) {
+ fprintf(stderr, "Error creating PNG data, %u: %s", pngError, lodepng_error_text(pngError));
+ return false;
+ }
+
+ if (width > 256 || height > 256) {
+ fprintf(stderr, "Only images 256x256 or less are supported.");
+ free(pixels);
+ return false;
+ }
+
+ uint8 *buffer = malloc((height * 2) + (width * height * 16));
+ uint16 *yOffsets = (uint16*)buffer;
+ uint8 *src = pixels;
+ uint8 *dst = buffer + (height * 2);
+
+ for (unsigned int y = 0; y < height; y++) {
+ rle_code *previousCode, *currentCode;
+
+ yOffsets[y] = (dst - buffer);
+
+ previousCode = NULL;
+ currentCode = (rle_code*)dst;
+ dst += 2;
+ int startX = 0;
+ int pixels = 0;
+ bool pushRun = false;
+ for (unsigned int x = 0; x < width; x++) {
+ int paletteIndex = get_palette_index(*((uint32*)src));
+ src += 4;
+ if (paletteIndex == -1) {
+ if (pixels != 0) {
+ x--;
+ src -= 4;
+ pushRun = true;
+ }
+ } else {
+ if (pixels == 0)
+ startX = x;
+ pixels++;
+ *dst++ = (uint8)paletteIndex;
+ }
+ if (pixels == 127 || x == width - 1)
+ pushRun = true;
+
+ if (pushRun) {
+ if (pixels > 0) {
+ previousCode = currentCode;
+ currentCode->num_pixels = pixels;
+ currentCode->offset_x = startX;
+
+ if (x == width - 1)
+ currentCode->num_pixels |= 0x80;
+
+ currentCode = (rle_code*)dst;
+ dst += 2;
+ } else {
+ if (previousCode == NULL) {
+ currentCode->num_pixels = 0x80;
+ currentCode->offset_x = 0;
+ } else {
+ previousCode->num_pixels |= 0x80;
+ dst -= 2;
+ }
+ }
+ startX = 0;
+ pixels = 0;
+ pushRun = false;
+ }
+ }
+ }
+ free(pixels);
+
+ int bufferLength = (int)(dst - buffer);
+ buffer = realloc(buffer, bufferLength);
+
+ outElement->offset = buffer;
+ outElement->width = width;
+ outElement->height = height;
+ outElement->flags = G1_FLAG_RLE_COMPRESSION;
+ outElement->x_offset = 0;
+ outElement->y_offset = 0;
+ outElement->zoomed_offset = 0;
+
+ *outBuffer = buffer;
+ *outBufferLength = bufferLength;
+ return true;
+}
+
+int cmdline_for_sprite(const char **argv, int argc)
+{
+ if (argc == 0)
+ return -1;
+
+ if (_strcmpi(argv[0], "details") == 0) {
+ if (argc < 2) {
+ fprintf(stderr, "usage: sprite details [idx]\n");
+ return -1;
+ } else if (argc == 2) {
+ const char *spriteFilePath = argv[1];
+
+ if (!sprite_file_open(spriteFilePath)) {
+ fprintf(stderr, "Unable to open input sprite file.\n");
+ return -1;
+ }
+
+ printf("sprites: %d\n", spriteFileHeader.num_entries);
+ printf("data size: %d\n", spriteFileHeader.total_size);
+
+ sprite_file_close();
+ return 1;
+ } else {
+ const char *spriteFilePath = argv[1];
+ int spriteIndex = atoi(argv[2]);
+
+ if (!sprite_file_open(spriteFilePath)) {
+ fprintf(stderr, "Unable to open input sprite file.\n");
+ return -1;
+ }
+
+ if (spriteIndex < 0 || spriteIndex >= (int)spriteFileHeader.num_entries) {
+ sprite_file_close();
+ fprintf(stderr, "Sprite #%d does not exist in sprite file.\n", spriteIndex);
+ return -1;
+ }
+
+ rct_g1_element *g1 = &spriteFileEntries[spriteIndex];
+ printf("width: %d\n", g1->width);
+ printf("height: %d\n", g1->height);
+ printf("x offset: %d\n", g1->x_offset);
+ printf("y offset: %d\n", g1->y_offset);
+ printf("data offset: 0x%X\n", g1->offset);
+
+ sprite_file_close();
+ return 1;
+ }
+ } else if (_strcmpi(argv[0], "export") == 0) {
+ if (argc < 4) {
+ fprintf(stderr, "usage: sprite export