1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-30 02:05:13 +01:00
Files
OpenRCT2/src/openrct2/cmdline_sprite.c
Ted John f275e5ba9b Remove all platform.h includes from header files
System headers, particularly windows.h polute the namespace too much with macros and unwanted definitions. Do not use them in header files.
2017-06-25 18:59:56 +01:00

1085 lines
33 KiB
C

#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#pragma warning(disable : 4706) // assignment within conditional expression
#include <jansson.h>
#include "cmdline_sprite.h"
#include "drawing/drawing.h"
#include "Imaging.h"
#include "localisation/localisation.h"
#include "OpenRCT2.h"
#include "platform/platform.h"
#include "util/util.h"
#define MODE_DEFAULT 0
#define MODE_CLOSEST 1
#define MODE_DITHERING 2
extern sint32 gSpriteMode;
#pragma pack(push, 1)
typedef struct rct_sprite_file_header {
uint32 num_entries;
uint32 total_size;
} rct_sprite_file_header;
assert_struct_size(rct_sprite_file_header, 8);
typedef struct rct_sprite_file_palette_entry {
uint8 b, g, r, a;
} rct_sprite_file_palette_entry;
typedef struct rle_code {
uint8 num_pixels;
uint8 offset_x;
} rle_code;
assert_struct_size(rle_code, 2);
#pragma pack(pop)
assert_struct_size(rct_sprite_file_palette_entry, 4);
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;
#ifdef _WIN32
static FILE * fopen_utf8(const char * path, const char * mode)
{
wchar_t * pathW = utf8_to_widechar(path);
wchar_t * modeW = utf8_to_widechar(mode);
FILE * file = _wfopen(pathW, modeW);
free(pathW);
free(modeW);
return file;
}
#define fopen fopen_utf8
#endif
static void sprite_file_load_palette(sint32 spriteIndex)
{
rct_g1_element *g1 = &spriteFileEntries[spriteIndex];
sint32 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++;
}
}
static void sprite_entries_make_absolute()
{
for (uint32 i = 0; i < spriteFileHeader.num_entries; i++)
spriteFileEntries[i].offset += (uintptr_t)spriteFileData;
}
static void sprite_entries_make_relative()
{
for (uint32 i = 0; i < spriteFileHeader.num_entries; i++)
spriteFileEntries[i].offset -= (uintptr_t)spriteFileData;
}
static bool sprite_file_open(const utf8 *path)
{
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) {
sint32 openEntryTableSize = spriteFileHeader.num_entries * sizeof(rct_g1_element_32bit);
rct_g1_element_32bit * openElements = (rct_g1_element_32bit *)malloc(openEntryTableSize);
if (openElements == NULL) {
fclose(file);
return false;
}
if (fread(openElements, openEntryTableSize, 1, file) != 1) {
free(openElements);
fclose(file);
return false;
}
spriteFileData = malloc(spriteFileHeader.total_size);
if (fread(spriteFileData, spriteFileHeader.total_size, 1, file) != 1) {
free(spriteFileData);
free(openElements);
fclose(file);
return false;
}
sint32 entryTableSize = spriteFileHeader.num_entries * sizeof(rct_g1_element);
spriteFileEntries = malloc(entryTableSize);
for (uint32 i = 0; i < spriteFileHeader.num_entries; i++) {
rct_g1_element_32bit * inElement = &openElements[i];
rct_g1_element * outElement = &spriteFileEntries[i];
outElement->offset = (uint8*)((uintptr_t)inElement->offset + (uintptr_t)spriteFileData);
outElement->width = inElement->width;
outElement->height = inElement->height;
outElement->x_offset = inElement->x_offset;
outElement->y_offset = inElement->y_offset;
outElement->flags = inElement->flags;
outElement->zoomed_offset = inElement->zoomed_offset;
}
free(openElements);
}
fclose(file);
return true;
}
static 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) {
sint32 saveEntryTableSize = spriteFileHeader.num_entries * sizeof(rct_g1_element_32bit);
rct_g1_element_32bit * saveElements = (rct_g1_element_32bit *)malloc(saveEntryTableSize);
if (saveElements == NULL) {
fclose(file);
return false;
}
for (uint32 i = 0; i < spriteFileHeader.num_entries; i++) {
rct_g1_element * inElement = &spriteFileEntries[i];
rct_g1_element_32bit * outElement = &saveElements[i];
outElement->offset = (uint32)((uintptr_t)inElement->offset - (uintptr_t)spriteFileData);
outElement->width = inElement->width;
outElement->height = inElement->height;
outElement->x_offset = inElement->x_offset;
outElement->y_offset = inElement->y_offset;
outElement->flags = inElement->flags;
outElement->zoomed_offset = inElement->zoomed_offset;
}
if (fwrite(saveElements, saveEntryTableSize, 1, file) != 1) {
free(saveElements);
fclose(file);
return false;
}
free(saveElements);
if (fwrite(spriteFileData, spriteFileHeader.total_size, 1, file) != 1) {
fclose(file);
return false;
}
}
fclose(file);
return true;
}
static void sprite_file_close()
{
SafeFree(spriteFileEntries);
SafeFree(spriteFileData);
}
static bool sprite_file_export(sint32 spriteIndex, const char *outPath)
{
rct_g1_element *spriteHeader;
rct_drawpixelinfo dpi;
uint8 *pixels;
sint32 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);
if (spriteHeader->flags & G1_FLAG_RLE_COMPRESSION) {
gfx_rle_sprite_to_buffer(spriteHeader->offset, pixels, (uint8*)spriteFilePalette, &dpi, IMAGE_TYPE_DEFAULT, 0, spriteHeader->height, 0, spriteHeader->width);
} else {
gfx_bmp_sprite_to_buffer((uint8*)spriteFilePalette, NULL, spriteHeader->offset, pixels, spriteHeader, &dpi, spriteHeader->height, spriteHeader->width, IMAGE_TYPE_DEFAULT);
}
if (image_io_png_write(&dpi, (rct_palette*)spriteFilePalette, outPath)) {
return true;
} else {
fprintf(stderr, "Error writing PNG");
return false;
}
}
static bool is_transparent_pixel(sint16 *colour){
return colour[3] < 128;
}
// Returns true if pixel index is an index not used for remapping
static bool is_changable_pixel(sint32 palette_index) {
if (palette_index == -1)
return true;
if (palette_index == 0)
return false;
if (palette_index >= 203 && palette_index < 214)
return false;
if (palette_index == 226)
return false;
if (palette_index >= 227 && palette_index < 229)
return false;
if (palette_index >= 243)
return false;
return true;
}
static sint32 get_closest_palette_index(sint16 *colour){
uint32 smallest_error = -1;
sint32 best_match = -1;
for (sint32 x = 0; x < 256; x++){
if (is_changable_pixel(x)){
uint32 error =
((sint16)(spriteFilePalette[x].r) - colour[0]) * ((sint16)(spriteFilePalette[x].r) - colour[0]) +
((sint16)(spriteFilePalette[x].g) - colour[1]) * ((sint16)(spriteFilePalette[x].g) - colour[1]) +
((sint16)(spriteFilePalette[x].b) - colour[2]) * ((sint16)(spriteFilePalette[x].b) - colour[2]);
if (smallest_error == -1 || smallest_error > error){
best_match = x;
smallest_error = error;
}
}
}
return best_match;
}
static sint32 get_palette_index(sint16 *colour)
{
if (is_transparent_pixel(colour))
return -1;
for (sint32 i = 0; i < 256; i++) {
if ((sint16)(spriteFilePalette[i].r) != colour[0]) continue;
if ((sint16)(spriteFilePalette[i].g) != colour[1]) continue;
if ((sint16)(spriteFilePalette[i].b) != colour[2]) continue;
return i;
}
return -1;
}
static bool sprite_file_import(const char *path, sint16 x_offset, sint16 y_offset, rct_g1_element *outElement, uint8 **outBuffer, int *outBufferLength, sint32 mode)
{
uint8 *pixels;
uint32 width, height;
if (!image_io_png_read(&pixels, &width, &height, path)) {
fprintf(stderr, "Error reading PNG");
return false;
}
if (width > 256 || height > 256) {
fprintf(stderr, "Only images 256x256 or less are supported.");
free(pixels);
return false;
}
memcpy(spriteFilePalette, _standardPalette, 256 * 4);
uint8 *buffer = malloc((height * 2) + (width * height * 16));
memset(buffer, 0, (height * 2) + (width * height * 16));
uint16 *yOffsets = (uint16*)buffer;
// A larger range is needed for proper dithering
sint16 *src = malloc(height * width * 4 * 2);
sint16 *src_orig = src;
for (uint32 x = 0; x < height * width * 4; x++){
src[x] = (sint16) pixels[x];
}
uint8 *dst = buffer + (height * 2);
for (uint32 y = 0; y < height; y++) {
rle_code *previousCode, *currentCode;
yOffsets[y] = (uint16)(dst - buffer);
previousCode = NULL;
currentCode = (rle_code*)dst;
dst += 2;
sint32 startX = 0;
sint32 npixels = 0;
bool pushRun = false;
for (uint32 x = 0; x < width; x++) {
sint32 paletteIndex = get_palette_index(src);
if (mode == MODE_CLOSEST || mode == MODE_DITHERING)
if (paletteIndex == -1 && !is_transparent_pixel(src))
paletteIndex = get_closest_palette_index(src);
if (mode == MODE_DITHERING)
if (!is_transparent_pixel(src) && is_changable_pixel(get_palette_index(src))){
sint16 dr = src[0] - (sint16)(spriteFilePalette[paletteIndex].r);
sint16 dg = src[1] - (sint16)(spriteFilePalette[paletteIndex].g);
sint16 db = src[2] - (sint16)(spriteFilePalette[paletteIndex].b);
if (x + 1 < width){
if (!is_transparent_pixel(src + 4) && is_changable_pixel(get_palette_index(src + 4))){
// Right
src[4] += dr * 7 / 16;
src[5] += dg * 7 / 16;
src[6] += db * 7 / 16;
}
}
if (y + 1 < height){
if (x > 0){
if (!is_transparent_pixel(src + 4 * (width - 1)) && is_changable_pixel(get_palette_index(src + 4 * (width - 1)))){
// Bottom left
src[4 * (width - 1)] += dr * 3 / 16;
src[4 * (width - 1) + 1] += dg * 3 / 16;
src[4 * (width - 1) + 2] += db * 3 / 16;
}
}
// Bottom
if (!is_transparent_pixel(src + 4 * width) && is_changable_pixel(get_palette_index(src + 4 * width))){
src[4 * width] += dr * 5 / 16;
src[4 * width + 1] += dg * 5 / 16;
src[4 * width + 2] += db * 5 / 16;
}
if (x + 1 < width){
if (!is_transparent_pixel(src + 4 * (width - 1)) && is_changable_pixel(get_palette_index(src + 4 * (width + 1)))){
// Bottom right
src[4 * (width + 1)] += dr * 1 / 16;
src[4 * (width + 1) + 1] += dg * 1 / 16;
src[4 * (width + 1) + 2] += db * 1 / 16;
}
}
}
}
src += 4;
if (paletteIndex == -1) {
if (npixels != 0) {
x--;
src -= 4;
pushRun = true;
}
} else {
if (npixels == 0)
startX = x;
npixels++;
*dst++ = (uint8)paletteIndex;
}
if (npixels == 127 || x == width - 1)
pushRun = true;
if (pushRun) {
if (npixels > 0) {
previousCode = currentCode;
currentCode->num_pixels = npixels;
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;
npixels = 0;
pushRun = false;
}
}
}
free(pixels);
free(src_orig);
sint32 bufferLength = (sint32)(dst - buffer);
buffer = realloc(buffer, bufferLength);
outElement->offset = buffer;
outElement->width = width;
outElement->height = height;
outElement->flags = G1_FLAG_RLE_COMPRESSION;
outElement->x_offset = x_offset;
outElement->y_offset = y_offset;
outElement->zoomed_offset = 0;
*outBuffer = buffer;
*outBufferLength = bufferLength;
return true;
}
sint32 cmdline_for_sprite(const char **argv, sint32 argc)
{
gOpenRCT2Headless = true;
if (argc == 0)
return -1;
if (_strcmpi(argv[0], "details") == 0) {
if (argc < 2) {
fprintf(stdout, "usage: sprite details <spritefile> [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: %u\n", spriteFileHeader.num_entries);
printf("data size: %u\n", spriteFileHeader.total_size);
sprite_file_close();
return 1;
} else {
const char *spriteFilePath = argv[1];
sint32 spriteIndex = atoi(argv[2]);
if (!sprite_file_open(spriteFilePath)) {
fprintf(stderr, "Unable to open input sprite file.\n");
return -1;
}
if (spriteIndex < 0 || spriteIndex >= (sint32)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: %p\n", g1->offset);
sprite_file_close();
return 1;
}
} else if (_strcmpi(argv[0], "export") == 0) {
if (argc < 4) {
fprintf(stdout, "usage: sprite export <spritefile> <idx> <output>\n");
return -1;
}
const char *spriteFilePath = argv[1];
sint32 spriteIndex = atoi(argv[2]);
const char *outputPath = argv[3];
if (!sprite_file_open(spriteFilePath)) {
fprintf(stderr, "Unable to open input sprite file.\n");
return -1;
}
if (spriteIndex < 0 || spriteIndex >= (sint32)spriteFileHeader.num_entries) {
fprintf(stderr, "Sprite #%d does not exist in sprite file.\n", spriteIndex);
return -1;
}
if (!sprite_file_export(spriteIndex, outputPath)) {
fprintf(stderr, "Could not export\n");
sprite_file_close();
return -1;
}
sprite_file_close();
return 1;
} else if (_strcmpi(argv[0], "exportall") == 0) {
if (argc < 3) {
fprintf(stdout, "usage: sprite exportall <spritefile> <output directory>\n");
return -1;
}
const char *spriteFilePath = argv[1];
char outputPath[MAX_PATH];
if (!sprite_file_open(spriteFilePath)) {
fprintf(stderr, "Unable to open input sprite file.\n");
return -1;
}
safe_strcpy(outputPath, argv[2], MAX_PATH);
path_end_with_separator(outputPath, MAX_PATH);
if (!platform_ensure_directory_exists(outputPath)){
fprintf(stderr, "Unable to create directory.\n");
return -1;
}
sint32 maxIndex = (sint32)spriteFileHeader.num_entries;
sint32 numbers = (sint32)floor(log(maxIndex));
size_t pathLen = strlen(outputPath);
if (pathLen >= (size_t)(MAX_PATH - numbers - 5)) {
fprintf(stderr, "Path too long.\n");
return -1;
}
for (sint32 x = 0; x < numbers; x++){
outputPath[pathLen + x] = '0';
}
safe_strcpy(outputPath + pathLen + numbers, ".png", MAX_PATH - pathLen - numbers);
for (sint32 spriteIndex = 0; spriteIndex < maxIndex; spriteIndex++){
if (spriteIndex % 100 == 99){
// Status indicator
printf("\r%d / %d, %d%%", spriteIndex, maxIndex, spriteIndex / maxIndex);
}
// Add to the index at the end of the file name
char *counter = outputPath + pathLen + numbers - 1;
(*counter)++;
while (*counter > '9'){
*counter = '0';
counter--;
(*counter)++;
}
if (!sprite_file_export(spriteIndex, outputPath)) {
fprintf(stderr, "Could not export\n");
sprite_file_close();
return -1;
}
}
sprite_file_close();
return 1;
} else if (_strcmpi(argv[0], "create") == 0) {
if (argc < 2) {
fprintf(stderr, "usage: sprite create <spritefile>\n");
return -1;
}
const char *spriteFilePath = argv[1];
spriteFileHeader.num_entries = 0;
spriteFileHeader.total_size = 0;
sprite_file_save(spriteFilePath);
sprite_file_close();
return 1;
} else if (_strcmpi(argv[0], "append") == 0) {
if (argc != 3 && argc != 5) {
fprintf(stderr, "usage: sprite append <spritefile> <input> [<x offset> <y offset>]\n");
return -1;
}
const char *spriteFilePath = argv[1];
const char *imagePath = argv[2];
sint16 x_offset = 0;
sint16 y_offset = 0;
if (argc == 5)
{
char *endptr;
x_offset = strtol(argv[3], &endptr, 0);
if (*endptr != 0)
{
fprintf(stderr, "X offset must be an integer\n");
return -1;
}
y_offset = strtol(argv[4], &endptr, 0);
if (*endptr != 0)
{
fprintf(stderr, "Y offset must be an integer\n");
return -1;
}
}
rct_g1_element spriteElement;
uint8 *buffer;
sint32 bufferLength;
if (!sprite_file_import(imagePath, x_offset, y_offset, &spriteElement, &buffer, &bufferLength, gSpriteMode))
return -1;
if (!sprite_file_open(spriteFilePath)) {
fprintf(stderr, "Unable to open input sprite file.\n");
return -1;
}
spriteFileHeader.num_entries++;
spriteFileHeader.total_size += bufferLength;
spriteFileEntries = realloc(spriteFileEntries, spriteFileHeader.num_entries * sizeof(rct_g1_element));
sprite_entries_make_relative();
spriteFileData = realloc(spriteFileData, spriteFileHeader.total_size);
sprite_entries_make_absolute();
spriteFileEntries[spriteFileHeader.num_entries - 1] = spriteElement;
memcpy(spriteFileData + (spriteFileHeader.total_size - bufferLength), buffer, bufferLength);
spriteFileEntries[spriteFileHeader.num_entries - 1].offset = spriteFileData + (spriteFileHeader.total_size - bufferLength);
free(buffer);
if (!sprite_file_save(spriteFilePath))
return -1;
return 1;
} else if (_strcmpi(argv[0], "build") == 0) {
if (argc < 3) {
fprintf(stdout, "usage: sprite build <spritefile> <sprite description file> [silent]\n");
return -1;
}
const char *spriteFilePath = argv[1];
const char *spriteDescriptionPath = argv[2];
char* directoryPath = path_get_directory(spriteDescriptionPath);
json_error_t error;
json_t* sprite_list=json_load_file(spriteDescriptionPath, JSON_REJECT_DUPLICATES, &error);
if (sprite_list == NULL)
{
fprintf(stderr, "Error parsing sprite description file: %s at line %d column %d\n", error.text, error.line, error.column);
return -1;
}
if (!json_is_array(sprite_list))
{
fprintf(stderr, "Error: expected array\n");
json_decref(sprite_list);
return -1;
}
bool silent = (argc >= 4 && strcmp(argv[3], "silent") == 0);
spriteFileHeader.num_entries = 0;
spriteFileHeader.total_size = 0;
sprite_file_save(spriteFilePath);
fprintf(stdout, "Building: %s\n", spriteFilePath);
size_t i;
json_t* sprite_description;
json_array_foreach(sprite_list, i, sprite_description)
{
if(!json_is_object(sprite_description))
{
fprintf(stderr, "Error: expected object for sprite %lu\n", (unsigned long)i);
json_decref(sprite_list);
return -1;
}
json_t* path = json_object_get(sprite_description,"path");
if(!path || !json_is_string(path))
{
fprintf(stderr, "Error: no path provided for sprite %lu\n", (unsigned long)i);
json_decref(sprite_list);
return -1;
}
// Get x and y offsets, if present
json_t* x_offset = json_object_get(sprite_description, "x_offset");
json_t* y_offset = json_object_get(sprite_description, "y_offset");
// Resolve absolute sprite path
char *imagePath = platform_get_absolute_path(json_string_value(path), directoryPath);
rct_g1_element spriteElement;
uint8 *buffer;
int bufferLength;
if (!sprite_file_import(imagePath, x_offset==NULL ? 0 : json_integer_value(x_offset), y_offset==NULL ? 0 : json_integer_value(y_offset), &spriteElement, &buffer, &bufferLength, gSpriteMode))
{
fprintf(stderr, "Could not import image file: %s\nCanceling\n", imagePath);
json_decref(sprite_list);
free(imagePath);
return -1;
}
if (!sprite_file_open(spriteFilePath))
{
fprintf(stderr, "Unable to open sprite file: %s\nCanceling\n", spriteFilePath);
json_decref(sprite_list);
free(imagePath);
return -1;
}
spriteFileHeader.num_entries++;
spriteFileHeader.total_size += bufferLength;
spriteFileEntries = realloc(spriteFileEntries, spriteFileHeader.num_entries * sizeof(rct_g1_element));
sprite_entries_make_relative();
spriteFileData = realloc(spriteFileData, spriteFileHeader.total_size);
sprite_entries_make_absolute();
spriteFileEntries[spriteFileHeader.num_entries - 1] = spriteElement;
memcpy(spriteFileData + (spriteFileHeader.total_size - bufferLength), buffer, bufferLength);
spriteFileEntries[spriteFileHeader.num_entries - 1].offset = spriteFileData + (spriteFileHeader.total_size - bufferLength);
free(buffer);
if (!sprite_file_save(spriteFilePath))
{
fprintf(stderr, "Could not save sprite file: %s\nCanceling\n", imagePath);
json_decref(sprite_list);
free(imagePath);
return -1;
}
if (!silent)
fprintf(stdout, "Added: %s\n", imagePath);
free(imagePath);
sprite_file_close();
}
json_decref(sprite_list);
free(directoryPath);
fprintf(stdout, "Finished\n");
return 1;
} else {
fprintf(stderr, "Unknown sprite command.");
return 1;
}
}
static rct_sprite_file_palette_entry _standardPalette[256] = {
// 0 (unused)
{ 0, 0, 0, 255 },
// 1 - 9 (misc. e.g. font and water)
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
{ 0, 0, 0, 255 },
//
{ 35, 35, 23, 255 },
{ 51, 51, 35, 255 },
{ 67, 67, 47, 255 },
{ 83, 83, 63, 255 },
{ 99, 99, 75, 255 },
{ 115, 115, 91, 255 },
{ 131, 131, 111, 255 },
{ 151, 151, 131, 255 },
{ 175, 175, 159, 255 },
{ 195, 195, 183, 255 },
{ 219, 219, 211, 255 },
{ 243, 243, 239, 255 },
{ 0, 47, 51, 255 },
{ 0, 59, 63, 255 },
{ 11, 75, 79, 255 },
{ 19, 91, 91, 255 },
{ 31, 107, 107, 255 },
{ 47, 123, 119, 255 },
{ 59, 139, 135, 255 },
{ 79, 155, 151, 255 },
{ 95, 175, 167, 255 },
{ 115, 191, 187, 255 },
{ 139, 207, 203, 255 },
{ 163, 227, 223, 255 },
{ 7, 43, 67, 255 },
{ 11, 59, 87, 255 },
{ 23, 75, 111, 255 },
{ 31, 87, 127, 255 },
{ 39, 99, 143, 255 },
{ 51, 115, 159, 255 },
{ 67, 131, 179, 255 },
{ 87, 151, 191, 255 },
{ 111, 175, 203, 255 },
{ 135, 199, 219, 255 },
{ 163, 219, 231, 255 },
{ 195, 239, 247, 255 },
{ 0, 27, 71, 255 },
{ 0, 43, 95, 255 },
{ 0, 63, 119, 255 },
{ 7, 83, 143, 255 },
{ 7, 111, 167, 255 },
{ 15, 139, 191, 255 },
{ 19, 167, 215, 255 },
{ 27, 203, 243, 255 },
{ 47, 231, 255, 255 },
{ 95, 243, 255, 255 },
{ 143, 251, 255, 255 },
{ 195, 255, 255, 255 },
{ 0, 0, 35, 255 },
{ 0, 0, 79, 255 },
{ 7, 7, 95, 255 },
{ 15, 15, 111, 255 },
{ 27, 27, 127, 255 },
{ 39, 39, 143, 255 },
{ 59, 59, 163, 255 },
{ 79, 79, 179, 255 },
{ 103, 103, 199, 255 },
{ 127, 127, 215, 255 },
{ 159, 159, 235, 255 },
{ 191, 191, 255, 255 },
{ 19, 51, 27, 255 },
{ 23, 63, 35, 255 },
{ 31, 79, 47, 255 },
{ 39, 95, 59, 255 },
{ 43, 111, 71, 255 },
{ 51, 127, 87, 255 },
{ 59, 143, 99, 255 },
{ 67, 155, 115, 255 },
{ 75, 171, 131, 255 },
{ 83, 187, 147, 255 },
{ 95, 203, 163, 255 },
{ 103, 219, 183, 255 },
{ 27, 55, 31, 255 },
{ 35, 71, 47, 255 },
{ 43, 83, 59, 255 },
{ 55, 99, 75, 255 },
{ 67, 111, 91, 255 },
{ 79, 135, 111, 255 },
{ 95, 159, 135, 255 },
{ 111, 183, 159, 255 },
{ 127, 207, 183, 255 },
{ 147, 219, 195, 255 },
{ 167, 231, 207, 255 },
{ 191, 247, 223, 255 },
{ 0, 63, 15, 255 },
{ 0, 83, 19, 255 },
{ 0, 103, 23, 255 },
{ 0, 123, 31, 255 },
{ 7, 143, 39, 255 },
{ 23, 159, 55, 255 },
{ 39, 175, 71, 255 },
{ 63, 191, 91, 255 },
{ 87, 207, 111, 255 },
{ 115, 223, 139, 255 },
{ 143, 239, 163, 255 },
{ 179, 255, 195, 255 },
{ 19, 43, 79, 255 },
{ 27, 55, 99, 255 },
{ 43, 71, 119, 255 },
{ 59, 87, 139, 255 },
{ 67, 99, 167, 255 },
{ 83, 115, 187, 255 },
{ 99, 131, 207, 255 },
{ 115, 151, 215, 255 },
{ 131, 171, 227, 255 },
{ 151, 191, 239, 255 },
{ 171, 207, 247, 255 },
{ 195, 227, 255, 255 },
{ 55, 19, 15, 255 },
{ 87, 43, 39, 255 },
{ 103, 55, 51, 255 },
{ 119, 67, 63, 255 },
{ 139, 83, 83, 255 },
{ 155, 99, 99, 255 },
{ 175, 119, 119, 255 },
{ 191, 139, 139, 255 },
{ 207, 159, 159, 255 },
{ 223, 183, 183, 255 },
{ 239, 211, 211, 255 },
{ 255, 239, 239, 255 },
{ 111, 27, 0, 255 },
{ 151, 39, 0, 255 },
{ 167, 51, 7, 255 },
{ 187, 67, 15, 255 },
{ 203, 83, 27, 255 },
{ 223, 103, 43, 255 },
{ 227, 135, 67, 255 },
{ 231, 163, 91, 255 },
{ 239, 187, 119, 255 },
{ 243, 211, 143, 255 },
{ 251, 231, 175, 255 },
{ 255, 247, 215, 255 },
{ 15, 43, 11, 255 },
{ 23, 55, 15, 255 },
{ 31, 71, 23, 255 },
{ 43, 83, 35, 255 },
{ 59, 99, 47, 255 },
{ 75, 115, 59, 255 },
{ 95, 135, 79, 255 },
{ 119, 155, 99, 255 },
{ 139, 175, 123, 255 },
{ 167, 199, 147, 255 },
{ 195, 219, 175, 255 },
{ 223, 243, 207, 255 },
{ 95, 0, 63, 255 },
{ 115, 7, 75, 255 },
{ 127, 15, 83, 255 },
{ 143, 31, 95, 255 },
{ 155, 43, 107, 255 },
{ 171, 63, 123, 255 },
{ 187, 83, 135, 255 },
{ 199, 103, 155, 255 },
{ 215, 127, 171, 255 },
{ 231, 155, 191, 255 },
{ 243, 195, 215, 255 },
{ 255, 235, 243, 255 },
{ 0, 0, 63, 255 },
{ 0, 0, 87, 255 },
{ 0, 0, 115, 255 },
{ 0, 0, 143, 255 },
{ 0, 0, 171, 255 },
{ 0, 0, 199, 255 },
{ 0, 7, 227, 255 },
{ 0, 7, 255, 255 },
{ 67, 79, 255, 255 },
{ 115, 123, 255, 255 },
{ 163, 171, 255, 255 },
{ 215, 219, 255, 255 },
{ 0, 39, 79, 255 },
{ 0, 51, 111, 255 },
{ 0, 63, 147, 255 },
{ 0, 71, 183, 255 },
{ 0, 79, 219, 255 },
{ 0, 83, 255, 255 },
{ 23, 111, 255, 255 },
{ 51, 139, 255, 255 },
{ 79, 163, 255, 255 },
{ 107, 183, 255, 255 },
{ 135, 203, 255, 255 },
{ 163, 219, 255, 255 },
{ 47, 51, 0, 255 },
{ 55, 63, 0, 255 },
{ 67, 75, 0, 255 },
{ 79, 87, 0, 255 },
{ 99, 107, 7, 255 },
{ 119, 127, 23, 255 },
{ 143, 147, 43, 255 },
{ 163, 167, 71, 255 },
{ 187, 187, 99, 255 },
{ 207, 207, 131, 255 },
{ 231, 231, 171, 255 },
{ 255, 255, 207, 255 },
// 203 - 214 (Secondary remap)
{ 27, 0, 63, 255 },
{ 51, 0, 103, 255 },
{ 63, 11, 123, 255 },
{ 79, 23, 143, 255 },
{ 95, 31, 163, 255 },
{ 111, 39, 183, 255 },
{ 143, 59, 219, 255 },
{ 171, 91, 239, 255 },
{ 187, 119, 243, 255 },
{ 203, 151, 247, 255 },
{ 223, 183, 251, 255 },
{ 239, 215, 255, 255 },
// 214 - 225 (Brown)
{ 0, 19, 39, 255 },
{ 7, 31, 55, 255 },
{ 15, 47, 71, 255 },
{ 31, 63, 91, 255 },
{ 51, 83, 107, 255 },
{ 75, 103, 123, 255 },
{ 107, 127, 143, 255 },
{ 127, 147, 163, 255 },
{ 147, 171, 187, 255 },
{ 171, 195, 207, 255 },
{ 195, 219, 231, 255 },
{ 223, 243, 255, 255 },
// 226 (unknown)
{ 75, 75, 55, 255 },
// 227 - 229 (tertiary remap)
{ 0, 183, 255, 255 },
{ 0, 219, 255, 255 },
{ 0, 255, 255, 255 },
// 230 - 239 (water)
{ 99, 107, 7, 255 },
{ 99, 107, 7, 255 },
{ 135, 143, 39, 255 },
{ 123, 131, 27, 255 },
{ 99, 107, 7, 255 },
{ 151, 155, 55, 255 },
{ 151, 155, 55, 255 },
{ 227, 227, 155, 255 },
{ 203, 203, 115, 255 },
{ 151, 155, 55, 255 },
// 240 - 242 (chain lift)
{ 91, 91, 67, 255 },
{ 107, 107, 83, 255 },
{ 123, 123, 99, 255 },
// Old 243 - 245, changed to nice shade remap below
// { 47, 47, 47, 255 },
// { 47, 47, 47, 255 },
// { 47, 71, 87, 255 },
// 243 to 254 (primary remap)
{ 47, 51, 111, 255 },
{ 47, 55, 131, 255 },
{ 51, 63, 151, 255 },
{ 51, 67, 171, 255 },
{ 47, 75, 191, 255 },
{ 43, 79, 211, 255 },
{ 35, 87, 231, 255 },
{ 31, 95, 255, 255 },
{ 39, 127, 255, 255 },
{ 51, 155, 255, 255 },
{ 63, 183, 255, 255 },
{ 75, 207, 255, 255 },
// 255 (unused?)
{ 0, 0, 0, 255 }
};