diff options
| author | Louis Burda <quent.burda@gmail.com> | 2022-06-02 15:28:40 +0200 |
|---|---|---|
| committer | Louis Burda <quent.burda@gmail.com> | 2022-06-02 15:28:40 +0200 |
| commit | 5bc16063c29aa4d3d287ebd163ccdbcbf54c4f9f (patch) | |
| tree | c131f947a37b3af2d14d41e9eda098bdec2d061c /gbdk/gbdk-support | |
| parent | 78a5f810b22f0d8cafa05f638b0cb2e889824859 (diff) | |
| download | cscg2022-gearboy-master.tar.gz cscg2022-gearboy-master.zip | |
Diffstat (limited to 'gbdk/gbdk-support')
67 files changed, 18447 insertions, 0 deletions
diff --git a/gbdk/gbdk-support/bankpack/LICENSE b/gbdk/gbdk-support/bankpack/LICENSE new file mode 100644 index 00000000..fdddb29a --- /dev/null +++ b/gbdk/gbdk-support/bankpack/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <https://unlicense.org> diff --git a/gbdk/gbdk-support/bankpack/Makefile b/gbdk/gbdk-support/bankpack/Makefile new file mode 100644 index 00000000..635d1408 --- /dev/null +++ b/gbdk/gbdk-support/bankpack/Makefile @@ -0,0 +1,30 @@ +# bankcheck (auto bank tool) makefile + +ifndef TARGETDIR +TARGETDIR = /opt/gbdk +endif + +ifeq ($(OS),Windows_NT) + BUILD_OS := Windows_NT +else + BUILD_OS := $(shell uname -s) +endif + +# Target older macOS version than whatever build OS is for better compatibility +ifeq ($(BUILD_OS),Darwin) + export MACOSX_DEPLOYMENT_TARGET=10.10 +endif + +CC = $(TOOLSPREFIX)gcc +CFLAGS = -ggdb -O -Wno-incompatible-pointer-types -DGBDKLIBDIR=\"$(TARGETDIR)\" +OBJ = bankpack.o files.o obj_data.o list.o path_ops.o +BIN = bankpack + +all: $(BIN) + +$(BIN): $(OBJ) + +clean: + rm -f *.o $(BIN) *~ + rm -f *.exe + diff --git a/gbdk/gbdk-support/bankpack/bankpack.c b/gbdk/gbdk-support/bankpack/bankpack.c new file mode 100644 index 00000000..8107c790 --- /dev/null +++ b/gbdk/gbdk-support/bankpack/bankpack.c @@ -0,0 +1,182 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +// #include <unistd.h> +#include <stdbool.h> +#include <stdint.h> +#include <time.h> + +#include "obj_data.h" +#include "files.h" + +bool g_option_verbose = false; +bool g_option_cartsize = false; + +static void display_help(void); +static int handle_args(int argc, char * argv[]); +static void option_set_verbose(bool is_enabled); +static void init(void); +void cleanup(void); + + +static void display_help(void) { + fprintf(stdout, + "bankalloc [options] objfile1 objfile2 etc\n" + "Use: Read .o files and auto-assign areas with bank=255.\n" + " Typically called by Lcc compiler driver before linker.\n" + "\n" + "Options\n" + "-h : Show this help\n" + "-lkin=<file> : Load object files specified in linker file <file>\n" + "-lkout=<file>: Write list of object files out to linker file <file>\n" + "-yt<mbctype> : Set MBC type per ROM byte 149 in Decimal or Hex (0xNN) (see pandocs)\n" + "-mbc=N : Similar to -yt, but sets MBC type directly to N instead\n" + " of by intepreting ROM byte 149\n" + " mbc1 will exclude banks {0x20,0x40,0x60} max=127, \n" + " mbc2 max=15, mbc3 max=127, mbc5 max=255 (not 511!) \n" + "-min=N : Min assigned ROM bank is N (default 1)\n" + "-max=N : Max assigned ROM bank is N, error if exceeded\n" + "-ext=<.ext> : Write files out with <.ext> instead of source extension\n" + "-path=<path> : Write files out to <path> (<path> *MUST* already exist)\n" + "-sym=<prefix>: Add symbols starting with <prefix> to match + update list.\n" + " Default entry is \"___bank_\" (see below)\n" + "-cartsize : Print min required cart size as \"autocartsize:<NNN>\"\n" + "-plat=<plat> : Select platform specific behavior (default:gb) (gb,sms)\n" + "-random : Distribute banks randomly for testing (honors -min/-max)\n" + "-v : Verbose output, show assignments\n" + "\n" + "Example: \"bankpack -ext=.rel -path=some/newpath/ file1.o file2.o\"\n" + "Unless -ext or -path specify otherwise, input files are overwritten.\n" + "\n" + "Default MBC type is not set. It *must* be specified by -mbc= or -yt!\n" + "\n" + "The following will have FF and 255 replaced with the assigned bank:\n" + "A _CODE_255 size <size> flags <flags> addr <address>\n" + "S b_<function name> Def0000FF\n" + "S ___bank_<const name> Def0000FF\n" + " (Above can be made by: const void __at(255) __bank_<const name>;\n" + ); +} + + +static int handle_args(int argc, char * argv[]) { + + int i; + + if( argc < 2 ) { + display_help(); + return false; + } + + // Start at first optional argument, argc is zero based + for (i = 1; i <= (argc -1); i++ ) { + + if (argv[i][0] == '-') { + if (strstr(argv[i], "-h") == argv[i]) { + display_help(); + return false; // Don't parse input when -h is used + } else if (strstr(argv[i], "-min=") == argv[i]) { + if (!banks_set_min(atoi(argv[i] + 5))) { + printf("BankPack: ERROR: Invalid min bank: %s\n", argv[i] + 5); + return false; + } + } else if (strstr(argv[i], "-max=") == argv[i]) { + if (!banks_set_max(atoi(argv[i] + 5))) { + printf("BankPack: ERROR: Invalid max bank: %s\n", argv[i] + 5); + return false; + } + } else if (strstr(argv[i], "-ext=") == argv[i]) { + files_set_out_ext(argv[i] + 5); + } else if (strstr(argv[i], "-path=") == argv[i]) { + files_set_out_path(argv[i] + 6); + } else if (strstr(argv[i], "-mbc=") == argv[i]) { + banks_set_mbc(atoi(argv[i] + 5)); + } else if (strstr(argv[i], "-yt") == argv[i]) { + banks_set_mbc_by_rom_byte_149(strtol(argv[i] + 3, NULL, 0)); + } else if (strstr(argv[i], "-v") == argv[i]) { + option_set_verbose(true); + } else if (strstr(argv[i], "-sym=") == argv[i]) { + symbol_match_add(argv[i] + 5); + } else if (strstr(argv[i], "-cartsize") == argv[i]) { + g_option_cartsize = true; + } else if (strstr(argv[i], "-plat=") == argv[i]) { + banks_set_platform(argv[i] + 6); + } else if (strstr(argv[i], "-random") == argv[i]) { + banks_set_random(true); + } else if (strstr(argv[i], "-lkin=") == argv[i]) { + files_read_linkerfile(argv[i] + strlen("-lkin=")); + } else if (strstr(argv[i], "-lkout=") == argv[i]) { + files_set_linkerfile_outname(argv[i] + strlen("-lkout=")); + } else + printf("BankPack: Warning: Ignoring unknown option %s\n", argv[i]); + } else { + // Add to list of object files to process + files_add(argv[i]); + } + } + + return true; +} + + +static void option_set_verbose(bool is_enabled) { + g_option_verbose = is_enabled; +} + + +static int matches_extension(char * filename, char * extension) { + return (strcmp(filename + (strlen(filename) - strlen(extension)), extension) == 0); +} + + +static void init(void) { + srand( time(0) ); + files_init(); + obj_data_init(); +} + + +void cleanup(void) { + files_cleanup(); + obj_data_cleanup(); +} + + +int main( int argc, char *argv[] ) { + + // Exit with failure by default + int ret = EXIT_FAILURE; + + // Register cleanup with exit handler + atexit(cleanup); + + init(); + + if (handle_args(argc, argv)) { + + // Require MBC for Game Boy + // SMS doesn't require an MBC setting + if ((banks_get_platform() == PLATFORM_GB) && (banks_get_mbc_type() == MBC_TYPE_NONE)) + printf("BankPack: ERROR: auto-banking does not work with unbanked ROMS (no MBC for Game Boy)\n"); + else { + // Extract areas, sort and assign them to banks + // then rewrite object files as needed + files_extract(); + files_rewrite(); + + if (g_option_verbose) + banks_show(); + if (g_option_cartsize) + fprintf(stdout,"autocartsize:%d\n",banks_calc_cart_size()); + + cleanup(); + ret = EXIT_SUCCESS; + } + } + + return ret; // Exit with failure by default +}
\ No newline at end of file diff --git a/gbdk/gbdk-support/bankpack/common.h b/gbdk/gbdk-support/bankpack/common.h new file mode 100644 index 00000000..43235216 --- /dev/null +++ b/gbdk/gbdk-support/bankpack/common.h @@ -0,0 +1,38 @@ + +#ifndef _COMMON_H +#define _COMMON_H + +enum { + MBC_TYPE_NONE = 0, + MBC_TYPE_MBC1 = 1, + MBC_TYPE_MBC2 = 2, + MBC_TYPE_MBC3 = 3, + /* MBC4 doesn't exist */ + MBC_TYPE_MBC5 = 5, + + MBC_TYPE_MIN = MBC_TYPE_MBC1, + MBC_TYPE_MAX = MBC_TYPE_MBC5, + MBC_TYPE_DEFAULT = MBC_TYPE_NONE +}; + +#define MAX_FILE_STR 2048 + +#define BANK_NUM_UNASSIGNED 0xFFFFU +#define BANK_NUM_AUTO 255 +#define BANK_NUM_ROM_MIN 1 +#define BANK_NUM_ROM_MAX 255 +#define BANK_ROM_TOTAL 256 // Banks 0-255 +#define BANK_ROM_CALC_MAX 512 // Banks 0-512 (>256 not supported for auto-banking right now) +#define BANK_SIZE_ROM 0x4000U + +#define BANK_ITEM_COUNT_MAX 0xFFFF + +#define BANK_NUM_ROM_MAX_MBC1 127 +#define BANK_NUM_ROM_MAX_MBC2 15 +#define BANK_NUM_ROM_MAX_MBC3 127 +#define BANK_NUM_ROM_MAX_MBC5 255 // 511 // TODO: support full MBC5 address range (currently 8 bit only) + +#define STRINGIFY(x) #x +#define TOSTR(x) STRINGIFY(x) + +#endif // _COMMON_H diff --git a/gbdk/gbdk-support/bankpack/files.c b/gbdk/gbdk-support/bankpack/files.c new file mode 100644 index 00000000..30574462 --- /dev/null +++ b/gbdk/gbdk-support/bankpack/files.c @@ -0,0 +1,284 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <unistd.h> +#include <stdint.h> + +#include "common.h" +#include "path_ops.h" +#include "list.h" +#include "files.h" +#include "obj_data.h" + +static void files_set_output_name(void); +static char * file_read_to_buffer(char *); + +list_type filelist; + +char g_out_ext[MAX_FILE_STR]; +char g_out_path[MAX_FILE_STR]; +char g_out_linkerfile_name[MAX_FILE_STR] = {'\0'}; + +void files_set_out_ext(char * ext_str) { + if (snprintf(g_out_ext, sizeof(g_out_ext), "%s", ext_str) > sizeof(g_out_ext)) + printf("Bankpack: Warning: truncated output extension to:%s\n",g_out_ext); +} + +void files_set_out_path(char * path_str) { + if (snprintf(g_out_path, sizeof(g_out_path), "%s", path_str) > sizeof(g_out_path)) + printf("Bankpack: Warning: truncated output path to:%s\n",g_out_path); +} + + +void files_init(void) { + list_init(&filelist, sizeof(file_item)); + g_out_ext[0] = '\0'; + g_out_path[0] = '\0'; +} + +void files_cleanup(void) { + list_cleanup(&filelist); +} + + +// Reads a list of object files from a linkerfile +// (one filename per line) and adds them +void files_read_linkerfile(char * filename_in) { + char strline_in[MAX_FILE_STR] = ""; + + FILE * in_file; + + // Open each object file and try to process all the lines + in_file = fopen(filename_in, "r"); + if (in_file) { + + // Read one line at a time into \0 terminated string + // Skip empty lines + // Strip newlines and carraige returns from end of filename + while ( fgets(strline_in, sizeof(strline_in), in_file) != NULL) { + if ((strline_in[0] != '\n') || (strline_in[0] != '\r')) { + strline_in[strcspn(strline_in, "\r\n")] = '\0'; + files_add(strline_in); + } + } + fclose(in_file); + + } // end: if valid file + else { + printf("BankPack: ERROR: failed to open input linkerfile: %s\n", filename_in); + exit(EXIT_FAILURE); + } +} + + +void files_set_linkerfile_outname(char * filename) { + + if (snprintf(g_out_linkerfile_name, sizeof(g_out_linkerfile_name), "%s", filename) > sizeof(g_out_linkerfile_name)) + printf("Warning: truncated output linkerfile name to:%s\n",g_out_linkerfile_name); +} + + +// Writes a list of loaded object filenames to +// a linkerfile (one filename per line) +void files_write_linkerfile(void) { + uint32_t c; + file_item * files = (file_item *)filelist.p_array; + FILE * out_file; + + // Open the linkerfile output and write all object filenames + out_file = fopen(g_out_linkerfile_name, "w"); + if (out_file) { + + // Process stored file names + for (c = 0; c < filelist.count; c++) + fprintf(out_file, "%s\n", files[c].name_out); + + fclose(out_file); + + } // end: if valid file + else { + printf("BankPack: ERROR: failed to open output linkerfile: %s\n", g_out_linkerfile_name); + exit(EXIT_FAILURE); + } +} + + +void files_add(char * filename) { + + file_item newfile; + + if (snprintf(newfile.name_in, sizeof(newfile.name_in), "%s", filename) > sizeof(newfile.name_in)) + printf("Warning: truncated input filename to:%s\n",newfile.name_in); + + newfile.name_out[0] = '\0'; + newfile.rewrite_needed = false; + newfile.bank_num = BANK_NUM_UNASSIGNED; + + list_additem(&filelist, &newfile); +} + + +char * file_get_name_in_by_id(uint32_t file_id) { + + file_item * files = (file_item *)filelist.p_array; + + if ((file_id >= 0) && (file_id < filelist.count)) + return files[file_id].name_in; + else + return (char *)"\0"; +} + + +char * file_get_name_out_by_id(uint32_t file_id) { + + file_item * files = (file_item *)filelist.p_array; + + if ((file_id >= 0) && (file_id < filelist.count)) + return files[file_id].name_out; + else + return (char *)"\0"; +} + + +// Update output names based on -path= and -ext= option params +static void files_set_output_name(void) { + + uint32_t c; + file_item * files = (file_item *)filelist.p_array; + + // Process stored file names + for (c = 0; c < filelist.count; c++) { + + snprintf(files[c].name_out, sizeof(files[c].name_out), "%s", files[c].name_in); + + if (g_out_ext[0]) + filename_replace_extension(files[c].name_out, g_out_ext, sizeof(files[c].name_out)); + if (g_out_path[0]) + filename_replace_path(files[c].name_out, g_out_path, sizeof(files[c].name_out)); + } +} + + +// Read from a file into a buffer (will allocate needed memory) +// Returns NULL if reading file didn't succeed +static char * file_read_to_buffer(char * filename) { + + long fsize; + FILE * file_in = fopen(filename, "rb"); + char * filedata = NULL; + + if (file_in) { + // Get file size + fseek(file_in, 0, SEEK_END); + fsize = ftell(file_in); + if (fsize != -1L) { + fseek(file_in, 0, SEEK_SET); + + filedata = malloc(fsize + 1); // One extra byte to add string null terminator + if (filedata) { + if (fsize != fread(filedata, 1, fsize, file_in)) + printf("BankPack: Warning: File read size didn't match expected for %s\n", filename); + filedata[fsize] = '\0'; // Add null string terminator at end + } else printf("BankPack: ERROR: Failed to allocate memory to read file %s\n", filename); + + } else printf("BankPack: ERROR: Failed to read size of file %s\n", filename); + + fclose(file_in); + } else printf("BankPack: ERROR: Failed to open input file %s\n", filename); + + return filedata; +} + + +// Extract areas from files, then collected assign them to banks +void files_extract(void) { + uint32_t c; + char strline_in[OBJ_NAME_MAX_STR_LEN] = ""; + file_item * files = (file_item *)filelist.p_array; + FILE * obj_file; + + // Process stored file names + for (c = 0; c < filelist.count; c++) { + + // Open each object file and try to process all the lines + obj_file = fopen(files[c].name_in, "r"); + if (obj_file) { + + // Read one line at a time into \0 terminated string + while ( fgets(strline_in, sizeof(strline_in), obj_file) != NULL) { + if (strline_in[0] == 'A') + areas_add(strline_in, c); + else if (strline_in[0] == 'S') + symbols_add(strline_in, c); + } + fclose(obj_file); + + } // end: if valid file + else { + printf("BankPack: ERROR: failed to open file %s\n", files[c].name_in); + exit(EXIT_FAILURE); + } + } + + obj_data_process(&filelist); + files_set_output_name(); +} + + +void files_rewrite(void) { + + uint32_t c; + char * in_file_buf = NULL; + char * strline_in = NULL; + FILE * out_file = NULL; + file_item * files = (file_item *)filelist.p_array; + + // If linkerfile output is enabled, write it + if (g_out_linkerfile_name[0] != '\0') + files_write_linkerfile(); + + // Process stored file names - (including unchanged ones since output may get new extensions or path) + for (c = 0; c < filelist.count; c++) { + + in_file_buf = file_read_to_buffer(files[c].name_in); + if (!in_file_buf) + exit(EXIT_FAILURE); + + out_file = fopen(files[c].name_out, "w"); + if (!out_file) { + printf("BankPack: ERROR: failed to open output file %s\n", files[c].name_out); + exit(EXIT_FAILURE); + } + + // Read one line at a time from a string buffer + // Note: strtok will replace the \n chars with \0 on each split, so be sure to add those back + strline_in = strtok(in_file_buf,"\n"); + while (strline_in != NULL) { + + // Only modify lines in flagged files + if (files[c].rewrite_needed) { + + if (!area_modify_and_write_to_file(strline_in, out_file, files[c].bank_num)) { + if (!symbol_modify_and_write_to_file(strline_in, out_file, files[c].bank_num, c)) { + // Default is to write line with no changes + fprintf(out_file, "%s\n", strline_in); + } + } + } else + fprintf(out_file, "%s\n", strline_in); + + // Read next line + strline_in = strtok(NULL,"\n"); + } + + if (in_file_buf) + free(in_file_buf); + fclose(out_file); + } + +} diff --git a/gbdk/gbdk-support/bankpack/files.h b/gbdk/gbdk-support/bankpack/files.h new file mode 100644 index 00000000..267c7d61 --- /dev/null +++ b/gbdk/gbdk-support/bankpack/files.h @@ -0,0 +1,35 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _FILES_H +#define _FILES_H + +#include "common.h" + +typedef struct file_item { + char name_in[MAX_FILE_STR]; + uint16_t bank_num; + bool rewrite_needed; + char name_out[MAX_FILE_STR]; +} file_item; + + +void files_init(void); +void files_cleanup(void); +void files_add(char *); + +void files_read_linkerfile(char *); +void files_set_linkerfile_outname(char *); +void files_write_linkerfile(void); + +char * file_get_name_in_by_id(uint32_t); +char * file_get_name_out_by_id(uint32_t); + +void files_set_out_ext(char *); +void files_set_out_path(char *); + +void files_extract(void); +void files_rewrite(void); + +#endif // _FILES_H diff --git a/gbdk/gbdk-support/bankpack/list.c b/gbdk/gbdk-support/bankpack/list.c new file mode 100644 index 00000000..1a382357 --- /dev/null +++ b/gbdk/gbdk-support/bankpack/list.c @@ -0,0 +1,66 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> + +#include "list.h" + +#define LIST_GROW_SIZE 50 // grow array by N entries at a time + +// Initialize the list and it's array +// typesize *must* match the type that will be used with the array +void list_init(list_type * p_list, size_t array_typesize) { + p_list->typesize = array_typesize; + p_list->count = 0; + p_list->size = LIST_GROW_SIZE; + p_list->p_array = (void *)malloc(p_list->size * p_list->typesize); + + if (!p_list->p_array) { + printf("BankPack: ERROR: Failed to allocate memory for list!\n"); + exit(EXIT_FAILURE); + } +} + + +// Free the array memory allocated for the list +void list_cleanup(list_type * p_list) { + if (p_list->p_array) { + free (p_list->p_array); + p_list->p_array = NULL; + } +} + + +// Add a new item to the lists array, resize if needed +// p_newitem *must* be the same type the list was initialized with +void list_additem(list_type * p_list, void * p_newitem) { + + void * tmp_list; + + p_list->count++; + + // Grow array if needed + if (p_list->count == p_list->size) { + // Save a copy in case reallocation fails + tmp_list = p_list->p_array; + + p_list->size += p_list->typesize * LIST_GROW_SIZE; + p_list->p_array = (void *)realloc(p_list->p_array, p_list->size * p_list->typesize); + // If realloc failed, free original buffer before quitting + if (!p_list->p_array) { + printf("BankPack: ERROR: Failed to reallocate memory for list!\n"); + if (tmp_list) free(tmp_list); + exit(EXIT_FAILURE); + } + } + + // Copy new entry + memcpy(p_list->p_array + ((p_list->count - 1) * p_list->typesize), + p_newitem, + p_list->typesize); +} + diff --git a/gbdk/gbdk-support/bankpack/list.h b/gbdk/gbdk-support/bankpack/list.h new file mode 100644 index 00000000..ef9d3d52 --- /dev/null +++ b/gbdk/gbdk-support/bankpack/list.h @@ -0,0 +1,20 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _LIST_H +#define _LIST_H + + +typedef struct list_type { + void * p_array; + uint32_t size; + uint32_t count; + size_t typesize; +} list_type; + +void list_init(list_type *, size_t); +void list_cleanup(list_type *); +void list_additem(list_type *, void *); + +#endif // _LIST_H
\ No newline at end of file diff --git a/gbdk/gbdk-support/bankpack/obj_data.c b/gbdk/gbdk-support/bankpack/obj_data.c new file mode 100644 index 00000000..88adf014 --- /dev/null +++ b/gbdk/gbdk-support/bankpack/obj_data.c @@ -0,0 +1,762 @@ + +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "list.h" +#include "files.h" +#include "obj_data.h" + + +static bool bank_check_mbc1_ok(uint32_t bank_num); +static void bank_update_assigned_minmax(uint16_t bank_num); +static void bank_update_all_max(uint16_t bank_num); +static void bank_add_area(bank_item * p_bank, uint16_t bank_num, area_item * p_area); +static void bank_check_area_size(area_item * p_area); +static void banks_assign_area(area_item * p_area); +static int area_item_compare(const void* a, const void* b); +static void areas_sort(void); +static bool symbol_banked_check_rewrite_ok(char *, uint32_t); + +extern bool g_option_verbose; + + +list_type banklist; +list_type arealist; +list_type symbollist; +list_type symbol_matchlist; + +uint16_t bank_limit_rom_min = BANK_NUM_ROM_MIN; +uint16_t bank_limit_rom_max = BANK_NUM_ROM_MAX; + + +uint16_t bank_assign_rom_min = BANK_NUM_ROM_MAX; +uint16_t bank_assign_rom_max = 0; +uint16_t bank_all_rom_max = 0; + +int g_mbc_type = MBC_TYPE_DEFAULT; +int g_platform = PLATFORM_DEFAULT; +bool g_opt_random_assign = false; + + +int banks_get_platform(void) { + return g_platform; +} + +void banks_set_platform(char * platform_str) { + + if (strcmp(platform_str, PLATFORM_STR_GB) == 0) + g_platform = PLATFORM_GB; + else if (strcmp(platform_str, PLATFORM_STR_AP) == 0) + g_platform = PLATFORM_GB; // Analogue Pocket uses GB platform + else if (strcmp(platform_str, PLATFORM_STR_DUCK) == 0) + g_platform = PLATFORM_GB; // Megaduck uses GB platform + else if (strcmp(platform_str, PLATFORM_STR_SMS) == 0) + g_platform = PLATFORM_SMS; + else if (strcmp(platform_str, PLATFORM_STR_GG) == 0) + g_platform = PLATFORM_SMS; // GG uses SMS platform + else if (strcmp(platform_str, PLATFORM_STR_MSXDOS) == 0) + g_platform = PLATFORM_SMS; // MSXDOS uses SMS platform + else + printf("BankPack: Warning: Invalid platform option %s\n", platform_str); +} + + + +int banks_get_mbc_type(void) { + return g_mbc_type; +} + + +// Set MBC type by interpreting from byte 149 +// +// For lcc linker option: -Wl-ytN where N is one of the numbers below +// (from makebin.c in SDCC) +// +// ROM Byte 0147: Cartridge type: +// 0-ROM ONLY 12-ROM+MBC3+RAM +// 1-ROM+MBC1 13-ROM+MBC3+RAM+BATT +// 2-ROM+MBC1+RAM 19-ROM+MBC5 +// 3-ROM+MBC1+RAM+BATT 1A-ROM+MBC5+RAM +// 5-ROM+MBC2 1B-ROM+MBC5+RAM+BATT +// 6-ROM+MBC2+BATTERY 1C-ROM+MBC5+RUMBLE +// 8-ROM+RAM 1D-ROM+MBC5+RUMBLE+SRAM +// 9-ROM+RAM+BATTERY 1E-ROM+MBC5+RUMBLE+SRAM+BATT +// B-ROM+MMM01 1F-Pocket Camera +// C-ROM+MMM01+SRAM FD-Bandai TAMA5 +// D-ROM+MMM01+SRAM+BATT FE - Hudson HuC-3 +// F-ROM+MBC3+TIMER+BATT FF - Hudson HuC-1 +// 10-ROM+MBC3+TIMER+RAM+BATT +// 11-ROM+MBC3 +void banks_set_mbc_by_rom_byte_149(int mbc_type_rom_byte) { + + switch (mbc_type_rom_byte) { + // NO MBC + case 0x00U: // 0-ROM ONLY + case 0x08U: // 2-ROM+MBC1+RAM + case 0x09U: // 8-ROM+RAM + case 0x0BU: // B-ROM+MMM01 + case 0x0CU: // C-ROM+MMM01+SRAM + case 0x0DU: // D-ROM+MMM01+SRAM+BATT + banks_set_mbc(MBC_TYPE_NONE); + break; + + // MBC 1 + case 0x01U: // 1-ROM+MBC1 + case 0x02U: // 2-ROM+MBC1+RAM + case 0x03U: // 3-ROM+MBC1+RAM+BATT + banks_set_mbc(MBC_TYPE_MBC1); + break; + + // MBC 2 + case 0x05U: // 5-ROM+MBC2 + case 0x06U: // 6-ROM+MBC2+BATTERY + banks_set_mbc(MBC_TYPE_MBC2); + break; + + // MBC 3 + case 0x0FU: // F-ROM+MBC3+TIMER+BATT + case 0x10U: // 10-ROM+MBC3+TIMER+RAM+BATT + case 0x11U: // 11-ROM+MBC3 + case 0x12U: // 12-ROM+MBC3+RAM + case 0x13U: // 13-ROM+MBC3+RAM+BATT + banks_set_mbc(MBC_TYPE_MBC3); + break; + + // MBC 5 + case 0x19U: // 19-ROM+MBC5 + case 0x1AU: // 1A-ROM+MBC5+RAM + case 0x1BU: // 1B-ROM+MBC5+RAM+BATT + case 0x1CU: // 1C-ROM+MBC5+RUMBLE + case 0x1DU: // 1D-ROM+MBC5+RUMBLE+SRAM + case 0x1EU: // 1E-ROM+MBC5+RUMBLE+SRAM+BATT + banks_set_mbc(MBC_TYPE_MBC5); + break; + + default: + printf("BankPack: Warning: unrecognized MBC option -yt=%x\n", mbc_type_rom_byte); + break; + } +} + + +// Set MBC type directly +void banks_set_mbc(int mbc_type) { + + uint16_t mbc_bank_limit_rom_max; + + switch (mbc_type) { + case MBC_TYPE_NONE: + g_mbc_type = MBC_TYPE_NONE; + break; + case MBC_TYPE_MBC1: + g_mbc_type = mbc_type; + mbc_bank_limit_rom_max = BANK_NUM_ROM_MAX_MBC1; + break; + case MBC_TYPE_MBC2: + g_mbc_type = mbc_type; + mbc_bank_limit_rom_max = BANK_NUM_ROM_MAX_MBC2; + break; + case MBC_TYPE_MBC3: + g_mbc_type = mbc_type; + mbc_bank_limit_rom_max = BANK_NUM_ROM_MAX_MBC3; + break; + case MBC_TYPE_MBC5: + g_mbc_type = mbc_type; + mbc_bank_limit_rom_max = BANK_NUM_ROM_MAX_MBC5; + break; + default: + printf("BankPack: Warning: unrecognized MBC option -mbc%d!\n", mbc_type); + break; + } + if (mbc_bank_limit_rom_max < bank_limit_rom_max) + bank_limit_rom_max = mbc_bank_limit_rom_max; +} + + + +// From makebin +// Byte +// 0148 - ROM size -yt<N> +// 0 - 256Kbit = 32KByte = 2 banks +// 1 - 512Kbit = 64KByte = 4 banks +// 2 - 1Mbit = 128KByte = 8 banks +// 3 - 2Mbit = 256KByte = 16 banks +// 4 - 4Mbit = 512KByte = 32 banks +// 5 - 8Mbit = 1MByte = 64 banks +// 6 - 16Mbit = 2MByte = 128 banks +// 7 - 16Mbit = 2MByte = 256 banks +// 8 - 16Mbit = 2MByte = 512 banks +// +// Not supported by makebin: +// $52 - 9Mbit = 1.1MByte = 72 banks +// $53 - 10Mbit = 1.2MByte = 80 banks +// $54 - 12Mbit = 1.5MByte = 96 banks +uint32_t banks_calc_cart_size(void) { + + uint32_t req_banks = 1; + + if ((bank_all_rom_max + 1) > BANK_ROM_CALC_MAX) { + printf("BankPack: Warning! Can't calc cart size, too many banks: %d (max %d)\n", (bank_all_rom_max + 1), BANK_ROM_CALC_MAX); + return (0); + } + + // Calculate nearest upper power of 2 + while (req_banks < (bank_all_rom_max + 1)) + req_banks *= 2; + + return (req_banks); +} + + +bool banks_set_min(uint16_t bank_num) { + if (bank_num < BANK_NUM_ROM_MIN) + return false; + else bank_limit_rom_min = bank_num; + + return true; +} + +bool banks_set_max(uint16_t bank_num) { + if (bank_num > BANK_NUM_ROM_MAX) + return false; + else bank_limit_rom_max = bank_num; + + return true; +} + + +void banks_set_random(bool is_random) { + g_opt_random_assign = is_random; +} + + + +void obj_data_init(void) { + int c; + bank_item newbank; + + list_init(&banklist, sizeof(bank_item)); + list_init(&arealist, sizeof(area_item)); + list_init(&symbollist, sizeof(symbol_item)); + list_init(&symbol_matchlist, sizeof(symbol_match_item)); + + // Pre-populate bank list with max number of of banks + // to allow handling fixed-bank (non-autobank) areas + newbank.size = newbank.free = BANK_SIZE_ROM; + newbank.type = BANK_TYPE_UNSET; + newbank.item_count = 0; + for (c=0; c < BANK_ROM_TOTAL; c++) + list_additem(&banklist, &newbank); + + // Add default symbol match and replace + symbol_match_add("___bank_"); +} + + +void obj_data_cleanup(void) { + list_cleanup(&banklist); + list_cleanup(&arealist); + list_cleanup(&symbollist); + list_cleanup(&symbol_matchlist); +} + + +// Add a symbol to the match and replace list +void symbol_match_add(char * symbol_str) { + + symbol_match_item newmatch; + + if (snprintf(newmatch.name, sizeof(newmatch.name), "%s", symbol_str) > sizeof(newmatch.name)) + printf("BankPack: Warning: truncated symbol match string to:%s\n",newmatch.name); + + list_additem(&symbol_matchlist, &newmatch); +} + + + +// Add an area into the pool of areas to assign (if it's banked CODE) +int areas_add(char * area_str, uint32_t file_id) { + + area_item newarea; + + // Only match areas which are banked ("_CODE_" vs "_CODE") and ("_LIT_") + if (AREA_LINE_RECORDS == sscanf(area_str,"A _CODE _%3d size %4x flags %*4x addr %*4x", + &newarea.bank_num_in, &newarea.size)) { + newarea.type = BANK_TYPE_DEFAULT; + } + else if (AREA_LINE_RECORDS == sscanf(area_str,"A _LIT_%3d size %4x flags %*4x addr %*4x", + &newarea.bank_num_in, &newarea.size)) { + newarea.type = BANK_TYPE_LIT_EXCLUSIVE; + } + else + return false; + + // Only process areas with (size > 0) + if (newarea.size > 0) { + if (newarea.type == BANK_TYPE_LIT_EXCLUSIVE) + sprintf(newarea.name, "_LIT_"); // Hardwired to _LIT_ for now + else + sprintf(newarea.name, "_CODE_"); // Hardwired to _CODE_ for now + newarea.file_id = file_id; + newarea.bank_num_out = BANK_NUM_UNASSIGNED; + list_additem(&arealist, &newarea); + return true; + } else + return false; +} + + +// Add an area into the pool of areas to assign (if it's banked CODE) +int symbols_add(char * symbol_str, uint32_t file_id) { + + symbol_item newsymbol; + + if (SYMBOL_LINE_RECORDS == sscanf(symbol_str,"S %" TOSTR(OBJ_NAME_MAX_STR_LEN) "s Def00%4x", + newsymbol.name, &newsymbol.bank_num_in)) { + + // Symbols that start with b_ store the bank num for the matching symbol without 'b' + newsymbol.is_banked_def = (newsymbol.name[0] == 'b'); + newsymbol.file_id = file_id; + newsymbol.found_matching_symbol = false; + + // Don't add banked symbols if they're not set to the autobank bank # + if ((newsymbol.is_banked_def) && (newsymbol.bank_num_in != BANK_NUM_AUTO)) + return false; + + list_additem(&symbollist, &newsymbol); + return true; + } + return false; +} + + +// Track Min/Max assigned banks used +static void bank_update_assigned_minmax(uint16_t bank_num) { + + if (bank_num > bank_assign_rom_max) + bank_assign_rom_max = bank_num; + + if (bank_num < bank_assign_rom_min) + bank_assign_rom_min = bank_num; + + bank_update_all_max(bank_num); +} + + +// Tracks Max bank for *all* banks used, including fixed banks outside max limits +static void bank_update_all_max(uint16_t bank_num) { + + if (bank_num > bank_all_rom_max) + bank_all_rom_max = bank_num; +} + + +// Add an area to a bank. +// It should only error out when there isn't enough +// room with a fixed-bank area (non-autobank) +static void bank_add_area(bank_item * p_bank, uint16_t bank_num, area_item * p_area) { + + // Make sure there is room and then update free space + if (p_area->size > p_bank->free) { + + // Trying to add an auto-bank area to a full bank should be prevented by previous tests, but just in case + if (p_area->bank_num_in == BANK_NUM_AUTO) { + printf("BankPack: ERROR! Auto-banked Area %s, bank %d, size %d won't fit in assigned bank %d (free %d)\n", + p_area->name, p_area->bank_num_in, p_area->size, bank_num, p_bank->free); + exit(EXIT_FAILURE); + } else { + // Only warn for fixed bank areas. Don't exit and add the area anyway + printf("BankPack: Warning: Fixed-bank Area %s, bank %d, size %d won't fit in assigned bank %d (free %d)\n", + p_area->name, p_area->bank_num_in, p_area->size, bank_num, p_bank->free); + } + + // Force bank space to zero, subtracting at this point would overflow + p_bank->free = 0; + } else + p_bank->free -= p_area->size; + + // Copy bank type from area: some platforms (sms) don't allow mixing of area types in the same bank + // Assign outbound bank number for the area + p_bank->type = p_area->type; + p_bank->item_count++; + p_area->bank_num_out = bank_num; + bank_update_assigned_minmax(bank_num); +} + + +// Check to see if the specified bank is allowed with MBC1 +static bool bank_check_mbc1_ok(uint32_t bank_num) { + + if ((bank_num == 0x20U) || (bank_num == 0x40U) || (bank_num == 0x60U)) + return false; + else + return true; +} + + +// Checks to make sure area size is not larger than an entire bank +static void bank_check_area_size(area_item * p_area) { + if (p_area->size > BANK_SIZE_ROM) { + printf("BankPack: ERROR! Area %s, bank %d, size %d is too large for bank size %d (file %s)\n", + p_area->name, p_area->bank_num_in, p_area->size, BANK_SIZE_ROM, file_get_name_in_by_id(p_area->file_id)); + exit(EXIT_FAILURE); + } +} + + +// Display a error message about bank mixing +static void bank_report_mixed_area_error(bank_item * p_bank, uint16_t bank_num, area_item * p_area) { + + printf("BankPack: ERROR! Bank %d already assigned different area type.\n" + " Can't mix _CODE_ and _LIT_ areas in the same bank for this platform.\n" + " Area %s, bank %d, file:%s\n", + p_area->bank_num_in, p_area->name, bank_num, file_get_name_in_by_id(p_area->file_id)); + + if (g_option_verbose) + banks_show(); +} + + +// Checks whether mixing area types in the same bankshould be rejected +static bool bank_check_mixed_area_types_ok(bank_item * p_bank, uint16_t bank_num, area_item * p_area) { + + // Don't allow mixing of _CODE_ and _LIT_ for sms/gg ports + // If one type has already been assigned to the bank, lock others out + if ((g_platform == PLATFORM_SMS) && + (p_bank->type != BANK_TYPE_UNSET) && + (p_area->type != p_bank->type)) + return false; + else + return true; +} + + +// Verify that a bank is available for an area +// Checks: Size, MBC Availability, Mixed area restrictions +static bool bank_check_ok_for_area(uint16_t bank_num, area_item * p_area, bank_item * banks) { + + // Check for MBC bank restrictions + if ((g_mbc_type != MBC_TYPE_MBC1) || (bank_check_mbc1_ok(bank_num))) { + // Check for allowed area mixing if needed + if (bank_check_mixed_area_types_ok(&banks[bank_num], bank_num, p_area)) { + // Make sure there is enough space for the area + if (p_area->size <= banks[bank_num].free) { + return true; + } + } + } + + return false; +} + + +// Assign areas randomly and distributed as sparsely as possible +static bool banks_assign_area_random(area_item * p_area, bank_item * banks) { + + uint16_t bank_num; + uint16_t item_count_min = BANK_ITEM_COUNT_MAX; + uint16_t list_of_banks[BANK_ROM_TOTAL]; + uint16_t list_count = 0; + + // Find lowest number of areas per bank among banks which have sufficient space for the area + for (bank_num = bank_limit_rom_min; bank_num <= bank_limit_rom_max; bank_num++) + if ((banks[bank_num].item_count < item_count_min) && bank_check_ok_for_area(bank_num, p_area, banks)) + item_count_min = banks[bank_num].item_count; + + // Now make a list of suitable banks below that threshold + for (bank_num = bank_limit_rom_min; bank_num <= bank_limit_rom_max; bank_num++) + if ((banks[bank_num].item_count <= item_count_min) && bank_check_ok_for_area(bank_num, p_area, banks)) + list_of_banks[list_count++] = bank_num; + + if (list_count > 0) { + // Choose a random bank from the selected banks and add the area to it + bank_num = list_of_banks[ rand() % list_count ]; + bank_add_area(&banks[bank_num], bank_num, p_area); + + return true; + } else + return false; // Fail if no banks were available +} + + +// Assign areas linearly, trying to fill up lowest banks first +static bool banks_assign_area_linear(area_item * p_area, bank_item * banks) { + + uint16_t bank_num; + + // Try to assign area to first allowed bank with enough free space + // Bank array index maps directly to bank numbers, so [2] will be _CODE_2 + for (bank_num = bank_limit_rom_min; bank_num <= bank_limit_rom_max; bank_num++) { + if (bank_check_ok_for_area(bank_num, p_area, banks)) { + bank_add_area(&banks[bank_num], bank_num, p_area); + return true; + } + } + return false; // Fail if no banks were available +} + + +// Find a bank for a given area using First Fit Decreasing (FFD) +// All possible banks (0-255) were pre-created and initialized [in obj_data_init()], +// so there is no need to add when using a fresh bank +static void banks_assign_area(area_item * p_area) { + + uint16_t bank_num; + bank_item * banks = (bank_item *)banklist.p_array; + bool result; + + bank_check_area_size(p_area); + + // Try to assign fixed bank areas to their expected bank. + // (ignore the area if it's outside the processing range) + if (p_area->bank_num_in != BANK_NUM_AUTO) { + + bank_num = p_area->bank_num_in; + + // Update max bank var that tracks regardless of limits + // For auto banks this will get updated via bank_add_area() + bank_update_all_max(bank_num); + + if ((bank_num >= bank_limit_rom_min) && + (bank_num <= bank_limit_rom_max)) { + + if ((g_mbc_type == MBC_TYPE_MBC1) && (!bank_check_mbc1_ok(bank_num))) + printf("BankPack: Warning: Area in fixed bank assigned to MBC1 excluded bank: %d, file: %s\n", bank_num, file_get_name_in_by_id(p_area->file_id)); + + if (!bank_check_mixed_area_types_ok(&banks[bank_num], bank_num, p_area)) { + bank_report_mixed_area_error(&banks[bank_num], bank_num, p_area); + exit(EXIT_FAILURE); + } + + bank_add_area(&banks[bank_num], bank_num, p_area); + } + //else + // printf("BankPack: Notice: Ignoring Area in fixed bank %d outside specified range %d - %d, file: %s\n", bank_num, bank_limit_rom_min, bank_limit_rom_max, file_get_name_in_by_id(p_area->file_id)); + + return; + } + else if (p_area->bank_num_in == BANK_NUM_AUTO) { + + if (g_opt_random_assign) + result = banks_assign_area_random(p_area, banks); + else + result = banks_assign_area_linear(p_area, banks); + + if (result) // Success + return; + } + + if (g_option_verbose) + banks_show(); + printf("BankPack: ERROR! Failed to assign bank for Area %s, bank %d, size %d. Out of banks!\n", + p_area->name, p_area->bank_num_in, p_area->size); + exit(EXIT_FAILURE); +} + + +#define QSORT_A_FIRST -1 +#define QSORT_A_SAME 0 +#define QSORT_A_AFTER 1 + +// qsort compare rule function for sorting areas +static int area_item_compare(const void* a, const void* b) { + + // sort by bank [asc] (fixed vs auto-bank), then by size [desc] + if (((area_item *)a)->bank_num_in != ((area_item *)b)->bank_num_in) + return (((area_item *)a)->bank_num_in < ((area_item *)b)->bank_num_in) ? QSORT_A_FIRST : QSORT_A_AFTER; + else if (((area_item *)a)->size != ((area_item *)b)->size) + return (((area_item *)a)->size > ((area_item *)b)->size) ? QSORT_A_FIRST : QSORT_A_AFTER; + else + return QSORT_A_SAME; +} + +static void areas_sort(void) { + // Sort banks by name + qsort (arealist.p_array, arealist.count, sizeof(area_item), area_item_compare); +} + + +// Assigns all areas -> banks, and bank numbers -> files +// Fixed bank areas are placed first, then auto-banks fill the rest in +// Only call after all areas have been collected from object files +void obj_data_process(list_type * p_filelist) { + uint32_t c, s; + area_item * areas = (area_item *)arealist.p_array; + symbol_item * symbols = (symbol_item *)symbollist.p_array; + file_item * files = (file_item *)(p_filelist->p_array); + + areas_sort(); + + // Assign areas to banks + for (c = 0; c < arealist.count; c++) { + banks_assign_area(&(areas[c])); + + // If areas was auto-banked then set bank number in associated file + if ((areas[c].bank_num_in == BANK_NUM_AUTO) && + (areas[c].bank_num_out != BANK_NUM_UNASSIGNED)) { + + if (files[ areas[c].file_id ].bank_num != BANK_NUM_UNASSIGNED) { + printf("BankPack: ERROR! Can't assign bank number to a file more than once! Already %d, new is %d\n", + files[ areas[c].file_id ].bank_num, areas[c].bank_num_out); + exit(EXIT_FAILURE); + } + + files[ areas[c].file_id ].bank_num = areas[c].bank_num_out; + files[ areas[c].file_id ].rewrite_needed = true; + } + } + + // Check all symbols for matches to banked entries, flag if match found + // TODO: ineffecient to loop over symbols for all files + for (c = 0; c < symbollist.count; c++) { + if (symbols[c].is_banked_def) { + for (s = 0; s < symbollist.count; s++) { + if (symbols[c].file_id == symbols[s].file_id) { + // offset +1 bast the "b" char at start of banekd symbol entry name + if (strcmp(symbols[c].name + 1, symbols[s].name) == 0) { + symbols[c].found_matching_symbol = true; + break; + } else if (s == symbollist.count -1) + printf(" -> NO MATCH FOUND%s\n", symbols[c].name); + } + } + } + } + +} + + +// Display file/area/bank assignment +// Should be called after obj_data_process() +void banks_show(void) { + + uint32_t c; + uint32_t a; + + printf("\n=== Banks assigned: %d -> %d (allowed range %d -> %d). Max including fixed: %d) ===\n", + bank_assign_rom_min, bank_assign_rom_max, + bank_limit_rom_min, bank_limit_rom_max, + bank_all_rom_max); + + bank_item * banks = (bank_item *)banklist.p_array; + area_item * areas = (area_item *)arealist.p_array; + for (c = 0; c < banklist.count; c++) { + if (banks[c].free != BANK_SIZE_ROM) { + printf("Bank %d: size=%5d, free=%5d\n", c, banks[c].size, banks[c].free); + for (a = 0; a < arealist.count; a++) { + if (areas[a].bank_num_out == c) { + printf(" +- Area: name=%8s, size=%5d, bank_in=%3d, bank_out=%3d, file=%s -> %s\n", + areas[a].name, + areas[a].size, + areas[a].bank_num_in, + areas[a].bank_num_out, + file_get_name_in_by_id(areas[a].file_id), + file_get_name_out_by_id(areas[a].file_id)); + } + } + } + } + + printf("\n"); +} + + +// Accepts an input string line and writes it +// out with an **updated bank num** to a file +// * Adds trailing \n if missing +bool area_modify_and_write_to_file(char * strline_in, FILE * out_file, uint16_t bank_num) { + + // Only rewrite area bank number for unset banked CODE + if (strline_in[0] == 'A') { + // For lines: A _CODE_255 ... + if (strstr(strline_in, "A _CODE_255")) { + fprintf(out_file, "A _CODE_%d %s", bank_num, strstr(strline_in, "size")); + // Add trailing \n if missing (may be, due to using strtok() to split the string) + if (strline_in[(strlen(strline_in)-1)] != '\n') + fprintf(out_file, "\n"); + return true; + } + else if (strstr(strline_in, "A _LIT_255")) { + fprintf(out_file, "A _LIT_%d %s", bank_num, strstr(strline_in, "size")); + // Add trailing \n if missing (may be, due to using strtok() to split the string) + if (strline_in[(strlen(strline_in)-1)] != '\n') + fprintf(out_file, "\n"); + return true; + } + } + return false; +} + + +// Check to make sure a banked symbol has a matching symbol in the same file +// This prevents mistakenly rewriting symbol b_<something> entries from asm files +bool symbol_banked_check_rewrite_ok(char * symbol_name, uint32_t file_id) { + + uint32_t c; + symbol_item * symbols = (symbol_item *)symbollist.p_array; + + for (c = 0; c < symbollist.count; c++) { + if ((symbols[c].file_id == file_id) && + (symbols[c].is_banked_def) && + (symbols[c].found_matching_symbol)) { + // Make sure b_<name> matches + if (strcmp(symbols[c].name + 2, symbol_name) == 0) + return true; + } + } + return false; +} + + +// Accepts an input string line +// and writes it out with an updated bank to a file +// * Adds trailing \n if missing +bool symbol_modify_and_write_to_file(char * strline_in, FILE * out_file, uint16_t bank_num, uint32_t file_id) { + + uint32_t c; + uint32_t bank_in = 0; + char strmatch[OBJ_NAME_MAX_STR_LEN]; + char symbol_name[OBJ_NAME_MAX_STR_LEN]; + symbol_match_item * sym_match = (symbol_match_item *)symbol_matchlist.p_array; + + // Only rewrite banked symbol entries + if (strline_in[0] == 'S') { + // For lines: S b_<symbol name>... Def0000FF + if (SYMBOL_REWRITE_RECORDS == sscanf(strline_in,"S b_%" TOSTR(OBJ_NAME_MAX_STR_LEN) "s Def%06x", symbol_name, &bank_in)) { + if (bank_in == BANK_NUM_AUTO) { + if (symbol_banked_check_rewrite_ok(symbol_name, file_id)) { + fprintf(out_file, "S b_%s Def0000%02x", symbol_name, bank_num); + if (strline_in[(strlen(strline_in)-1)] != '\n') + fprintf(out_file, "\n"); + return true; + } + } + } // For lines: S ___bank_<trailing symbol name>... Def0000FF + else { + for (c = 0; c < symbol_matchlist.count; c++) { + // Prepare a sscanf match test string for the current symbol name + if (snprintf(strmatch, sizeof(strmatch), "S %s%%" TOSTR(OBJ_NAME_MAX_STR_LEN) "s Def%%06x", sym_match[c].name) > sizeof(strmatch)) + printf("BankPack: Warning: truncated symbol match string to:%s\n",strmatch); + + if (SYMBOL_REWRITE_RECORDS == sscanf(strline_in, strmatch, symbol_name, &bank_in)) { + if (bank_in == BANK_NUM_AUTO) { + fprintf(out_file, "S %s%s Def0000%02x", sym_match[c].name, symbol_name, bank_num); + if (strline_in[(strlen(strline_in)-1)] != '\n') + fprintf(out_file, "\n"); + return true; + } + } + } + } + } + return false; +} diff --git a/gbdk/gbdk-support/bankpack/obj_data.h b/gbdk/gbdk-support/bankpack/obj_data.h new file mode 100644 index 00000000..0ec1828f --- /dev/null +++ b/gbdk/gbdk-support/bankpack/obj_data.h @@ -0,0 +1,88 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _AREAS_H +#define _AREAS_H + +#include "list.h" + +#define OBJ_NAME_MAX_STR_LEN 255 +#define AREA_LINE_RECORDS 2 // Bank number, Size +#define SYMBOL_LINE_RECORDS 2 // Name, DefVal +#define SYMBOL_REWRITE_RECORDS 2 // Name, DefVal + +#define PLATFORM_GB 0 +#define PLATFORM_SMS 1 +#define PLATFORM_DEFAULT PLATFORM_GB + +#define PLATFORM_STR_GB "gb" +#define PLATFORM_STR_AP "ap" // Uses PLATFORM_GB +#define PLATFORM_STR_DUCK "duck" // Uses PLATFORM_GB +#define PLATFORM_STR_SMS "sms" +#define PLATFORM_STR_GG "gg" // Uses PLATFORM_SMS +#define PLATFORM_STR_MSXDOS "msxdos" // Uses PLATFORM_SMS + +#define BANK_TYPE_UNSET 0 +#define BANK_TYPE_DEFAULT 1 +#define BANK_TYPE_LIT_EXCLUSIVE 2 + + +typedef struct bank_item { + uint32_t size; + uint32_t free; + uint32_t type; + uint16_t item_count; +} bank_item; + + +typedef struct area_item { + uint32_t file_id; + char name[OBJ_NAME_MAX_STR_LEN]; + uint32_t size; // uint32_t to avoid mingw sscanf() buffer overflow + uint32_t bank_num_in; // uint32_t to avoid mingw sscanf() buffer overflow + uint32_t bank_num_out; // uint32_t to avoid mingw sscanf() buffer overflow + uint32_t type; +} area_item; + +typedef struct symbol_item { + uint32_t file_id; + char name[OBJ_NAME_MAX_STR_LEN]; + bool is_banked_def; + uint32_t bank_num_in; // uint32_t to avoid mingw sscanf() buffer overflow + bool found_matching_symbol; +} symbol_item; + +typedef struct symbol_match_item { + char name[OBJ_NAME_MAX_STR_LEN]; +} symbol_match_item; + +void banks_set_platform(char * platform_str); +int banks_get_platform(void); +int banks_get_mbc_type(void); +void banks_set_mbc(int); +void banks_set_mbc_by_rom_byte_149(int); + +uint32_t banks_calc_cart_size(void); + +bool banks_set_min(uint16_t bank_num); +bool banks_set_max(uint16_t bank_num); + +void banks_set_random(bool is_random); + +void obj_data_init(void); +void obj_data_cleanup(void); + +int areas_add(char * area_str, uint32_t file_id); +int symbols_add(char * area_str, uint32_t file_id); +void symbol_match_add(char *); + +void obj_data_process(list_type *); + +bool area_modify_and_write_to_file(char * strline_in, FILE * out_file, uint16_t bank_num); +bool symbol_modify_and_write_to_file(char * strline_in, FILE * out_file, uint16_t bank_num, uint32_t file_id); + +void banks_show(void); + + +#endif // _AREAS_H diff --git a/gbdk/gbdk-support/bankpack/path_ops.c b/gbdk/gbdk-support/bankpack/path_ops.c new file mode 100644 index 00000000..58c064cd --- /dev/null +++ b/gbdk/gbdk-support/bankpack/path_ops.c @@ -0,0 +1,103 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include "common.h" + +const char kExtensionSeparator = '.'; +const char kPathSeparator = + +#ifdef _WIN32 + #ifndef _WIN32 + #define __WIN32__ + #endif +#endif + +#ifdef __WIN32__ + '\\'; +#else + '/'; +#endif + + +static const char * get_filename_from_path(const char * path); +static void filename_remove_extension(char * path); + + +void filename_replace_extension(char * filename, char * new_ext, size_t maxlen) { + + // Use a temp work string in case out and in filename are the same pointer + char temp[MAX_FILE_STR]; + char ext_sep[2] = {'\0'}; // default to empty string + + // Add leading . to path if needed + if (new_ext[0] != kExtensionSeparator) { + ext_sep[0] = kExtensionSeparator; + ext_sep[1] = '\0'; + } + + // Strip extension from filename, append new extension + filename_remove_extension(filename); + snprintf(temp, maxlen, "%s%s%s", filename, ext_sep, new_ext); + snprintf(filename, maxlen, "%s", temp); +} + + +void filename_replace_path(char * filename, char * new_path, size_t maxlen) { + + // Use a temp work string in case out and in filename are the same pointer + char temp[MAX_FILE_STR]; + char path_sep[2] = {'\0'}; // default to empty string + + // Add trailing slash to path if needed + if ((new_path[(strlen(new_path)-1)] != kPathSeparator)) { + path_sep[0] = kPathSeparator; + path_sep[1] = '\0'; + } + + // Strip path from path+filename, pre-pend new path + snprintf(temp, maxlen, "%s%s%s", new_path, path_sep, get_filename_from_path(filename)); + snprintf(filename, maxlen, "%s", temp); +} + + +static const char * get_filename_from_path(const char * path) +{ + size_t i; + + // Returns string starting at last occurrance of path separator char + for(i = strlen(path) - 1; i; i--) { + if (path[i] == kPathSeparator) { + return &path[i+1]; + } + } + return path; +} + + +static void filename_remove_extension(char * path) +{ + char * last_ext; + char * last_slash; + + // Find the last path separator if present + // Starting from here ensures that no path ".." characters + // get mistaken as extension delimiters. + last_slash = strrchr (path, kExtensionSeparator); + if (!last_slash) + last_slash = path; + + // Then check to see if there is an extension (starting with the last occurance of '.') + // (tries to remove *all* trailing extensions backward until the last slash) + last_ext = strrchr (last_slash, kExtensionSeparator); + while (last_ext) { + if (last_ext != NULL) { + // If an extension is found then overwrite it with a string terminator + *last_ext = '\0'; + } + last_ext = strrchr (last_slash, kExtensionSeparator); + } +} + diff --git a/gbdk/gbdk-support/bankpack/path_ops.h b/gbdk/gbdk-support/bankpack/path_ops.h new file mode 100644 index 00000000..3d4c1ae5 --- /dev/null +++ b/gbdk/gbdk-support/bankpack/path_ops.h @@ -0,0 +1,11 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _PATH_OPS_H +#define _PATH_OPS_H + +void filename_replace_extension(char * filename, char * new_ext, size_t maxlen); +void filename_replace_path(char * filename, char * new_path, size_t maxlen); + +#endif // _PATH_OPS_H
\ No newline at end of file diff --git a/gbdk/gbdk-support/gbcompress/LICENSE b/gbdk/gbdk-support/gbcompress/LICENSE new file mode 100644 index 00000000..fdddb29a --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <https://unlicense.org> diff --git a/gbdk/gbdk-support/gbcompress/Makefile b/gbdk/gbdk-support/gbcompress/Makefile new file mode 100644 index 00000000..b87f0504 --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/Makefile @@ -0,0 +1,43 @@ +# gbcompress makefile + +ifndef TARGETDIR +TARGETDIR = /opt/gbdk +endif + +ifeq ($(OS),Windows_NT) + BUILD_OS := Windows_NT +else + BUILD_OS := $(shell uname -s) +endif + +# Target older macOS version than whatever build OS is for better compatibility +ifeq ($(BUILD_OS),Darwin) + export MACOSX_DEPLOYMENT_TARGET=10.10 +endif + +CC = $(TOOLSPREFIX)gcc +CFLAGS = -ggdb -O -Wno-incompatible-pointer-types -DGBDKLIBDIR=\"$(TARGETDIR)\" +OBJ = main.o gbcompress.o rlecompress.o files.o files_c_source.o +BIN = gbcompress + +all: $(BIN) + +$(BIN): $(OBJ) + +clean: + rm -f *.o $(BIN) *~ + rm -f tmp.* + rm -f *.exe + +# round trip the executable through compression and de-compression as a brief test +test: + rm -f tmp.cmp; rm -f tmp.dcmp + cp $(BIN) tmp.in; ./gbcompress -v tmp.in tmp.cmp; ./gbcompress -v -d tmp.cmp tmp.dcmp; diff -s tmp.in tmp.dcmp + rm -f tmp.cmp.c; rm -f tmp.dcmp.c; rm -f tmp.cmp; rm -f tmp.dcmp + cp $(BIN) tmp.in; ./gbcompress -v --cout --varname=some_array tmp.in tmp.cmp.c; ./gbcompress -v -d --cin tmp.cmp.c tmp.dcmp; diff -s tmp.in tmp.dcmp + rm tmp.* + rm -f tmp.cmp; rm -f tmp.dcmp + cp $(BIN) tmp.in; ./gbcompress --alg=rle -v tmp.in tmp.cmp; ./gbcompress --alg=rle -v -d tmp.cmp tmp.dcmp; diff -s tmp.in tmp.dcmp + rm -f tmp.cmp.c; rm -f tmp.dcmp.c; rm -f tmp.cmp; rm -f tmp.dcmp + cp $(BIN) tmp.in; ./gbcompress --alg=rle -v --cout --varname=some_array tmp.in tmp.cmp.c; ./gbcompress --alg=rle -v -d --cin tmp.cmp.c tmp.dcmp; diff -s tmp.in tmp.dcmp + rm tmp.* diff --git a/gbdk/gbdk-support/gbcompress/files.c b/gbdk/gbdk-support/gbcompress/files.c new file mode 100644 index 00000000..c8bda1c8 --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/files.c @@ -0,0 +1,115 @@ +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + + + +// Read from a file into a buffer (will allocate needed memory) +// Returns NULL if reading file didn't succeed +uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size) { + + long fsize; + FILE * file_in = fopen(filename, "rb"); + uint8_t * filedata = NULL; + + if (file_in) { + // Get file size + fseek(file_in, 0, SEEK_END); + fsize = ftell(file_in); + if (fsize != -1L) { + fseek(file_in, 0, SEEK_SET); + + filedata = malloc(fsize); + if (filedata) { + if (fsize != fread(filedata, 1, fsize, file_in)) { + printf("gbcompress: Warning: File read size didn't match expected for %s\n", filename); + filedata = NULL; + } + // Read was successful, set return size + *ret_size = fsize; + } else printf("gbcompress: ERROR: Failed to allocate memory to read file %s\n", filename); + + } else printf("gbcompress: ERROR: Failed to read size of file %s\n", filename); + + fclose(file_in); + } else printf("gbcompress: ERROR: Failed to open input file %s\n", filename); + + return filedata; +} + + + +// Writes a buffer to a file +bool file_write_from_buffer(char * filename, uint8_t * p_buf, uint32_t data_len) { + + bool status = false; + size_t wrote_bytes; + FILE * file_out = fopen(filename, "wb"); + + if (file_out) { + if (data_len == fwrite(p_buf, 1, data_len, file_out)) + status = true; + else + printf("gbcompress: Warning: File write size didn't match expected for %s\n", filename); + + fclose(file_out); + } + return status; +} + + + + +// Read from a file into a buffer (will allocate needed memory) +// Returns NULL if reading file didn't succeed +char * file_read_into_buffer_char(char * filename, uint32_t *ret_size) { + + long fsize; + FILE * file_in = fopen(filename, "r"); + char * filedata = NULL; + + if (file_in) { + // Get file size + fseek(file_in, 0, SEEK_END); + fsize = ftell(file_in); + if (fsize != -1L) { + fseek(file_in, 0, SEEK_SET); + + filedata = malloc(fsize); + if (filedata) { + if (fsize != fread(filedata, 1, fsize, file_in)) { + printf("gbcompress: Warning: File read size didn't match expected for %s\n", filename); + filedata = NULL; + } + // Read was successful, set return size + *ret_size = fsize; + } else printf("gbcompress: ERROR: Failed to allocate memory to read file %s\n", filename); + + } else printf("gbcompress: ERROR: Failed to read size of file %s\n", filename); + + fclose(file_in); + } else printf("gbcompress: ERROR: Failed to open input file %s\n", filename); + + return filedata; +} + + + +// Writes a buffer to a file +bool file_write_from_buffer_char(char * filename, char * p_buf, uint32_t data_len) { + + bool status = false; + size_t wrote_bytes; + FILE * file_out = fopen(filename, "w"); + + if (file_out) { + if (data_len == fwrite(p_buf, 1, data_len, file_out)) + status = true; + else + printf("gbcompress: Warning: File write size didn't match expected for %s\n", filename); + + fclose(file_out); + } + return status; +} diff --git a/gbdk/gbdk-support/gbcompress/files.h b/gbdk/gbdk-support/gbcompress/files.h new file mode 100644 index 00000000..ca0fe3b0 --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/files.h @@ -0,0 +1,10 @@ +#ifndef _FILES_H +#define _FILES_H + +uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size); +bool file_write_from_buffer(char * filename, uint8_t * p_buf, uint32_t data_len); + +char * file_read_into_buffer_char(char * filename, uint32_t *ret_size); +bool file_write_from_buffer_char(char * filename, char * p_buf, uint32_t data_len); + +#endif // _FILES_H diff --git a/gbdk/gbdk-support/gbcompress/files_c_source.c b/gbdk/gbdk-support/gbcompress/files_c_source.c new file mode 100644 index 00000000..deffe1ca --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/files_c_source.c @@ -0,0 +1,228 @@ + +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +#include "files.h" + +#define STR_FFWD_MATCH_ANY NULL + +#define BUF_DEFAULT_SIZE 20000 +#define BUF_GROW_SIZE 10000 + + +static uint32_t size_compressed = 0; +static uint32_t size_decompressed = 0; + + +void c_source_set_sizes(uint32_t size_compressed_in, uint32_t size_decompressed_in) { + size_compressed = size_compressed_in; + size_decompressed = size_decompressed_in; +} + + +// Search for a character in a string +// +// Terminate search if: +// * any non-allowed character was found (str_chars_allowed, NULL for allow any chars) +// * end of string is reached (by terminator or length) +// +static char * str_ffwd_to(char * str_in, uint32_t * max_len, char char_match, char * str_chars_allowed) { + + char str_cur[2] = " \0"; // current character to test, for strpbrk + + while ((*max_len) && (*str_in != '\0')) { + + str_cur[0] = *str_in; + if (*str_in == char_match) + return str_in; + else if (str_chars_allowed) { + if (strpbrk(str_cur, str_chars_allowed) == NULL) { + // signal failure if any non-allowed character was found + return NULL; + } + } + + str_in++; + (*max_len)--; + } + + return NULL; +} + + + +// Convert an array of comma delimtied numbers into a buffer +// +// If conversion fails: returns NULL, *p_ret_size == 0 +// +static uint8_t * str_array_to_buf(char * str_in, uint32_t * p_ret_len) { + + uint32_t buf_size = BUF_DEFAULT_SIZE; + uint8_t * p_buf = malloc(buf_size); + uint8_t * p_buf_last; + char * end_ptr; + + // Read value at a time from a string buffer + // (strtok will replace the , chars with \0 on each split) + char * str_cur = strtok(str_in,","); + + if (p_buf) { + + // Start with size zero + *p_ret_len = 0; + + // Loop as long as strtok returns comma separated items + while (str_cur != NULL) { + + // Grow buffer if needed + if ((*p_ret_len) >= buf_size) { + + buf_size += BUF_GROW_SIZE; + p_buf_last = p_buf; + p_buf = (uint8_t *)realloc(p_buf, buf_size); + + // Exit if realloc failed + if (p_buf == NULL) { + *p_ret_len = 0; + free(p_buf_last); + return NULL; + } + } + + // Store current value + p_buf[ *p_ret_len ] = (uint8_t)strtol(str_cur, &end_ptr, 0); + + // Only advance data buffer if conversion didn't fail + if (str_cur != end_ptr) + (*p_ret_len)++; + + // Read next value (strtok maintains state of last call) + str_cur = strtok(NULL,","); + } + } + + return p_buf; +} + + + +// Read from a file into a buffer, find first C source formatted +// array and parse it into another buffer (will allocate needed memory) +// +// Returns NULL if reading file didn't succeed +// +uint8_t * file_read_c_input_into_buffer(char * filename, uint32_t *ret_size) { + + char * filedata = NULL; + uint32_t file_size; + + char * str_c_array = NULL; + char * str_c_array_start = NULL; + uint8_t * p_array_data = NULL; + + filedata = file_read_into_buffer_char(filename, &file_size); + + if (filedata) { + str_c_array = filedata; + + // Find array opening bracket `[` + str_c_array = str_ffwd_to(str_c_array, &file_size, '[', STR_FFWD_MATCH_ANY); + if (str_c_array) { + // Find Array closing bracket `]` + str_c_array = str_ffwd_to(str_c_array, &file_size, ']', "[xX0123456789ABCDEFabcdef\t\n\r "); + if (str_c_array) { + // Find Start or array + str_c_array = str_ffwd_to(str_c_array, &file_size, '{', "]\t\n\r ="); + if (str_c_array) { + // Save start of array + str_c_array_start = str_c_array + 1; + + // Find end of array + str_c_array = str_ffwd_to(str_c_array, &file_size, '}', "{xX0123456789ABCDEFabcdef,\t\n\r "); + if (str_c_array) { + // Terminate string at end of array + *str_c_array = '\0'; + + // If conversion fails: p_array_data == NULL, ret_size == 0 + p_array_data = str_array_to_buf(str_c_array_start, ret_size); + + // printf("C array length in = %d", *ret_size); + } + } + } + } + + // Free file data if allocated + free(filedata); + filedata = NULL; + } + + return p_array_data; +} + + + +// Writes a buffer to a file in C source format +// Adds a matching .h if possible +// +bool file_write_c_output_from_buffer(char * filename, uint8_t * p_buf, uint32_t data_len, char * var_name, bool var_is_const) { + + bool status = false; + size_t wrote_bytes; + FILE * file_out = fopen(filename, "w"); + int i; + + if (file_out) { + + // Array entry with variable name + fprintf(file_out, "\n\n%s unsigned char %s[] = {", (var_is_const) ? "const" : "", var_name); + + for (i = 0; i < data_len; i++) { + + // Line break every 16 bytes (includes at start. but exclude if on last entry) + if ((i != data_len -1 ) && ((i % 16) == 0)) + fprintf(file_out, "\n "); + + // Write current byte + fprintf(file_out, "0x%.2X", p_buf[i]); + + // Add comma after every entry except last one + if (i != data_len -1) + fprintf(file_out, ", "); + } + fprintf(file_out, "\n};\n\n"); + status = true; + + fclose(file_out); + + + // Create matching .h header file output, if file ends in .c or .C + if (strlen(filename) >= 2) { + if ((strcmp(&filename[strlen(filename) - 2],".c") == 0) || + (strcmp(&filename[strlen(filename) - 2],".C") == 0)) { + + // Replace file extension + filename[strlen(filename) - 2] = '.'; + filename[strlen(filename) - 1] = 'h'; + + FILE * file_out = fopen(filename, "w"); + + if (file_out) { + + fprintf(file_out, "\n\n#define %s_sz_comp %d\n", var_name, size_compressed); + fprintf(file_out, "#define %s_sz_decomp %d\n", var_name, size_decompressed); + + // array entry with variable name + fprintf(file_out, "\n\nextern %s unsigned char %s[];\n\n", (var_is_const) ? "const" : "", var_name); + + fclose(file_out); + } + } + } + } + + return status; +} diff --git a/gbdk/gbdk-support/gbcompress/files_c_source.h b/gbdk/gbdk-support/gbcompress/files_c_source.h new file mode 100644 index 00000000..1e3522a3 --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/files_c_source.h @@ -0,0 +1,9 @@ +#ifndef _FILES_C_SOURCE_H +#define _FILES_C_SOURCE_H + +void c_source_set_sizes(uint32_t, uint32_t); + +bool file_write_c_output_from_buffer(char *, uint8_t *, uint32_t, char *, bool); +uint8_t * file_read_c_input_into_buffer(char * filename, uint32_t *ret_size); + +#endif // _FILES_C_SOURCE_H diff --git a/gbdk/gbdk-support/gbcompress/gbcompress.c b/gbdk/gbdk-support/gbcompress/gbcompress.c new file mode 100644 index 00000000..6e493e5e --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/gbcompress.c @@ -0,0 +1,382 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +//#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> +#include "gbcompress.h" + +const uint8_t len_mask = 0x3F; +const uint8_t token_mask = 0xC0; + +#define token_byte 0x00 +#define token_word 0x40 +#define token_str 0x80 +#define token_trash 0xC0 + +const uint8_t EOFMarker = 0x00; + +uint8_t * FinBuf = NULL; +uint32_t Fsize_in = 0; +uint32_t FinIndex = 0; + +uint8_t ** pp_FoutBuf = NULL; +uint8_t * FoutBuf = NULL; +uint32_t Fsize_out = 0; +uint32_t FoutIndex = 0; + + +static void check_write_size(uint8_t len) { + + // Grow output buffer if needed + if ((FoutIndex + len) >= Fsize_out) { + uint8_t * p_tmp = *pp_FoutBuf; + + // Reallocate to twice as large + Fsize_out = Fsize_out * 2; + *pp_FoutBuf = (void *)realloc(*pp_FoutBuf, Fsize_out); + + // If realloc failed, free original buffer before quitting + if (!(*pp_FoutBuf)) { + printf("Error: Failed to grow memory for output buffer!\n"); + if (p_tmp) free(p_tmp); + p_tmp = NULL; + exit(EXIT_FAILURE); + } else + FoutBuf = *pp_FoutBuf; // Update working pointer + } +} + + +static void write_byte(uint8_t len, uint8_t data) { + + check_write_size(2); // writing 2 bytes + + FoutBuf[FoutIndex++] = ((len - 1) & len_mask); + FoutBuf[FoutIndex++] = data; +} + + +static void write_word( uint8_t len, uint16_t data ) { + + check_write_size(3); // writing 3 bytes + + FoutBuf[FoutIndex++] = (((len - 1) & len_mask) | token_word); + FoutBuf[FoutIndex++] = (uint8_t)((data >> 8) & 0xFF); + FoutBuf[FoutIndex++] = (uint8_t)(data & 0xFF); +} + + +static void write_string( uint8_t len, uint16_t data) { + + check_write_size(3); // writing 3 bytes + + // conver's complement does not give the negation, see § Most negative number below. t back-ref offset from positive unsigned to negative signed + data = (data ^ 0xFFFF) + 1; + + FoutBuf[FoutIndex++] = (((len - 1) & len_mask) | token_str); + FoutBuf[FoutIndex++] = (uint8_t)(data & 0xFF); + FoutBuf[FoutIndex++] = (uint8_t)((data >> 8) & 0xFF); +} + + +static void write_trash( uint8_t len, uint8_t * pos) { + + uint8_t i; + + check_write_size(len); // writing len bytes + + FoutBuf[FoutIndex++] = (((len-1) & len_mask) | token_trash); + for (i=0; i < len; i++) + FoutBuf[FoutIndex++] = pos[i]; +} + + +static void write_end(void) { + + check_write_size(1); // writing 1 byte + + FoutBuf[FoutIndex++] = EOFMarker; +} + + +static int read_uint16_t(uint32_t byte_pos, uint16_t * out_data) { + + if ((byte_pos + 2) < Fsize_in) { + *out_data = (uint16_t)((FinBuf[byte_pos] << 8) + (uint16_t)FinBuf[byte_pos+1]); + return true; + } + else return false; +} + + +// Writes out any pending "trash" (non-rle sequence) and resets the counter +static void flush_trash(uint32_t * byte_pos, uint32_t * trash_len) { + + if (*trash_len > 0) { + write_trash(*trash_len, &FinBuf[*byte_pos - *trash_len]); + *trash_len = 0; + } +} + + +// Convert buffer inBuf to gbcompress rle encoding and write out to outBuf +// Returns converted length +uint32_t gbcompress_buf(uint8_t * inBuf, uint32_t size_in, uint8_t ** pp_outBuf, uint32_t size_out) { + + uint8_t rle_u8_match; // x + uint16_t rle_u16_match; // y + + uint32_t rle_u8_len; // r_rb + uint32_t rle_u16_len; // r_rw + uint32_t rle_str_len; // r_rs + uint32_t trash_len; // tb (by "trash" the original author meant, "non-rle sequence of bytes") + + uint32_t rle_str_start; // rr + uint32_t rle_str_back_offset; // sr (this is signed in original code, handled differently to be unsigned now) + uint32_t rle_str_len_work; // rl + + FinBuf = inBuf; + Fsize_in = size_in; + FinIndex = 0; // bp + + pp_FoutBuf = pp_outBuf; + FoutBuf = *pp_outBuf; + Fsize_out = size_out; + FoutIndex = 0; + + trash_len = 0; + + while (FinIndex < Fsize_in) { + + // Check for u8 RLE run up to 63 bytes max + rle_u8_match = FinBuf[FinIndex]; + rle_u8_len = 1; + while ((FinIndex + rle_u8_len) < Fsize_in) { + // If the current u8 matches, increment the length + if ((FinBuf[FinIndex + rle_u8_len] == rle_u8_match) && + (rle_u8_len < 64)) { + rle_u8_len++; + } + else break; + } + + // Check for overlapping uint16_t RLE run up to 63 bytes max + // Read in initial u16 to match against + if (read_uint16_t(FinIndex, &rle_u16_match)) { + uint16_t temp_u16; + rle_u16_len = 1; + // If the current u16 matches, increment the length + while (read_uint16_t(FinIndex + (rle_u16_len * 2), &temp_u16)) { + if ((temp_u16 == rle_u16_match) && + (rle_u16_len < 64)) { + rle_u16_len++; + } + else break; + } + } + + // Check for matching sequences starting at current position + // against all previous data beginning at start up to 63 bytes max + // (back reference "strings") + rle_str_back_offset = 0; + rle_str_len = 0; + // Max string backreference length is 16 bits unsigned + // adjust search start accordingly + if (FinIndex > 0xFFFF) + rle_str_start = FinIndex - 0xFFFF; + else + rle_str_start = 0; + + while (rle_str_start < FinIndex) { + rle_str_len_work = 0; + + // Check for a matching run at rle_str_start against FinIndex + // and save length to rle_str_len_work + while ((FinIndex + rle_str_len_work) < Fsize_in) { + // Test to see if u8 in current sequence matches the u8 for a previous sequence + // Break out of the current sequence if it would reach 64 bytes + // or it would reach the current byte pos (and cause an overlap) + if ((FinBuf[rle_str_start + rle_str_len_work] == FinBuf[FinIndex + rle_str_len_work]) && + ((rle_str_start + rle_str_len_work) < FinIndex) && + (rle_str_len_work < 64)) { + + rle_str_len_work++; + } + else break; + } + + // If the newly tested sequence length is greater than the previous length + // then save the length and store an negative offset to it in rle_str_back_offset + if (rle_str_len_work > rle_str_len) { + rle_str_back_offset = FinIndex - rle_str_start; // Changed this from neg offset to pos, and gets flipped to negative on writing it out + rle_str_len = rle_str_len_work; + } + + rle_str_start++; + } + + + // Write out any rle data if it's ready + if ((rle_u8_len > 2) && + (rle_u8_len > rle_u16_len) && + (rle_u8_len > rle_str_len)) { + flush_trash(&FinIndex, &trash_len); + write_byte(rle_u8_len, rle_u8_match); + FinIndex = FinIndex + rle_u8_len; + } + else if ((rle_u16_len > 2) && + ((rle_u16_len*2) > rle_str_len)) { + flush_trash(&FinIndex, &trash_len); + write_word(rle_u16_len, rle_u16_match); + FinIndex = FinIndex + rle_u16_len*2; + } + else if (rle_str_len > 3) { + flush_trash(&FinIndex, &trash_len); + write_string(rle_str_len, rle_str_back_offset); + FinIndex = FinIndex + rle_str_len; + } + else if (trash_len >= 64) { + write_trash(trash_len, &FinBuf[FinIndex-trash_len]); + trash_len = 0; + } + else { + trash_len++; + FinIndex++; + } + + } + + // Flush any remaining "trash" bytes + flush_trash(&FinIndex, &trash_len); + + write_end(); + + return FoutIndex; +} + + + +static void write_single_byte(uint8_t data) { + + check_write_size(1); + + FoutBuf[FoutIndex++] = data; +} + + +static uint8_t read_single_byte(void) { + + if (FinIndex >= Fsize_in) { + printf("Error: Read past end of input buffer!\n"); + exit(EXIT_FAILURE); + } + + return (FinBuf[FinIndex++]); +} + + +// Decompress buffer inBuf from gbcompress rle encoding and write to outBuf +// Returns converted length +uint32_t gbdecompress_buf(uint8_t * inBuf, uint32_t size_in, uint8_t ** pp_outBuf, uint32_t size_out) { + + uint8_t rle_val[2]; + uint8_t token; + uint8_t rle_toggle; + uint8_t * inBuf_end = inBuf + size_in - 1; + uint32_t rle_len; + uint32_t backref_index; + + FinBuf = inBuf; + Fsize_in = size_in; + FinIndex = 0; + + pp_FoutBuf = pp_outBuf; + FoutBuf = *pp_outBuf; + Fsize_out = size_out; + FoutIndex = 0; + + + while (FinIndex < size_in) { + + token = read_single_byte(); + + // Check for EOF token, exit if encountered + if (token == EOFMarker) + break; + + // Read a RLE token + // First byte should always be a token + rle_len = (token & len_mask) + 1; + token = token & token_mask; + + // Read how to handle ENCODED RLE Value + switch (token) { + case token_byte: + // Load only one byte + rle_val[0] = read_single_byte(); + break; + + case token_word: + // If token is Word double the RLE decode length + rle_len *= 2; + rle_toggle = 0; // Reset RLE decoded word/byte flipflop + + // Load LS byte then MS Byte + rle_val[0] = read_single_byte(); + rle_val[1] = read_single_byte(); + break; + + case token_str: + // This token: copy N bytes from negative offset of current + // DECODED memory location (back reference) + backref_index = ((uint16_t)read_single_byte() | (( (uint16_t)read_single_byte() ) << 8)); + // Convert input from from Signed to Unsigned + backref_index = (backref_index ^ 0xFFFF) + 1; + + // Assign the string back reference relative to the current buffer pointer + backref_index = (FoutIndex - (uint32_t)backref_index); + break; + + case token_trash: // AKA "Trash Bytes" in GBTD + // This token : copy next N bytes directly from ENCODED input + break; + } + + while (rle_len) { + + // Copy the decoded byte into VRAM + switch (token) { + case token_byte: + // Copy from cached repeating RLE value + write_single_byte(rle_val[0]); + break; + + case token_word: + // Copy from cached repeating RLE value + // Toggle between MS/LS bytes input values + write_single_byte(rle_val[rle_toggle]); + rle_toggle ^= 0x01; + break; + + case token_str: + // Copy byte from the backreferenced VRAM address + // Then increment backreference to next VRAM byte + write_single_byte(FoutBuf[backref_index++]); + break; + + case token_trash: + // Copy directly from encoded input + write_single_byte(read_single_byte()); + break; + } + + rle_len--; // Decrement number of RLE bytes remaining + } + } + + return FoutIndex; +} diff --git a/gbdk/gbdk-support/gbcompress/gbcompress.h b/gbdk/gbdk-support/gbcompress/gbcompress.h new file mode 100644 index 00000000..746b15db --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/gbcompress.h @@ -0,0 +1,11 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _GBCOMPRESS_H +#define _GBCOMPRESS_H + +uint32_t gbcompress_buf(uint8_t * inBuf, uint32_t InSize, uint8_t ** pp_outBuf, uint32_t size_out); +uint32_t gbdecompress_buf(uint8_t * inBuf, uint32_t size_in, uint8_t ** pp_outBuf, uint32_t size_out); + +#endif // _GBCOMPRESS_H diff --git a/gbdk/gbdk-support/gbcompress/main.c b/gbdk/gbdk-support/gbcompress/main.c new file mode 100644 index 00000000..13c82748 --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/main.c @@ -0,0 +1,242 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> + +#include "gbcompress.h" +#include "rlecompress.h" +#include "files.h" +#include "files_c_source.h" + +#define MAX_STR_LEN 4096 + +#define COMPRESSION_TYPE_GB 0 +#define COMPRESSION_TYPE_RLE_BLOCK 1 +#define COMPRESSION_TYPE_DEFAULT COMPRESSION_TYPE_GB + +char filename_in[MAX_STR_LEN] = {'\0'}; +char filename_out[MAX_STR_LEN] = {'\0'}; + +uint8_t * p_buf_in = NULL; +uint8_t * p_buf_out = NULL; + +bool opt_mode_compress = true; +bool opt_verbose = false; +bool opt_compression_type = COMPRESSION_TYPE_DEFAULT; +bool opt_c_source_input = false; +bool opt_c_source_output = false; +char opt_c_source_output_varname[MAX_STR_LEN] = "var_name"; + +static void display_help(void); +static int handle_args(int argc, char * argv[]); +static int compress(void); +static int decompress(void); +void cleanup(void); + + +static void display_help(void) { + fprintf(stdout, + "gbcompress [options] infile outfile\n" + "Use: compress a binary file and write it out.\n" + "\n" + "Options\n" + "-h : Show this help screen\n" + "-d : Decompress (default is compress)\n" + "-v : Verbose output\n" + "--cin : Read input as .c source format (8 bit char ONLY, uses first array found)\n" + "--cout : Write output in .c / .h source format (8 bit char ONLY) \n" + "--varname=<NAME> : specify variable name for c source output\n" + "--alg=<type> : specify compression type: 'rle', 'gb' (default)\n" + "Example: \"gbcompress binaryfile.bin compressed.bin\"\n" + "Example: \"gbcompress -d compressedfile.bin decompressed.bin\"\n" + "Example: \"gbcompress --alg=rle binaryfile.bin compressed.bin\"\n" + "\n" + "The default compression (gb) is the type used by gbtd/gbmb\n" + "The rle compression is Amiga IFF style\n" + ); +} + + +int handle_args(int argc, char * argv[]) { + + int i = 1; // start at first arg + + if( argc < 3 ) { + display_help(); + return false; + } + + // Start at first optional argument + // Last two arguments *must* be input/output files + for (i = 1; i < (argc - 2); i++ ) { + + if (argv[i][0] == '-') { + if (strstr(argv[i], "-h") == argv[i]) { + display_help(); + return false; // Don't parse input when -h is used + } else if (strstr(argv[i], "-v") == argv[i]) { + opt_verbose = true; + } else if (strstr(argv[i], "--cin") == argv[i]) { + opt_c_source_input = true; + } else if (strstr(argv[i], "--cout") == argv[i]) { + opt_c_source_output = true; + } else if (strstr(argv[i], "--varname=") == argv[i]) { + snprintf(opt_c_source_output_varname, sizeof(opt_c_source_output_varname), "%s", argv[i] + 10); + } else if (strstr(argv[i], "--alg=gb") == argv[i]) { + opt_compression_type = COMPRESSION_TYPE_GB; + } else if (strstr(argv[i], "--alg=rle") == argv[i]) { + opt_compression_type = COMPRESSION_TYPE_RLE_BLOCK; + } else if (strstr(argv[i], "-d") == argv[i]) { + opt_mode_compress = false; + } else + printf("gbcompress: Warning: Ignoring unknown option %s\n", argv[i]); + } + } + + // Copy input and output filenames from last two arguments + // if not preceded with option dash + if (argv[i][0] != '-') { + snprintf(filename_in, sizeof(filename_in), "%s", argv[i++]); + + if (argv[i][0] != '-') { + snprintf(filename_out, sizeof(filename_out), "%s", argv[i++]); + return true; + } + } + + + return false; +} + + +void cleanup(void) { + if (p_buf_in != NULL) { + free(p_buf_in); + p_buf_in = NULL; + } + if (p_buf_out != NULL) { + free(p_buf_out); + p_buf_out = NULL; + } +} + + +static int compress() { + + uint32_t buf_size_in = 0; + uint32_t buf_size_out = 0; + uint32_t out_len = 0; + bool result = false; + + if (opt_c_source_input) + p_buf_in = file_read_c_input_into_buffer(filename_in, &buf_size_in); + else + p_buf_in = file_read_into_buffer(filename_in, &buf_size_in); + + // Allocate buffer output buffer same size as input + // It can grow more in gbdecompress_buf() + buf_size_out = buf_size_in; + p_buf_out = malloc(buf_size_out); + + if ((p_buf_in) && (p_buf_out) && (buf_size_in > 0)) { + + if (opt_compression_type == COMPRESSION_TYPE_GB) + out_len = gbcompress_buf(p_buf_in, buf_size_in, &p_buf_out, buf_size_out); + else if (opt_compression_type == COMPRESSION_TYPE_RLE_BLOCK) + out_len = rlecompress_buf(p_buf_in, buf_size_in, &p_buf_out, buf_size_out); + else + return EXIT_FAILURE; + + if (out_len > 0) { + + if (opt_c_source_output) { + c_source_set_sizes(out_len, buf_size_in); // compressed, decompressed + result = file_write_c_output_from_buffer(filename_out, p_buf_out, out_len, opt_c_source_output_varname, true); + } + else + result = file_write_from_buffer(filename_out, p_buf_out, out_len); + + if (result) { + if (opt_verbose) + printf("Compressed: %d bytes -> %d bytes (%%%.2f)\n", buf_size_in, out_len, ((double)out_len / (double)buf_size_in) * 100); + return EXIT_SUCCESS; + } + } + } + + return EXIT_FAILURE; +} + + +static int decompress() { + + uint32_t buf_size_in = 0; + uint32_t buf_size_out = 0; + uint32_t out_len = 0; + bool result = false; + + if (opt_c_source_input) + p_buf_in = file_read_c_input_into_buffer(filename_in, &buf_size_in); + else + p_buf_in = file_read_into_buffer(filename_in, &buf_size_in); + + // Allocate buffer output buffer 3x size of input + // It can grow more in gbdecompress_buf() + buf_size_out = buf_size_in * 3; + p_buf_out = malloc(buf_size_out); + + if ((p_buf_in) && (p_buf_out) && (buf_size_in > 0)) { + + if (opt_compression_type == COMPRESSION_TYPE_GB) + out_len = gbdecompress_buf(p_buf_in, buf_size_in, &p_buf_out, buf_size_out); + else if (opt_compression_type == COMPRESSION_TYPE_RLE_BLOCK) + out_len = rledecompress_buf(p_buf_in, buf_size_in, &p_buf_out, buf_size_out); + else + return EXIT_FAILURE; + + if (out_len > 0) { + + if (opt_c_source_output) { + c_source_set_sizes(buf_size_in, out_len); // compressed, decompressed + result = file_write_c_output_from_buffer(filename_out, p_buf_out, out_len, opt_c_source_output_varname, true); + } + else { + result = file_write_from_buffer(filename_out, p_buf_out, out_len); + } + + if (result) { + if (opt_verbose) + printf("Decompressed: %d bytes -> %d bytes (compression was %%%.2f)\n", buf_size_in, out_len, ((double)buf_size_in / (double)out_len) * 100); + return EXIT_SUCCESS; + } + } + } + + return EXIT_FAILURE; +} + + +int main( int argc, char *argv[] ) { + + // Exit with failure by default + int ret = EXIT_FAILURE; + + // Register cleanup with exit handler + atexit(cleanup); + + if (handle_args(argc, argv)) { + + if (opt_mode_compress) + ret = compress(); + else + ret = decompress(); + } + cleanup(); + + return ret; // Exit with failure by default +} diff --git a/gbdk/gbdk-support/gbcompress/rlecompress.c b/gbdk/gbdk-support/gbcompress/rlecompress.c new file mode 100644 index 00000000..47f02637 --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/rlecompress.c @@ -0,0 +1,232 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +//#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> +#include "rlecompress.h" + + +static uint8_t * FinBuf = NULL; +static uint32_t Fsize_in = 0; +static uint32_t FinIndex = 0; + +static uint8_t ** pp_FoutBuf = NULL; +static uint8_t * FoutBuf = NULL; +static uint32_t Fsize_out = 0; +static uint32_t FoutIndex = 0; + + +#define RLE_MASK_LEN 0x7F +#define RLE_MASK_TYPE 0x80 +#define RLE_TYPE_RAND 0x00 +#define RLE_TYPE_REPEAT 0x80 +#define RLE_MAX_LEN 127 +#define RLE_CTRL_END 0x00 +#define RLE_MAX_BLOCK 127 +#define RLE_CHANGE_COST 2 + + +static uint8_t rle_queued[128]; +static int rle_queue_idx = 0; +static int run_len = 0; + + +// Initialize the buffer vars +static void initbufs(uint8_t * inBuf, uint32_t size_in, uint8_t ** pp_outBuf, uint32_t size_out) { + + FinBuf = inBuf; + Fsize_in = size_in; + FinIndex = 0; + + pp_FoutBuf = pp_outBuf; + FoutBuf = *pp_outBuf; + Fsize_out = size_out; + FoutIndex = 0; +} + + +static void check_write_size(int len) { + + // Grow output buffer if needed + if ((FoutIndex + len) >= Fsize_out) { + uint8_t * p_tmp = *pp_FoutBuf; + + // Reallocate to twice as large + Fsize_out = Fsize_out * 2; + *pp_FoutBuf = (void *)realloc(*pp_FoutBuf, Fsize_out); + + // If realloc failed, free original buffer before quitting + if (!(*pp_FoutBuf)) { + printf("Error: Failed to grow memory for output buffer!\n"); + if (p_tmp) free(p_tmp); + p_tmp = NULL; + exit(EXIT_FAILURE); + } else + FoutBuf = *pp_FoutBuf; // Update working pointer + } +} + + + +static void write_end_of_data(void) { + + check_write_size(1); // writing 1 control byte + FoutBuf[FoutIndex++] = RLE_CTRL_END; // Write sequence control byte +} + + +static void write_run_repeat(int len, uint8_t value) { + + if (len) { + check_write_size(2); // writing 1 repeated value bytes + 1 control byte + // Write sequence control byte (length + type) + // Convert length to twos complement to identify it as repeat run + FoutBuf[FoutIndex++] = ((len & RLE_MASK_LEN) ^ 0xFFu) + 1u; + FoutBuf[FoutIndex++] = value; + } +} + + +static void rle_commit() { + + int idx = 0; + + if (rle_queue_idx > 0) { + check_write_size(rle_queue_idx + 1); // writing len bytes + 1 control byte + // Write sequence control byte (length + type) + FoutBuf[FoutIndex++] = (rle_queue_idx & RLE_MASK_LEN) | RLE_TYPE_RAND; + while (rle_queue_idx > 0) { + FoutBuf[FoutIndex++] = rle_queued[idx++]; + rle_queue_idx--; + } + } + rle_queue_idx = 0; // redundant +} + + + +// Convert buffer inBuf to rlecompress rle encoding and write out to outBuf +// Returns converted length +uint32_t rlecompress_buf(uint8_t * inBuf, uint32_t size_in, uint8_t ** pp_outBuf, uint32_t size_out) { + + uint8_t last, current; + initbufs(inBuf, size_in, pp_outBuf, size_out); + + last = 0; + run_len = 0; + rle_queue_idx = 0; + + while (FinIndex < Fsize_in) { + + current = FinBuf[FinIndex++]; + + if (current != last) { + // The run stopped, if switching to repeat is worthwhile then + // flush preceding random data + if (run_len > RLE_CHANGE_COST) { + rle_commit(); + write_run_repeat(run_len, last); + } else { + // Otherwise treat repeat data as random and queue it + // with flushing as needed + while(run_len--) { + if (rle_queue_idx >= RLE_MAX_BLOCK) { + rle_commit(); + } + rle_queued[rle_queue_idx++] = last; + } + } + run_len = 1; + last = current; + } + else { + // Flush pending repeat if max length is encountered + if (run_len >= RLE_MAX_BLOCK) { + rle_commit(); + write_run_repeat(run_len, last); + run_len = 0; + } + run_len++; + } + + } + + // If switching to repeat is worthwhile then flush preceding random data + if (run_len > RLE_CHANGE_COST) { + rle_commit(); + write_run_repeat(run_len, last); + } else { + // Otherwise treat repeat data as random and queue it + // with flushing as needed + while(run_len--) { + if (rle_queue_idx >= RLE_MAX_BLOCK) { + rle_commit(); + } + rle_queued[rle_queue_idx++] = last; + } + } + + // Flush any trailing data + rle_commit(); + write_end_of_data(); + + return FoutIndex; + +} + + + +static void write_single_byte(uint8_t data) { + + check_write_size(1); + + FoutBuf[FoutIndex++] = data; +} + + +static uint8_t read_single_byte(void) { + + if (FinIndex >= Fsize_in) { + printf("Error: Read past end of input buffer!\n"); + exit(EXIT_FAILURE); + } + + return (FinBuf[FinIndex++]); +} + + + +// Decompress buffer inBuf from gbcompress rle encoding and write to outBuf +// Returns converted length +uint32_t rledecompress_buf(uint8_t * inBuf, uint32_t size_in, uint8_t ** pp_outBuf, uint32_t size_out) { + + initbufs(inBuf, size_in, pp_outBuf, size_out); + + uint8_t token, rle_len, value; + + while (FinIndex < size_in) { + + token = read_single_byte(); + + // Check for EOF token, exit if encountered + if (token == RLE_CTRL_END) + break; + else if ((token & RLE_MASK_TYPE) == RLE_TYPE_RAND) { + rle_len = token & RLE_MASK_LEN; + while (rle_len--) + write_single_byte(read_single_byte()); + } + else { // if ((token & RLE_MASK_TYPE) == RLE_TYPE_REPEAT) { + rle_len = ((token ^ 0xFF) + 1) & RLE_MASK_LEN; // length is two's complement + value = read_single_byte(); + while (rle_len--) + write_single_byte(value); + } + } + + return FoutIndex; +} diff --git a/gbdk/gbdk-support/gbcompress/rlecompress.h b/gbdk/gbdk-support/gbcompress/rlecompress.h new file mode 100644 index 00000000..c39ae43d --- /dev/null +++ b/gbdk/gbdk-support/gbcompress/rlecompress.h @@ -0,0 +1,12 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _RLECOMPRESS_H +#define _RLECOMPRESS_H + +uint32_t rlecompress_buf(uint8_t * inBuf, uint32_t InSize, uint8_t ** pp_outBuf, uint32_t size_out); +uint32_t rledecompress_buf(uint8_t * inBuf, uint32_t size_in, uint8_t ** pp_outBuf, uint32_t size_out); +uint32_t rlecompress_buf_escaped(uint8_t * inBuf, uint32_t InSize, uint8_t ** pp_outBuf, uint32_t size_out); + +#endif // _RLECOMPRESS_H diff --git a/gbdk/gbdk-support/ihxcheck/LICENSE b/gbdk/gbdk-support/ihxcheck/LICENSE new file mode 100644 index 00000000..fdddb29a --- /dev/null +++ b/gbdk/gbdk-support/ihxcheck/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <https://unlicense.org> diff --git a/gbdk/gbdk-support/ihxcheck/Makefile b/gbdk/gbdk-support/ihxcheck/Makefile new file mode 100644 index 00000000..472eda8b --- /dev/null +++ b/gbdk/gbdk-support/ihxcheck/Makefile @@ -0,0 +1,31 @@ +# ihxcheck makefile + +ifndef TARGETDIR +TARGETDIR = /opt/gbdk +endif + +ifeq ($(OS),Windows_NT) + BUILD_OS := Windows_NT +else + BUILD_OS := $(shell uname -s) +endif + +# Target older macOS version than whatever build OS is for better compatibility +ifeq ($(BUILD_OS),Darwin) + export MACOSX_DEPLOYMENT_TARGET=10.10 +endif + +CC = $(TOOLSPREFIX)gcc +CFLAGS = -ggdb -O -Wno-incompatible-pointer-types -DGBDKLIBDIR=\"$(TARGETDIR)\" +OBJ = ihxcheck.o areas.o ihx_file.o +BIN = ihxcheck + + +all: $(BIN) + +$(BIN): $(OBJ) + +clean: + rm -f *.o $(BIN) *~ + rm -f *.exe + diff --git a/gbdk/gbdk-support/ihxcheck/areas.c b/gbdk/gbdk-support/ihxcheck/areas.c new file mode 100644 index 00000000..9289b58c --- /dev/null +++ b/gbdk/gbdk-support/ihxcheck/areas.c @@ -0,0 +1,97 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +// #include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "areas.h" + +#define AREA_GROW_SIZE 500 + +area_item * arealist; +uint32_t arealist_size; +uint32_t arealist_count; + + +uint32_t min(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + +uint32_t max(uint32_t a, uint32_t b) { + return (a > b) ? a : b; +} + + +// Returns size of overlap between two address ranges, +// if zero then no overlap +static uint32_t addrs_check_overlap(uint32_t a_start, uint32_t a_end, uint32_t b_start, uint32_t b_end) { + + uint32_t size_used; + + // Check whether the address range *doesn't* overlap + if ((b_start > a_end) || (b_end < a_start)) { + size_used = 0; // no overlap, size = 0 + } else { + size_used = min(b_end, a_end) - max(b_start, a_start) + 1; // Calculate minimum overlap + + printf("WARNING: Multiple write of %5d bytes at 0x%x -> 0x%x (%x -> %x, %x -> %x)\n", + size_used, max(b_start, a_start), min(b_end, a_end), + a_start, a_end, b_start, b_end); + } + return size_used; +} + + +void arealist_additem(area_item * p_area) { + + arealist_count++; + // Grow array if needed + if (arealist_count == arealist_size) { + arealist_size += AREA_GROW_SIZE; + arealist = (area_item *)realloc(arealist, arealist_size * sizeof(area_item)); + } + + arealist[arealist_count-1] = *p_area; +} + + +void areas_init(void) { + arealist_count = 0; + arealist_size = AREA_GROW_SIZE; + arealist = (area_item *)malloc(arealist_size * sizeof(area_item)); +} + + +void areas_cleanup(void) { + if (arealist) + free (arealist); +} + + +int areas_add(area_item * p_area) { + + uint32_t c; + uint32_t size_used; + int ret = true; // default to success + + // Check for overlap with existing areas + for (c = 0; c < arealist_count; c++) { + + size_used = addrs_check_overlap(arealist[c].start, arealist[c].end, + p_area->start, p_area->end); + // Signal failure on any overlap + // (Keep looping to display all warnings though) + if (size_used > 0) + ret = false; + } + + // Now add the area + arealist_additem(p_area); + + return ret; +}
\ No newline at end of file diff --git a/gbdk/gbdk-support/ihxcheck/areas.h b/gbdk/gbdk-support/ihxcheck/areas.h new file mode 100644 index 00000000..1596a7de --- /dev/null +++ b/gbdk/gbdk-support/ihxcheck/areas.h @@ -0,0 +1,19 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _AREAS_H +#define _AREAS_H + +typedef struct area_item { + uint32_t start; + uint32_t end; + uint32_t length; +} area_item; + + +void areas_init(void); +void areas_cleanup(void); +int areas_add(area_item * p_area); + +#endif // _AREAS_H
\ No newline at end of file diff --git a/gbdk/gbdk-support/ihxcheck/ihx_file.c b/gbdk/gbdk-support/ihxcheck/ihx_file.c new file mode 100644 index 00000000..e2b8a6e3 --- /dev/null +++ b/gbdk/gbdk-support/ihxcheck/ihx_file.c @@ -0,0 +1,271 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "areas.h" +#include "ihx_file.h" + +// Example data to parse from a .ihx file +// No area names +// : 01 0020 00 E9 F6 +// S BB AAAA RR DD CC +// +// S: Start (":") (1 char) +// BB: ByteCount (2 chars, one byte) +// AAAA: Address (4 chars, two bytes) +// RR: Record Type (2 chars, one byte. 00=data, 01=EOF, Others) +// DD: Data (<ByteCount> bytes) +// CC: Checksum (2 chars, one byte. Sum record bytes, 2's complement of LSByte) +/* +:01002000E9F6 +:05002800220D20FCC9BF +:070030001A22130D20FAC98A +. +. +. +:00000001FF (EOF indicator) +*/ + +/* TODO/WARNING/BUG: 100% full banks +It may not be possible to easily tell the difference between a perfectly filled +bank (100% and no more) and an adjacent partially filled bank that starts at +zero - versus - the first bank overflowed into the second that is empty. + +Currently the 100% bank will get merged into the next one and present as overflow +*/ + + +#define BANK_NUM(addr) ((addr & 0xFFFFC000U) >> 14) +#define ADDR_UNSET 0xFFFFFFFEU + +#define MAX_STR_LEN 4096 +#define IHX_DATA_LEN_MAX 255 +#define IHX_REC_LEN_MIN (1 + 2 + 4 + 2 + 0 + 2) // Start(1), ByteCount(2), Addr(4), Rec(2), Data(0..255x2), Checksum(2) + +// IHX record types +#define IHX_REC_DATA 0x00U +#define IHX_REC_EOF 0x01U +#define IHX_REC_EXTSEG 0x02U +#define IHX_REC_STARTSEG 0x03U +#define IHX_REC_EXTLIN 0x04U +#define IHX_REC_STARTLIN 0x05U + +typedef struct ihx_record { + uint16_t length; + uint32_t byte_count; + uint32_t address; + uint32_t address_end; + uint32_t type; + uint32_t checksum; // Would prefer this be a uint8_t, but mingw sscanf("%2hhx") has a buffer overflow that corrupts adjacent data +} ihx_record; + +uint32_t g_address_upper; +bool g_option_warnings_as_errors = false; + +void set_option_warnings_as_errors(bool new_val) { + g_option_warnings_as_errors = new_val; +} + + +// Return false if any character isn't a valid hex digit +int check_hex(char * c) { + while (*c != '\0') { + if ((*c >= '0') && (*c <= '9')) + c++; + if ((*c >= 'A') && (*c <= 'F')) + c++; + if ((*c >= 'a') && (*c <= 'f')) + c++; + else + return false; + } + + return true; +} + + +// Parse and validate an IHX record +int ihx_parse_and_validate_record(char * p_str, ihx_record * p_rec) { + + int calc_length = 0; + int c; + uint32_t ctemp, checksum_calc = 0; // Avoid mingw sscanf("%2hhx") buffer overflow with uint8_t + + // Remove trailing CR and LF + p_rec->length = strlen(p_str); + for (c = 0;c < p_rec->length;c++) { + if (p_str[c] == '\n' || p_str[c] == '\r') { + p_str[c] = '\0'; // Replace char with string terminator + p_rec->length = c; // Shrink length to truncated size + break; // Exit loop after finding first CR or LF + } + } + + // Only parse lines that start with ':' character (Start token for IHX record) + if (p_str[0] != ':') { + printf("Warning: IHX: Invalid start of line token for line: %s \n", p_str); + return false; + } + + // Require minimum length + if (p_rec->length < IHX_REC_LEN_MIN) { + printf("Warning: IHX: Invalid line, too few characters: %s. Is %d, needs at least %d \n", p_str, p_rec->length, IHX_REC_LEN_MIN); + return false; + } + + // Only hex characters are allowed after start token + p_str++; // Advance past Start code + if (check_hex(p_str)) { + printf("Warning: IHX: Invalid line, non-hex characters present: %s\n", p_str); + return false; + } + + // Read record header: byte count, start address, type + sscanf(p_str, "%2x%4x%2x", &p_rec->byte_count, &p_rec->address, &p_rec->type); + p_str += (2 + 4 + 2); + + + // Require expected data byte count to fit within record length (at 2 chars per hex byte) + calc_length = IHX_REC_LEN_MIN + (p_rec->byte_count * 2); + if (p_rec->length != calc_length) { + printf("Warning: IHX: byte count doesn't match length available in record! Record length = %d, Calc length = %d, bytecount = %d \n", p_rec->length, calc_length, p_rec->byte_count); + return false; + } + + // Is this an extended linear address record? Read in offset address if so + if (p_rec->type == IHX_REC_EXTLIN) { + sscanf(p_str, "%4x", &g_address_upper); + g_address_upper <<= 16; // Shift into upper 16 bits of address space + } + else if (p_rec->type == IHX_REC_DATA) { + + // Don't process records with zero bytes of length + if (p_rec->byte_count == 0) { + printf("Warning: IHX: Zero length record starting at %x\n", p_rec->address); + return false; + } + + // Apply extended linear address (upper 16 bits of address space) + // Calculate end address + p_rec->address |= g_address_upper; + p_rec->address_end = p_rec->address + p_rec->byte_count - 1; + } + + + // Read data segment and calculate checsum of data + headers + checksum_calc = p_rec->byte_count + (p_rec->address & 0xFF) + ((p_rec->address >> 8) & 0xFF) + p_rec->type; + for (c = 0;c < p_rec->byte_count;c++) { + sscanf(p_str, "%2x", &ctemp); + p_str += 2; + checksum_calc += ctemp; + } + + // Final calculated checeksum is 2's complement of LSByte + checksum_calc = (((checksum_calc & 0xFF) ^ 0xFF) + 1) & 0xFF; + + // Read checksum from data + sscanf(p_str, "%2x", &p_rec->checksum); + p_str += 2; + + if (p_rec->checksum != checksum_calc) { + printf("Warning: IHX: record checksum %x didn't match calculated checksum %x\n", p_rec->checksum, checksum_calc); + return false; + } + + // For records that start in banks above the unbanked region (0x000 - 0x3FFF) + // Warn (but don't error) if they cross the boundary between different banks + if ((p_rec->address >= 0x00004000U) && + ((p_rec->address & 0xFFFFC000U) != (p_rec->address_end & 0xFFFFC000U))) { + printf("Warning: Write from one bank spans into the next. %x -> %x (bank %d -> %d)\n", + p_rec->address, p_rec->address_end, BANK_NUM(p_rec->address), BANK_NUM(p_rec->address_end)); + } + + return true; +} + + +int ihx_file_process_areas(char * filename_in) { + + int ret = EXIT_SUCCESS; // default to success + char cols; + char strline_in[MAX_STR_LEN] = ""; + FILE * ihx_file = fopen(filename_in, "r"); + area_item area; + ihx_record ihx_rec; + + areas_init(); + + // Initialize global upper address modifier + g_address_upper = 0x0000; + + // Initialize area record + area.start = ADDR_UNSET; + area.end = ADDR_UNSET; + + + if (ihx_file) { + + // Read one line at a time into \0 terminated string + while (fgets(strline_in, sizeof(strline_in), ihx_file) != NULL) { + + // Parse record, skip if fails validation + if (!ihx_parse_and_validate_record(strline_in, &ihx_rec)) + continue; + + // Process the pending record and exit if last record (EOF) + // Also ignore non-default data records (don't seem to occur for gbz80) + if (ihx_rec.type == IHX_REC_EOF) { + if (!areas_add(&area) && g_option_warnings_as_errors) + ret = EXIT_FAILURE; + continue; + } else if (ihx_rec.type == IHX_REC_EXTLIN) { + // printf("Extended linear address changed to %08x %s\n\n\n", g_address_upper, strline_in); + continue; + } else if (ihx_rec.type != IHX_REC_DATA) { + printf("Warning: IHX: dropped record %s of type %d\n", strline_in, ihx_rec.type); + continue; + } + + // Records are left pending (non-processed) until they don't merge + // with the current incoming record *or* the final (EOF) record is found. + + // Try to merge with (pending) previous record if it's address-adjacent, + // except when the new record starts or ends on a bank boundary + // (this reduces count from 1000's since most are only 32 bytes long) + if ((ihx_rec.address == area.end + 1) && ((ihx_rec.address & 0x00003FFFU) != 0x00000000U)) { + area.end = ihx_rec.address_end; // append to previous area + } else if ((ihx_rec.address_end == area.start + 1) && !((ihx_rec.address_end & 0x00003FFFU) != 0x00003FFFU)) { + area.start = ihx_rec.address; // pre-pend to previous area + } else { + // New record was *not* adjacent to last, + // so process the last/pending record + if (area.start != ADDR_UNSET) { + if (!areas_add(&area) && g_option_warnings_as_errors) + ret = EXIT_FAILURE; + } + // Now queue current record as pending for next loop + area.start = ihx_rec.address; + area.end = ihx_rec.address + ihx_rec.byte_count - 1; + } + + } // end: while still lines to process + + fclose(ihx_file); + + } // end: if valid file + else { + printf("Problem with filename or unable to open file! %s\n", filename_in); + ret = EXIT_FAILURE; + } + + areas_cleanup(); + return ret; +} + diff --git a/gbdk/gbdk-support/ihxcheck/ihx_file.h b/gbdk/gbdk-support/ihxcheck/ihx_file.h new file mode 100644 index 00000000..04e3cbd2 --- /dev/null +++ b/gbdk/gbdk-support/ihxcheck/ihx_file.h @@ -0,0 +1,12 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + + +#ifndef _IHX_FILE_H +#define _IHX_FILE_H + +int ihx_file_process_areas(char * filename_in); +void set_option_warnings_as_errors(bool new_val); + +#endif // _IHX_FILE_H
\ No newline at end of file diff --git a/gbdk/gbdk-support/ihxcheck/ihxcheck.c b/gbdk/gbdk-support/ihxcheck/ihxcheck.c new file mode 100644 index 00000000..603b9bf1 --- /dev/null +++ b/gbdk/gbdk-support/ihxcheck/ihxcheck.c @@ -0,0 +1,88 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "ihx_file.h" +#include "areas.h" + +#define MAX_STR_LEN 4096 + +void display_help(void); +int handle_args(int argc, char * argv[]); + +char filename_in[MAX_STR_LEN] = {'\0'}; + + +void display_help(void) { + fprintf(stdout, + "ihx_check input_file.ihx [options]\n" + "\n" + "Options\n" + "-h : Show this help\n" + "-e : Treat warnings as errors\n" + "\n" + "Use: Read a .ihx and warn about overlapped areas.\n" + "Example: \"ihx_check build/MyProject.ihx\"\n" + ); +} + + +int handle_args(int argc, char * argv[]) { + + int i; + + if( argc < 2 ) { + display_help(); + return false; + } + + // Copy input filename (if not preceded with option dash) + if (argv[1][0] != '-') + snprintf(filename_in, sizeof(filename_in), "%s", argv[1]); + + // Start at first optional argument, argc is zero based + for (i = 1; i <= (argc -1); i++ ) { + + if (strstr(argv[i], "-h") == argv[i]) { + display_help(); + return false; // Don't parse input when -h is used + + } else if (strstr(argv[i], "-e") == argv[i]) { + set_option_warnings_as_errors(true); + } + + } + + return true; +} + + +int matches_extension(char * filename, char * extension) { + return (strcmp(filename + (strlen(filename) - strlen(extension)), extension) == 0); +} + + +int main( int argc, char *argv[] ) { + + int ret = EXIT_FAILURE; // Exit with failure by default + + if (handle_args(argc, argv)) { + + // Must at least have extension + if (strlen(filename_in) >=5) { + // detect file extension + if (matches_extension(filename_in, (char *)".ihx")) { + ret = ihx_file_process_areas(filename_in); + } + } + } + + return ret; // Exit with failure by default +}
\ No newline at end of file diff --git a/gbdk/gbdk-support/lcc/Makefile b/gbdk/gbdk-support/lcc/Makefile new file mode 100644 index 00000000..1320b482 --- /dev/null +++ b/gbdk/gbdk-support/lcc/Makefile @@ -0,0 +1,39 @@ +# Simple Makefile for the lcc frontend. + +ifndef TARGETDIR +TARGETDIR = /opt/gbdk +endif + +# MacOS date doesn't support '--utc', use '-u' +BUILDDATE=$(shell date -u +%Y/%m/%d) +BUILDTIME=$(shell date -u +%H:%M:%S) + +ifeq ($(OS),Windows_NT) + BUILD_OS := Windows_NT +else + BUILD_OS := $(shell uname -s) +endif + +# Target older macOS version than whatever build OS is for better compatibility +ifeq ($(BUILD_OS),Darwin) + export MACOSX_DEPLOYMENT_TARGET=10.10 +endif + + +CC = $(TOOLSPREFIX)gcc +CFLAGS = -ggdb -O -Wno-incompatible-pointer-types -DGBDKLIBDIR=\"$(TARGETDIR)\" +CFLAGS += -DBUILDDATE=\"$(BUILDDATE)\" -DBUILDTIME=\"$(BUILDTIME)\" +ifdef BINDIR +CFLAGS += -DGBDKBINDIR=\"$(BINDIR)\" +endif +OBJ = lcc.o gb.o targets.o list.o +BIN = lcc + +all: $(BIN) + +$(BIN): $(OBJ) + +clean: + rm -f *.o $(BIN) *~ + rm -f *.exe + diff --git a/gbdk/gbdk-support/lcc/gb.c b/gbdk/gbdk-support/lcc/gb.c new file mode 100644 index 00000000..276ccad7 --- /dev/null +++ b/gbdk/gbdk-support/lcc/gb.c @@ -0,0 +1,353 @@ +/* Unix */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <ctype.h> + +#ifdef __WIN32__ +#include <windows.h> +#endif + +#include "gb.h" +#include "targets.h" + +#ifndef GBDKLIBDIR +#define GBDKLIBDIR "\\gbdk\\" +#endif + +extern char *progname; +extern char * strsave(const char *); + +// Set default class as the first entry in classes (Game Boy) +static CLASS *_class = &classes[0]; + +static struct { + const char *name; + const char *val; +} _tokens[] = { + // expandable string tokens used in "CLASS" command strings + { "port", "sm83" }, // if default class is ever changed from Game Boy, this default (and plat) may need to be changed to match + { "plat", "gb" }, + { "sdccdir", "%bindir%"}, + { "cpp", "%sdccdir%sdcpp" }, + { "cppdefault", "-Wall -DSDCC_PORT=%port% -DSDCC_PLAT=%plat% -D%cppmodel%" + }, + { "cppmodel", "SDCC_MODEL_SMALL" }, + { "includedefault", "-I%includedir%" }, + { "includedir", "%prefix%include" }, + { "prefix", GBDKLIBDIR }, + { "comopt", "--noinvariant --noinduction" }, + { "commodel", "small" }, + { "com", "%sdccdir%sdcc" }, + { "comflag", "-c"}, + { "comdefault", "-m%port% --no-std-crt0 --fsigned-char --use-stdout -D__PORT_%port% -D__TARGET_%plat% "}, + /* asdsgb assembler defaults: + -p: disable pagination + -o: create object file + -g: make undef symbols global + -n: defer symbol resolving to link time (autobanking relies on this) [requires sdcc 12238+] + */ + { "asdefault", "-pogn -I%libdir%%plat%" }, + { "as_gb", "%sdccdir%sdasgb" }, + { "as_z80", "%sdccdir%sdasz80" }, + { "bankpack", "%bindir%bankpack" }, + { "ld_gb", "%sdccdir%sdldgb" }, + { "ld_z80", "%sdccdir%sdldz80" }, + { "libdir", "%prefix%lib/%libmodel%/asxxxx/" }, + { "libmodel", "small" }, +#ifndef GBDKBINDIR + { "bindir", "%prefix%bin/" }, +#else + { "bindir", GBDKBINDIR }, +#endif + { "ihxcheck", "%bindir%ihxcheck" }, + { "mkbin", "%sdccdir%makebin" }, + { "crt0dir", "%libdir%%plat%/crt0.o"}, + { "libs_include", "-k %libdir%%port%/ -l %port%.lib -k %libdir%%plat%/ -l %plat%.lib"}, + { "mkcom", "%sdccdir%makecom"} +}; + +static char *getTokenVal(const char *key) +{ + int i; + for (i = 0; i < ARRAY_LEN(_tokens); i++) { + if (!strcmp(_tokens[i].name, key)) + return strdup(_tokens[i].val); + } + assert(0); + return NULL; +} + +static void setTokenVal(const char *key, const char *val) +{ + int i; + for (i = 0; i < ARRAY_LEN(_tokens); i++) { + if (!strcmp(_tokens[i].name, key)) { + _tokens[i].val = strdup(val); + return; + } + } + assert(0); +} + +// Sets the local _class to a given Port/Platform from classes[] +static int setClass(const char *port, const char *plat) +{ + int i; + for (i = 0; i < classes_count; i++) { + if (!strcmp(classes[i].port, port)) { + if (plat && classes[i].plat && !strcmp(classes[i].plat, plat)) { + _class = classes + i; + return 1; + } + else if (!classes[i].plat || !plat) { + _class = classes + i; + return 1; + } + } + } + return 0; +} + +/* Algorithim + while (chars in string) + if space, concat on end + if % + Copy off what we have sofar + Call ourself on value of token + Continue scanning +*/ + +/* src is destroyed */ +static char **subBuildArgs(char **args, char *template) +{ + char *src = template; + char *last = src; + static int quoting = 0; + + /* Shared buffer between calls of this function. */ + static char buffer[128]; + static int indent = 0; + + indent++; + while (*src) { + if (isspace(*src) && !quoting) { + /* End of set - add in the command */ + *src = '\0'; + strcat(buffer, last); + *args = strdup(buffer); + buffer[0] = '\0'; + args++; + last = src + 1; + } + else if (*src == '%') { + /* Again copy off what we already have */ + *src = '\0'; + strcat(buffer, last); + *src = '%'; + src++; + last = src; + while (*src != '%') { + if (!*src) { + /* End of string without closing % */ + assert(0); + } + src++; + } + *src = '\0'; + /* And recurse :) */ + args = subBuildArgs(args, getTokenVal(last)); + *src = '%'; + last = src + 1; + } + else if (*src == '\"') { + quoting = !quoting; + } + src++; + } + strcat(buffer, last); + if (indent == 1) { + *args = strdup(buffer); + args++; + buffer[0] = '\0'; + } + + indent--; + return args; +} + +static void buildArgs(char **args, const char *template) +{ + char *s = strdup(template); + char **last; + last = subBuildArgs(args, s); + *last = NULL; +} + +// If order is changed here, file type handling MUST be updated +// in lcc.c: "switch (suffix(name, suffixes, 5)) {" +char *suffixes[] = { + EXT_C, // 0 + EXT_I, // 1 + EXT_ASM ";" EXT_S, // 2 + EXT_O ";" EXT_OBJ, // 3 + EXT_IHX, // 4 + EXT_GB, // 5 + 0 +}; + +char inputs[256] = ""; + +char *cpp[256]; +char *include[256]; +char *com[256] = { "", "", "" }; +char *as[256]; +char *ihxcheck[256]; +char *ld[256]; +char *bankpack[256]; +char *mkbin[256]; +char *postproc[256]; +char *rom_extension; +arg_entry *llist0_defaults; +int llist0_defaults_len = 0; + + +const char *starts_with(const char *s1, const char *s2) +{ + if (!strncmp(s1, s2, strlen(s2))) { + return s1 + strlen(s2); + } + return NULL; +} + +int option(char *arg) { + const char *tail; + if ((tail = starts_with(arg, "--prefix="))) { + /* Go through and set all of the paths */ + setTokenVal("prefix", tail); + return 1; + } + else if ((tail = starts_with(arg, "--gbdklibdir="))) { + setTokenVal("libdir", tail); + return 1; + } + else if ((tail = starts_with(arg, "--gbdkincludedir="))) { + setTokenVal("includedir", tail); + return 1; + } + else if ((tail = starts_with(arg, "--sdccbindir="))) { + // Allow to easily run with external SDCC snapshot / release + setTokenVal("sdccdir", tail); + return 1; + } + else if ((tail = starts_with(arg, "-S"))) { + // -S is compile to ASM only + // When composing the compile stage, swap in of -S instead of default -c + setTokenVal("comflag", "-S"); + } + else if ((tail = starts_with(arg, "-no-crt"))) { + // When composing link stage, clear out crt0dir path + setTokenVal("crt0dir", ""); + } + else if ((tail = starts_with(arg, "-no-libs"))) { + // When composing link stage, clear out crt0dir path + setTokenVal("libs_include", ""); + } + else if ((tail = starts_with(arg, "-m"))) { + char word_count = 0; + char * p_str = strtok( strsave(tail),":"); // Copy arg str so it doesn't get unmodified by strtok() + char * words[3]; // +1 in size of expected number of entries to detect excess + + // Split string into words separated by ':' chars (expecting PORT and PLAT) + while (p_str != NULL) { + words[word_count++] = p_str; + p_str = strtok(NULL, ":"); + if (word_count >= ARRAY_LEN(words)) break; + } + + // Requires both PORT and PLAT, must match a valid setClass entry. + if (word_count == 2) { + setTokenVal("port", words[0]); + setTokenVal("plat", words[1]); + if (!setClass(words[0], words[1])) { + fprintf(stderr, "Error: %s: unrecognised PORT:PLATFORM from %s:%s\n", progname, words[0], words[1]); + exit(-1); + } + } else { + fprintf(stderr, "Error: -m requires both/only PORT and PLATFORM values (ex: -msm83:gb) : %s\n", arg); + exit(-1); + } + + return 1; + } + else if ((tail = starts_with(arg, "--model-"))) { + if (!strcmp(tail, "small")) { + setTokenVal("commodel", "small"); + setTokenVal("libmodel", "small"); + setTokenVal("cppmodel", "SDCC_MODEL_SMALL"); + return 1; + } + else if (!strcmp(tail, "medium")) { + setTokenVal("commodel", "medium"); + setTokenVal("libmodel", "medium"); + setTokenVal("cppmodel", "SDCC_MODEL_MEDIUM"); + return 1; + } + } + return 0; +} + +// Build the port/platform specific toolchain command strings +// and apply any related settings +void finalise(void) +{ + if (!_class->plat) { + setTokenVal("plat", _class->default_plat); + } + buildArgs(cpp, _class->cpp); + buildArgs(include, _class->include); + buildArgs(com, _class->com); + buildArgs(as, _class->as); + buildArgs(bankpack, _class->bankpack); + buildArgs(ld, _class->ld); + buildArgs(ihxcheck, _class->ihxcheck); + buildArgs(mkbin, _class->mkbin); + if (strlen(_class->postproc) != 0) buildArgs(postproc, _class->postproc); else postproc[0] = '\0'; + rom_extension = strdup(_class->rom_extension); + llist0_defaults = _class->llist0_defaults; + llist0_defaults_len = _class->llist0_defaults_len; +} + +void set_gbdk_dir(char* argv_0) +{ + char buf[1024 - 2]; // Path will get quoted below, so reserve two characters for them +#ifdef __WIN32__ + char slash = '\\'; + if (GetModuleFileName(NULL,buf, sizeof(buf)) == 0) + { + return; + } +#else + char slash = '/'; + strncpy(buf, argv_0, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; +#endif + + // Strip of the trailing GBDKDIR/bin/lcc.exe and use it as the prefix. + char *p = strrchr(buf, slash); + if (p) { + while(p != buf && *(p - 1) == slash) //Fixes https://github.com/Zal0/gbdk-2020/issues/29 + -- p; + *p = '\0'; + + p = strrchr(buf, slash); + if (p) { + *++p = '\0'; + char quotedBuf[1024]; + snprintf(quotedBuf, sizeof(quotedBuf), "\"%s\"", buf); + setTokenVal("prefix", quotedBuf); + } + } +} diff --git a/gbdk/gbdk-support/lcc/gb.h b/gbdk/gbdk-support/lcc/gb.h new file mode 100644 index 00000000..51abf237 --- /dev/null +++ b/gbdk/gbdk-support/lcc/gb.h @@ -0,0 +1,31 @@ +// gb.h + +#ifndef _LCC_GB_H +#define _LCC_GB_H + +#define SUFX_NOMATCH -1 + +#define EXT_C ".c" +#define EXT_I ".i" +#define EXT_ASM ".asm" +#define EXT_S ".s" +#define EXT_O ".o" +#define EXT_OBJ ".obj" +#define EXT_IHX ".ihx" +#define EXT_LK ".lk" +#define EXT_ROM ".rom" + +// ROM file extensions +#define EXT_GB ".gb" +#define EXT_AP ".pocket" +#define EXT_DUCK ".duck" +#define EXT_SMS ".sms" +#define EXT_GG ".gg" +#define EXT_MSXDOS ".com" + +#define ARRAY_LEN(A) (sizeof(A) / sizeof(A[0])) + +#endif // _LCC_GB_H + + + diff --git a/gbdk/gbdk-support/lcc/lcc.c b/gbdk/gbdk-support/lcc/lcc.c new file mode 100644 index 00000000..c3d12a7c --- /dev/null +++ b/gbdk/gbdk-support/lcc/lcc.c @@ -0,0 +1,1188 @@ +/* + * lcc [ option ]... [ file | -llib ]... + * front end for the ANSI C compiler + */ +static char rcsid[] = "$Id: lcc.c,v 2.0 " BUILDDATE " " BUILDTIME " gbdk-2020 Exp $"; + +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include <signal.h> + +#ifdef _WIN32 +# include <io.h> +# include <process.h> +#else +# include <unistd.h> +# include <sys/types.h> +# include <sys/wait.h> +#endif + +#include "gb.h" +#include "list.h" +#include "targets.h" + +#ifndef TEMPDIR +#define TEMPDIR "/tmp" +#endif + +void *alloc(int); +extern char *basepath(char *); +extern char *path_stripext(char *); +extern char *path_newext(char *, char *); +static int callsys(char *[]); +extern char *concat(const char *, const char *); +static void compose(char *[], List, List, List); +static void error(char *, char *); +static char *exists(char *); +static char *first(char *); +static int filename(char *, char *); +static void help(void); +static void initinputs(void); +static void interrupt(int); +static void opt(char *); +extern int main(int, char *[]); +extern char *replace(const char *, int, int); +static void rm(List); +extern char *strsave(const char *); +extern char *stringf(const char *, ...); +extern int suffix(char *, char *[], int); +extern char *tempname(char *); + +static bool arg_has_searchkey(char *, char *); + +// Adds linker default required vars if not present (defined by user) +static void Fixllist(); + +static void handle_autobanking(void); + + +// These get populated from _class using finalise() in gb.c +extern char *cpp[], *include[], *com[], *as[], *bankpack[], *ld[], *ihxcheck[], *mkbin[], *postproc[], inputs[], *suffixes[], *rom_extension; +extern arg_entry *llist0_defaults; +extern int llist0_defaults_len; + +extern int option(char *); +extern void set_gbdk_dir(char*); + +void finalise(void); + +static int errcnt; /* number of errors */ +static int Eflag; /* -E specified */ +static int Sflag; /* -S specified */ +static int cflag; /* -c specified */ +static int Kflag; /* -K specified */ +static int autobankflag; /* -K specified */ +int verbose; /* incremented for each -v */ +static List bankpack_flags; /* bankpack flags */ +static List ihxchecklist; /* ihxcheck flags */ +static List mkbinlist; /* loader files, flags */ + +// Index entries for llist[] +#define L_ARGS 0 +#define L_FILES 1 +#define L_LKFILES 2 +static List llist[3]; /* [2] = .lkfiles, [1] = linker object file list, [0] = linker flags */ + +static List alist; /* assembler flags */ +List clist; /* compiler flags */ +static List plist; /* preprocessor flags */ +static List ilist; /* list of additional includes from LCCINPUTS */ +static List rmlist; /* list of files to remove */ +static char *outfile; /* ld output file or -[cS] object file */ +static int ac; /* argument count */ +static char **av; /* argument vector */ +char *tempdir = TEMPDIR; /* directory for temporary files */ +char *progname; +static List lccinputs; /* list of input directories */ +char bankpack_newext[1024] = {'\0'}; +static int ihx_inputs = 0; // Number of ihx files present in input list +static char ihxFile[256] = {'\0'}; +static char binFile[256] = {'\0'}; + + +int main(int argc, char *argv[]) { + int i, j, nf; + + progname = argv[0]; + ac = argc + 50; + av = alloc(ac * sizeof(char *)); + if (signal(SIGINT, SIG_IGN) != SIG_IGN) + signal(SIGINT, interrupt); + if (signal(SIGTERM, SIG_IGN) != SIG_IGN) + signal(SIGTERM, interrupt); +#ifdef SIGHUP + if (signal(SIGHUP, SIG_IGN) != SIG_IGN) + signal(SIGHUP, interrupt); +#endif + if (getenv("TMP")) + tempdir = getenv("TMP"); + else if (getenv("TEMP")) + tempdir = getenv("TEMP"); + else if (getenv("TMPDIR")) + tempdir = getenv("TMPDIR"); + assert(tempdir); + + // Remove trailing slashes + i = strlen(tempdir); + for (; (i > 0) && ((tempdir[i - 1] == '/') || (tempdir[i - 1] == '\\')); i--) + tempdir[i - 1] = '\0'; + if (argc <= 1) { + help(); + exit(0); + } +// clist = append("-D__LCC__", 0); + initinputs(); + if (getenv("GBDKDIR")) + option(stringf("--prefix=%s", getenv("GBDKDIR"))); + else + set_gbdk_dir(argv[0]); + for (nf = 0, i = j = 1; i < argc; i++) { + if (strcmp(argv[i], "-o") == 0) { + if (++i < argc) { + // Don't allow output file to have ".c" or ".i" extension (first two in suffixes[]) + if (suffix(argv[i], suffixes, 2) != SUFX_NOMATCH) { + error("-o would overwrite %s", argv[i]); + exit(8); + } + // Valid output file found + outfile = argv[i]; + continue; + } + else { + error("unrecognized option `%s'", argv[i - 1]); + exit(8); + } + } + else if (strcmp(argv[i], "-target") == 0) { + if (argv[i + 1] && *argv[i + 1] != '-') + i++; + continue; + } + else if (*argv[i] == '-' && argv[i][1] != 'l') { + opt(argv[i]); + continue; + } + else if (*argv[i] != '-') { + // Count number of (.ihx) files + if (suffix(argv[i], (char * []){EXT_IHX}, 1) != SUFX_NOMATCH) + ihx_inputs++; + // Count number of (.c, .i, .asm, .s) files + else if (suffix(argv[i], suffixes, 3) != SUFX_NOMATCH) + nf++; + } + argv[j++] = argv[i]; + } + + // Ignore -o output request if: + // * compile only (-c) *OR* compile to ASM (-S) is specified + // * and there are 2 or more source files (.c, .i, .asm, .s) in the input list (what "nf" seems to count) + // Instead, it will generate output matching each input filename + if ((cflag || Sflag) && outfile && nf != 1) { + fprintf(stderr, "%s: -o %s ignored\n", progname, outfile); + outfile = 0; + } + + // When .ihx is an input only ihxcheck and makebin will be called. + // Warn that all source files won't be processed + if ((ihx_inputs > 0) && (nf > 0)) { + fprintf(stderr, "%s: Warning: .ihx file present as input, all other input files ignored\n", progname); + } + + // Add includes + argv[j] = 0; + + // This copies settings from port:platform "class" structure + // into command strings used for compose() + finalise(); + + for (i = 0; include[i]; i++) + clist = append(include[i], clist); + if (ilist) { + List b = ilist; + do { + b = b->link; + clist = append(b->str, clist); + } while (b != ilist); + } + ilist = 0; + for (i = 1; argv[i]; i++) + // Process arguments + if (*argv[i] == '-') + opt(argv[i]); + else { + // Process filenames + char *name = exists(argv[i]); + if (name) { + if (strcmp(name, argv[i]) != 0 + || ((nf > 1) && (suffix(name, suffixes, 3) != SUFX_NOMATCH)) ) // Does it match: .c, .i, .asm, .s + + fprintf(stderr, "%s:\n", name); + // Send input filename argument to "filename processor" + // which will add them to llist[n] in some form most of the time + filename(name, 0); + } + else + error("can't find `%s'", argv[i]); + } + + + // Perform Link / ihxcheck / makebin stages (unless some conditions prevent it) + if (errcnt == 0 && !Eflag && !cflag && !Sflag && + (llist[L_FILES] || llist[L_LKFILES] || ((ihxFile[0] != '\0') && ihx_inputs))) { + + int target_is_ihx = 0; + + // if outfile is not specified, set it to default rom extension for active port:platform + if(!outfile) + outfile = concat("a", rom_extension); + + // If an .ihx file is present as input skip link related stages + if (ihx_inputs > 0) { + + // Only one .ihx can be used for input, warn that others will be ignored + if (ihx_inputs > 1) + fprintf(stderr, "%s: Warning: Multiple (%d) .ihx files present as input, only one (%s) will be used\n", progname, ihx_inputs, ihxFile); + } + else { + + //file.gb to file.ihx (don't use tmpfile because maps and other stuffs are created there) + // Check to see if output target is a .ihx file + target_is_ihx = (suffix(outfile, (char *[]){EXT_IHX}, 1) != SUFX_NOMATCH); + + // Build ihx file name from output name + sprintf(ihxFile, "%s%s", path_stripext(outfile), EXT_IHX); + + // Only remove .ihx from the delete-list if it's not the final target + if (!target_is_ihx) + append(ihxFile, rmlist); + + // If auto bank assignment is enabled, modify obj files before linking + // Will alter: llist[L_FILES] and llist[L_LKFILES] + if (autobankflag) + handle_autobanking(); + + // Copy any pending linkerfiles into the linker list (with "-f" as preceding arg) + llist[L_FILES] = list_add_to_another(llist[L_FILES], llist[L_LKFILES], NULL, "-f"); + // Call linker (add output ihxfile in compose $3) + Fixllist(); // Fixlist adds required default linker vars if not added by user + compose(ld, llist[L_ARGS], llist[L_FILES], append(ihxFile, 0)); + + if (callsys(av)) + errcnt++; + } // end: non-ihx input file handling + + // ihxcheck (test for multiple writes to the same ROM address) + if (!Kflag) { + compose(ihxcheck, ihxchecklist, append(ihxFile, 0), 0); + if (callsys(av)) + errcnt++; + } + + // No need to makebin (.ihx -> .gb [or other rom_extension]) if .ihx is final target + if (!target_is_ihx) + { + if(errcnt == 0) + { + // makebin - use output filename unless there is a post-process step + if (strlen(postproc) == 0) + sprintf(binFile, "%s", outfile); + else + sprintf(binFile, "%s", path_newext(outfile, EXT_ROM)); + + compose(mkbin, mkbinlist, append(ihxFile, 0), append(binFile, 0)); + if (callsys(av)) + errcnt++; + + // post-process step (such as makecom), if applicable + if ((strlen(postproc) != 0) && (errcnt == 0)) { + compose(postproc, append(binFile, 0), append(outfile, 0), 0); + if (callsys(av)) + errcnt++; + } + } + } + } + rm(rmlist); + if (verbose > 0) + fprintf(stderr, "\n"); + return errcnt ? EXIT_FAILURE : EXIT_SUCCESS; +} + + + +// Check whether string "arg" has "searchkey" at the first possible +// occurrence of searchkey's starting character in arg (typically "." or "_") +static bool arg_has_searchkey(char * arg, char * searchkey) { + char * str_start = strchr(arg, searchkey[0]); + + if (str_start) + return (strncmp(str_start, searchkey, strlen(searchkey)) == 0); + else + return false; +} + + +// Adds linker default required vars if not present (defined by user) +// Uses data from targets.c for per port/platform defaults +static void Fixllist() +{ + int c; + + // Iterate through linker list entries + if(llist[L_ARGS]) { + List b = llist[L_ARGS]; + do { + b = b->link; + // Only -g and -b settings are supported at this time + if(b->str[1] == 'g' || b->str[1] == 'b') + { + // '-g' and '-b' now have their values separated by a space. + // That splits them into two consecutive llist items, + // so try advancing to the next item to access it's value. + // Example: b = "-g", next = ".STACK=0xE000" + if (b != llist[L_ARGS]) + b = b->link; + else + break; // end of list + + // Check current linker arg to see if any default settings are present + // If they do, flag them as present so they don't need to be added later + for (c = 0; c < llist0_defaults_len; c++) + if (arg_has_searchkey(b->str, llist0_defaults[c].searchkey)) + llist0_defaults[c].found = true; + } + } while (b != llist[L_ARGS]); + } + + // Add required default settings to the linker list if they weren't found + for (c = 0; c < llist0_defaults_len; c++) + if (llist0_defaults[c].found == false) { + // Add the entry to the linker llist[L_ARGS], flag first then value + llist[L_ARGS] = append(llist0_defaults[c].addflag, llist[L_ARGS]); + llist[L_ARGS] = append(llist0_defaults[c].addvalue, llist[L_ARGS]); + } +} + + +/* alloc - allocate n bytes or die */ +void *alloc(int n) { + static char *avail, *limit; + + n = (n + sizeof(char *) - 1)&~(sizeof(char *) - 1); + if (n >= limit - avail) { + avail = malloc(n + 4 * 1024); + assert(avail); + limit = avail + n + 4 * 1024; + } + avail += n; + return avail - n; +} + + +/* basepath - return base name for name, e.g. /usr/drh/foo.c => foo */ +char *basepath(char *name) { + char *s, *b, *t = 0; + + for (b = s = name; *s; s++) + if (*s == '/' || *s == '\\') { + b = s + 1; + t = 0; + } + else if (*s == '.') + t = s; + s = strsave(b); + if (t) + s[t - b] = 0; + return s; +} + +// path_stripext - return a new string of path [name] with extension removed +// e.g. /usr/drh/foo.c => /usr/drh/foo +char *path_stripext(char *name) { + char * copy_str = strsave(name); + char * end_str = copy_str + strlen(copy_str); + + // Work from end of string backward, + // truncate string at first "." char + while (end_str > copy_str) { + if (*end_str == '.') { + *end_str = '\0'; + break; + } + end_str--; + } + return copy_str; +} + + +// path_newext - return a new string of path [name] with extension replaced +// e.g. /usr/drh/foo.c => /usr/drh/foo +char *path_newext(char *name, char *new_ext) { + return stringf("%s%s", path_stripext(name), new_ext); +} + + +// Check if an extension matches the end of a filename +int matches_ext(const char * filename, const char * ext) +{ + // Only test match if filename is larger than extension + // Then check the end of the filename for [ext] length of chars + if (strlen(filename) >= strlen(ext)) + return strncmp(filename + strlen(filename) - strlen(ext), ext, strlen(ext)) == 0; + else return 0; +} + + +#ifndef WIN32 +#define _P_WAIT 0 + +static int _spawnvp(int mode, const char *cmdname, char *argv[]) { + int status; + pid_t pid, n; + + switch (pid = fork()) { + case -1: + fprintf(stderr, "%s: no more processes\n", progname); + return 100; + case 0: + execv(cmdname, argv); + fprintf(stderr, "%s: ", progname); + perror(cmdname); + fflush(stdout); + exit(100); + } + while ((n = wait(&status)) != pid && n != -1) + ; + if (n == -1) + status = -1; + if (status & 0377) { + fprintf(stderr, "%s: fatal error in %s\n", progname, cmdname); + status |= 0400; + } + return (status >> 8) & 0377; +} +#endif + +/* removes quotes from src and stores it on dst */ +void removeQuotes(char* src, char* dst) +{ + while(*src != '\0') + { + if(*src != '\"') + { + if(*dst != *src) + *(dst) = *src; + dst ++; + } + src ++; + } + if(*dst != '\0') + *dst = '\0'; +} + +/* turns "C:\Users\Zalo\Desktop\gb\gbdk 2020\build\gbdk\"bin/sdcpp + into "C:\Users\Zalo\Desktop\gb\gbdk 2020\build\gbdk\bin/sdcpp" */ +void fixQuotes(char* str) +{ + while(*str != '\"' && *str != '\0') + str ++; + + char* src = str; + if(*src == '\"') + { + *(str ++) = '\"'; + while(*src != '\0') + { + if(*src != '\"') + *(str ++) = *src; + src ++; + } + *(str ++) = '\"'; + *(str ++) = '\0'; + } +} + +/* callsys - execute the command described by av[0...], return status */ +static int callsys(char **av) { + int i, status = 0; + static char **argv; + static int argc; + + for (i = 0; av[i] != NULL; i++) + ; + if (i + 1 > argc) { + argc = i + 1; + if (argv == NULL) + argv = malloc(argc * sizeof *argv); + else { + char **argv0 = argv; + argv = realloc(argv0, argc * sizeof *argv); + if (argv == NULL) + free(argv0); + } + assert(argv); + } + for (i = 0; status == 0 && av[i] != NULL; ) { + int j = 0; + char *s; + for (; av[i] != NULL && (s = strchr(av[i], '\n')) == NULL; i++) + argv[j++] = av[i]; + if (s != NULL) { + if (s > av[i]) + argv[j++] = stringf("%.*s", s - av[i], av[i]); + if (s[1] != '\0') + av[i] = s + 1; + else + i++; + } + argv[j] = NULL; + if (verbose > 0) { + int k; + fprintf(stderr, "%s", argv[0]); + for (k = 1; argv[k] != NULL; k++) + fprintf(stderr, " %s", argv[k]); + fprintf(stderr, "\n"); + } + if (verbose < 2) + { + char argv_0_no_quotes[256]; + removeQuotes(argv[0], argv_0_no_quotes); + for(char** it = argv; *it != 0; it ++) + { +#ifdef __WIN32__ + fixQuotes(*it); //On windows quotes must be kept, and fixed +#else + removeQuotes(*it, *it); //On macos, quotes must be fully removed from args +#endif + } + //For future reference: + //_spawnvp requires _FileName to not have quotes + //_Arguments must have quotes on windows, but not in macos + //Quoted strings must begin and end with quotes, no quotes in the middle + status = _spawnvp(_P_WAIT, argv_0_no_quotes, argv); + } + if (status == -1) { + fprintf(stderr, "%s: ", progname); + perror(argv[0]); + } + } + return status; +} + +/* concat - return concatenation of strings s1 and s2 */ +char *concat(const char *s1, const char *s2) { + int n = strlen(s1); + char *s = alloc(n + strlen(s2) + 1); + + strcpy(s, s1); + strcpy(s + n, s2); + return s; +} + +/* compose - compose cmd into av substituting a, b, c for $1, $2, $3, resp. */ +static void compose(char *cmd[], List a, List b, List c) { + int i, j; + List lists[3]; + + lists[0] = a; + lists[1] = b; + lists[2] = c; + for (i = j = 0; cmd[i]; i++) { + char *s = strchr(cmd[i], '$'); + if (s && isdigit(s[1])) { + int k = s[1] - '0'; + assert(k >= 1 && k <= 3); + b = lists[k - 1]; + if (b) { + b = b->link; + av[j] = alloc(strlen(cmd[i]) + strlen(b->str) - 1); + strncpy(av[j], cmd[i], s - cmd[i]); + av[j][s - cmd[i]] = '\0'; + strcat(av[j], b->str); + strcat(av[j++], s + 2); + while (b != lists[k - 1]) { + b = b->link; + assert(j < ac); + av[j++] = b->str; + }; + } + } + else if (*cmd[i]) { + assert(j < ac); + av[j++] = cmd[i]; + } + } + av[j] = NULL; +} + +/* error - issue error msg according to fmt, bump error count */ +static void error(char *fmt, char *msg) { + fprintf(stderr, "%s: ", progname); + fprintf(stderr, fmt, msg); + fprintf(stderr, "\n"); + errcnt++; +} + +/* exists - if `name' readable return its path name or return null */ +static char *exists(char *name) { + List b; + + if ((name[0] == '/' || name[0] == '\\' || name[2] == ':') + && access(name, 4) == 0) + return name; + if (!(name[0] == '/' || name[0] == '\\' || name[2] == ':') + && (b = lccinputs)) + do { + b = b->link; + if (b->str[0]) { + char buf[1024]; + sprintf(buf, "%s/%s", b->str, name); + if (access(buf, 4) == 0) + return strsave(buf); + } + else if (access(name, 4) == 0) + return name; + } while (b != lccinputs); + if (verbose > 1) + return name; + return 0; +} + +/* first - return first component in semicolon separated list */ +static char *first(char *list) { + char *s = strchr(list, ';'); + + if (s) { + char buf[1024]; + size_t len = s - list; + if(len >= sizeof(buf)) len = sizeof(buf)-1; + strncpy(buf, list, len); + buf[len] = '\0'; + return strsave(buf); + } + else + return list; +} + +/* filename - process file name argument `name', return status */ +static int filename(char *name, char *base) { + int status = 0; + static char *stemp, *itemp; + + if (base == 0) + base = basepath(name); + + // Handle all available suffixes except .gb (last in list) + switch (suffix(name, suffixes, 5)) { + case 0: /* C source files */ + { + char *ofile; + if ((cflag || Sflag) && outfile) + ofile = outfile; + else if (cflag) + ofile = concat(base, EXT_O); + else if (Sflag) { + // When compiling to asm only, set outfile as .asm + ofile = concat(base, EXT_ASM); + } + else + { + ofile = tempname(EXT_O); + + char* ofileBase = basepath(ofile); + rmlist = append(stringf("%s/%s%s", tempdir, ofileBase, EXT_ASM), rmlist); + rmlist = append(stringf("%s/%s%s", tempdir, ofileBase, ".lst"), rmlist); + rmlist = append(stringf("%s/%s%s", tempdir, ofileBase, ".sym"), rmlist); + rmlist = append(stringf("%s/%s%s", tempdir, ofileBase, ".adb"), rmlist); + } + + compose(com, clist, append(name, 0), append(ofile, 0)); + status = callsys(av); + if (!find(ofile, llist[L_FILES])) + llist[L_FILES] = append(ofile, llist[L_FILES]); + } + break; + case 2: /* assembly language files */ + if (Eflag) + break; + if (!Sflag) { + char *ofile; + if (cflag && outfile) + ofile = outfile; + else if (cflag) + ofile = concat(base, EXT_O); + else + ofile = tempname(EXT_O); + compose(as, alist, append(name, 0), append(ofile, 0)); + status = callsys(av); + if (!find(ofile, llist[L_FILES])) + llist[L_FILES] = append(ofile, llist[L_FILES]); + } + break; + case 3: /* object files */ + if (!find(name, llist[L_FILES])) + llist[L_FILES] = append(name, llist[L_FILES]); + break; + case 4: // .ihx files + // Apply "name" as .ihx file (there can be only one as input) + strncpy(ihxFile, name, sizeof(ihxFile) - 1); + break; + default: + if (Eflag) { + compose(cpp, plist, append(name, 0), 0); + status = callsys(av); + } + llist[L_FILES] = append(name, llist[L_FILES]); + break; + } + if (status) + errcnt++; + return status; +} + +/* help - print help message */ +static void help(void) { + static char *msgs[] = { +"", " [ option | file ]...\n", +" except for -l, options are processed left-to-right before files\n", +" unrecognized options are taken to be linker options\n", +"-A warn about nonANSI usage; 2nd -A warns more\n", +"-b emit expression-level profiling code; see bprint(1)\n", +#ifdef sparc +"-Bstatic -Bdynamic specify static or dynamic libraries\n", +#endif +"-Bdir/ use the compiler named `dir/rcc'\n", +"-c compile only\n", +"-dn set switch statement density to `n'\n", +"-debug Turns on --debug for compiler, -y (.cdb) and -j (.noi) for linker\n", +"-Dname -Dname=def define the preprocessor symbol `name'\n", +"-E run only the preprocessor on the named C programs and unsuffixed files\n", +"-g produce symbol table information for debuggers\n", +"-help or -? print this message\n", +"-Idir add `dir' to the beginning of the list of #include directories\n", +"-K don't run ihxcheck test on linker ihx output\n", +"-lx search library `x'\n", +"-m select port and platform: \"-m[port]:[plat]\" ports:sm83,z80 plats:ap,duck,gb,sms,gg\n", +"-N do not search the standard directories for #include files\n", +"-n emit code to check for dereferencing zero pointers\n", +"-no-crt do not auto-include the gbdk crt0.o runtime in linker list\n", +"-no-libs do not auto-include the gbdk libs in linker list\n", +"-O is ignored\n", +"-o file leave the output in `file'\n", +"-P print ANSI-style declarations for globals\n", +"-p -pg emit profiling code; see prof(1) and gprof(1)\n", +"-S compile to assembly language\n", +"-autobank auto-assign banks set to 255 (bankpack)\n" +#ifdef linux +"-static specify static libraries (default is dynamic)\n", +#endif +"-t -tname emit function tracing calls to printf or to `name'\n", +"-target name is ignored\n", +"-tempdir=dir place temporary files in `dir/'", "\n" +"-Uname undefine the preprocessor symbol `name'\n", +"-v show commands as they are executed; 2nd -v suppresses execution\n", +"-w suppress warnings\n", +"-Woarg specify system-specific `arg'\n", +"-W[pfablim]arg pass `arg' to the preprocessor, compiler, assembler, bankpack, linker, ihxcheck, or makebin\n", + 0 }; + int i; + char *s; + + msgs[0] = progname; + for (i = 0; msgs[i]; i++) { + fprintf(stderr, "%s", msgs[i]); + if (strncmp("-tempdir", msgs[i], 8) == 0 && tempdir) + fprintf(stderr, "; default=%s", tempdir); + } +#define xx(v) if ((s = getenv(#v))) fprintf(stderr, #v "=%s\n", s) + xx(LCCINPUTS); + xx(LCCDIR); +#ifdef WIN32 + xx(include); + xx(lib); +#endif +#undef xx +} + +/* initinputs - if LCCINPUTS or include is defined, use them to initialize various lists */ +static void initinputs(void) { + char *s = getenv("LCCINPUTS"); + List list, b; + + if (s == 0 || (s = inputs)[0] == 0) + s = "."; + if (s) { + lccinputs = path2list(s); + b = lccinputs; + if (b) + do { + b = b->link; + if (strcmp(b->str, ".") != 0) { + ilist = append(concat("-I", b->str), ilist); + if (strstr(com[1], "win32") == NULL) + llist[L_ARGS] = append(concat("-L", b->str), llist[L_ARGS]); + } + else + b->str = ""; + } while (b != lccinputs); + } +#ifdef WIN32 + if (list = b = path2list(getenv("include"))) + do { + b = b->link; + ilist = append(stringf("-I\"%s\"", b->str), ilist); + } while (b != list); +#endif +} + +/* interrupt - catch interrupt signals */ +static void interrupt(int n) { + rm(rmlist); + exit(n = 100); +} + +/* opt - process option in arg */ +static void opt(char *arg) { + switch (arg[1]) { /* multi-character options */ + case 'W': /* -Wxarg */ + if (arg[2] && arg[3]) + switch (arg[2]) { + case 'o': + if (option(&arg[3])) + return; + break; + case 'p': /* Preprocessor */ + clist = append(&arg[3], clist); + return; + case 'f': /* Compiler */ + if (strcmp(&arg[3], "-C") || option("-b")) { + clist = append(&arg[3], clist); + return; + } + break; /* and fall thru */ + case 'a': /* Assembler */ + alist = append(&arg[3], alist); + return; + case 'i': /* ihxcheck arg list */ + ihxchecklist = append(&arg[3], ihxchecklist); + return; + case 'b': /* auto bankpack_flags arg list */ + bankpack_flags = append(&arg[3], bankpack_flags); + // -Wb-ext=[.some-extension] + // If bankpack is going to rewrite input object files to a new extension + // then save that extension for rewriting the linker list (llist[L_FILES]) + if (strstr(&arg[3], "-ext=") != NULL) { + if (arg[8]) { + sprintf(bankpack_newext, "%s", &arg[8]); + } + } + return; + case 'l': /* Linker */ + if(arg[4] == 'y' && (arg[5] == 't' || arg[5] == 'o' || arg[5] == 'a' || arg[5] == 'p') && (arg[6] != '\0' && arg[6] != ' ')) + goto makebinoption; //automatically pass -yo -ya -yt -yp options to makebin (backwards compatibility) + { + // If using linker file for sdldgb (-f file[.lk]). + // Starting at arg[5] should be name of the linkerfile + if ((arg[4] == 'f') && (arg[5])) { + // Items in llist[L_LKFILES] get added to llist[L_FILES] right before the linker is called. + // That avoids sending to bankpack with the "-f" flag mixed in with the names of the .o object files. + llist[L_LKFILES] = append(stringf(&arg[5]), llist[L_LKFILES]); + } else { + //sdldgb requires spaces between -k and the path + llist[L_ARGS] = append(stringf("%c%c", arg[3], arg[4]), llist[L_ARGS]); //splitting the args into 2 works on Win and Linux + if (arg[5]) { + llist[L_ARGS] = append(&arg[5], llist[L_ARGS]); // Add filename separately if present + } + } + } + return; + case 'm': /* Makebin */ + makebinoption:{ + char *tmp = malloc(256); + char *tmp2 = malloc(256); + tmp2[0] = '\0'; // Zero out second arg by default + if (arg[4] == 'y') { + sprintf(tmp, "%c%c%c", arg[3], arg[4], arg[5]); //-yo -ya -yt -yl -yk -yn -yp + if (!(arg[5] == 'c' || arg[5] == 'C' || arg[5] == 's' || arg[5] == 'S' || arg[5] == 'j' || arg[5] == 'p')) // Don't add second arg for -yc -yC -ys -yS -yj + sprintf(tmp2, "%s", &arg[6]); + // -yp of SDCC 4.1.0's makebin erroneously does not use a space between flag and it's value + // So append trailing values to first arg that would otherwise go in the second arg + if (arg[5] == 'p') + sprintf(tmp, "%s", &arg[3]); + + // If MBC option is present for makebin (-Wl-yt <n> or -Wm-yt <n>) then make a copy for bankpack to use + if (arg[5] == 't') + bankpack_flags = append(&arg[3], bankpack_flags); + } else if ((arg[4] == 'x') && arg[5] && arg[6]) { + // SMS options + // Print "-" plus first two option chars into first arg + // and any trailing option chars into a separate arg + sprintf(tmp, "%c%c%c", arg[3], arg[4], arg[5]); //-xo -xj -xv + if(arg[6]) + sprintf(tmp2, "%s", &arg[6]); + } else { + sprintf(tmp, "%c%c", arg[3], arg[4]); //-s + if(arg[5]) + sprintf(tmp2, "%s", &arg[5]); + } + mkbinlist = append(tmp, mkbinlist); + if (tmp2[0] != '\0') // Only append second argument if it's populated + mkbinlist = append(tmp2, mkbinlist); + }return; + } + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'd': /* -dn */ + if (strcmp(arg, "-debug") == 0) { + // Load default debug options + clist = append("--debug", clist); // Debug for sdcc compiler + llist[L_ARGS] = append("-y", llist[L_ARGS]); // Enable .cdb output for sdldgb linker + llist[L_ARGS] = append("-j", llist[L_ARGS]); // Enable .noi output + return; + } + + arg[1] = 's'; + clist = append(arg, clist); + return; + case 't': /* -t -tname -tempdir=dir */ + if (strncmp(arg, "-tempdir=", 9) == 0) + tempdir = arg + 9; + else + clist = append(arg, clist); + return; + case 'p': /* -p -pg */ + if (option(arg)) + clist = append(arg, clist); + else + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'D': /* -Dname -Dname=def */ + case 'U': /* -Uname */ + case 'I': /* -Idir */ + clist = append(arg, clist); + return; + case 'K': + Kflag++; + return; + case 'a': + if (strcmp(arg, "-autobank") == 0) { + autobankflag++; + return; + } + case 'n': + if (strcmp(arg, "-no-crt") == 0) { + option(arg); // Clear crt0 entry in linker compose string + return; + } + else if (strcmp(arg, "-no-libs") == 0) { + option(arg); // Clear libs entry in linker compose string + return; + } + case 'B': /* -Bdir -Bstatic -Bdynamic */ +#ifdef sparc + if (strcmp(arg, "-Bstatic") == 0 || strcmp(arg, "-Bdynamic") == 0) + llist[L_FILES] = append(arg, llist[L_FILES]); + else +#endif + { + static char *path; + if (path) + error("-B overwrites earlier option", 0); + path = arg + 2; + if (strstr(com[1], "win32") != NULL) + com[0] = concat(replace(path, '/', '\\'), concat("rcc", EXT_IHX)); + else + com[0] = concat(path, "rcc"); + if (path[0] == 0) + error("missing directory in -B option", 0); + } + return; + case 'h': + if (strcmp(arg, "-help") == 0) { + static int printed = 0; + case '?': + if (!printed) + help(); + printed = 1; + return; + } +#ifdef linux + case 's': + if (strcmp(arg, "-static") == 0) { + if (!option(arg)) + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + } +#endif + } + if (arg[2] == 0) + switch (arg[1]) { /* single-character options */ + case 'S': // Requested compile to assembly only + Sflag++; + option(arg); // Update composing the compile stage, use of -S instead of -c + return; + case 'O': + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'A': case 'n': case 'w': case 'P': + clist = append(arg, clist); + return; + case 'g': case 'b': + if (option(arg)) + clist = append(arg[1] == 'g' ? "-g2" : arg, clist); + else + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'G': + if (option(arg)) { + clist = append("-g3", clist); + llist[L_ARGS] = append("-N", llist[L_ARGS]); + } + else + fprintf(stderr, "%s: %s ignored\n", progname, arg); + return; + case 'E': + Eflag++; + return; + case 'c': + cflag++; + return; + case 'N': + if (strcmp(basepath(cpp[0]), "gcc-cpp") == 0) + clist = append("-nostdinc", clist); + include[0] = 0; + ilist = 0; + return; + case 'v': + if (verbose++ == 0) { +#if 0 + /* GBDK removed */ + if (strcmp(basepath(cpp[0]), "gcc-cpp") == 0) + clist = append(arg, clist); + clist = append(arg, clist); +#endif + fprintf(stderr, "%s %s\n", progname, rcsid); + } + return; + } + if (option(arg)) + return; + if (cflag || Sflag || Eflag) + fprintf(stderr, "%s: %s ignored\n", progname, arg); + else + llist[L_FILES] = append(arg, llist[L_FILES]); +} + + +/* replace - copy str, then replace occurrences of from with to, return the copy */ +char *replace(const char *str, int from, int to) { + char *s = strsave(str), *p = s; + + for (; (p = strchr(p, from)) != NULL; p++) + *p = to; + return s; +} + +/* rm - remove files in list */ +static void rm(List list) { + if (list) { + List b = list; + if (verbose) + fprintf(stderr, "rm"); + do { + if (verbose) + fprintf(stderr, " %s", b->str); + if (verbose < 2) + remove(b->str); + } while ((b = b->link) != list); + if (verbose) + fprintf(stderr, "\n"); + } +} + +/* strsave - return a saved copy of string str */ +char *strsave(const char *str) { + return strcpy(alloc(strlen(str) + 1), str); +} + +/* stringf - format and return a string */ +char *stringf(const char *fmt, ...) { + char buf[1024]; + va_list ap; + int n; + + va_start(ap, fmt); + n = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + return strsave(buf); +} + +/* suffix - if one of tails[0..n-1] holds a proper suffix of name, return its index */ +int suffix(char *name, char *tails[], int n) { + int i, len = strlen(name); + + for (i = 0; i < n; i++) { + char *s = tails[i], *t; + for (; (t = strchr(s, ';')); s = t + 1) { + int m = t - s; + if (len > m && strncmp(&name[len - m], s, m) == 0) + return i; + } + if (*s) { + int m = strlen(s); + if (len > m && strncmp(&name[len - m], s, m) == 0) + return i; + } + } + return SUFX_NOMATCH; +} + +/* tempname - generate a temporary file name in tempdir with given suffix */ +char *tempname(char *suffix) { + static int n; + char *name = stringf("%s/lcc%d%d%s", tempdir, getpid(), n++, suffix); + + if (strstr(com[1], "win32") != NULL) + name = replace(name, '/', '\\'); + rmlist = append(name, rmlist); + return name; +} + + +// Performs the autobanking stage +// +// Should be called prior to doing compose() for the linker +// +static void handle_autobanking(void) { + + // bankpack will be populated if supported by active port:platform + if (bankpack[0][0] != '\0') { + + char * bankpack_linkerfile_name = tempname(EXT_LK); + rmlist = append(bankpack_linkerfile_name, rmlist); // Delete the linkerfile when done + // Always use a linkerfile when using bankpack through lcc + // Writes all input object files out to [bankpack_linkerfile_name] + bankpack_flags = append(stringf("%s%s","-lkout=", bankpack_linkerfile_name), bankpack_flags); + + // Add linkerfile entries (usually *.lk) to the bankpack arg list if any are present + bankpack_flags = list_add_to_another(bankpack_flags, llist[L_LKFILES], "-lkin=", NULL); + + // Prepare the bankpack command line, then execute it + compose(bankpack, bankpack_flags, llist[L_FILES], 0); + if (callsys(av)) + errcnt++; + + // Clear out the objects file and linkerfiles from their lists + // Then replace them with the filename passed to bankpack for "-lkout=" + llist[L_FILES] = list_remove_all(llist[L_FILES]); + llist[L_LKFILES] = list_remove_all(llist[L_LKFILES]); + llist[L_LKFILES] = append(stringf("%s", bankpack_linkerfile_name), llist[L_LKFILES]); + } + else + fprintf(stderr, "Warning: bankpack enabled but not supported by active port:platform\n"); +}
\ No newline at end of file diff --git a/gbdk/gbdk-support/lcc/lcc.sln b/gbdk/gbdk-support/lcc/lcc.sln new file mode 100644 index 00000000..ab150a4e --- /dev/null +++ b/gbdk/gbdk-support/lcc/lcc.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lcc", "lcc.vcxproj", "{2B573C10-5A15-4E77-8F33-804EABADC75B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2B573C10-5A15-4E77-8F33-804EABADC75B}.Debug|x64.ActiveCfg = Debug|x64 + {2B573C10-5A15-4E77-8F33-804EABADC75B}.Debug|x64.Build.0 = Debug|x64 + {2B573C10-5A15-4E77-8F33-804EABADC75B}.Debug|x86.ActiveCfg = Debug|Win32 + {2B573C10-5A15-4E77-8F33-804EABADC75B}.Debug|x86.Build.0 = Debug|Win32 + {2B573C10-5A15-4E77-8F33-804EABADC75B}.Release|x64.ActiveCfg = Release|x64 + {2B573C10-5A15-4E77-8F33-804EABADC75B}.Release|x64.Build.0 = Release|x64 + {2B573C10-5A15-4E77-8F33-804EABADC75B}.Release|x86.ActiveCfg = Release|Win32 + {2B573C10-5A15-4E77-8F33-804EABADC75B}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/gbdk/gbdk-support/lcc/lcc.vcxproj b/gbdk/gbdk-support/lcc/lcc.vcxproj new file mode 100644 index 00000000..5881bc65 --- /dev/null +++ b/gbdk/gbdk-support/lcc/lcc.vcxproj @@ -0,0 +1,148 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <ItemGroup> + <ClCompile Include="gb.c" /> + <ClCompile Include="lcc.c" /> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{2B573C10-5A15-4E77-8F33-804EABADC75B}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>lcc</RootNamespace> + <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>$(SolutionDir)..\build\gbdk\bin\</OutDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>WIN32;__WIN32__;_DEBUG;_CONSOLE;%(PreprocessorDefinitions);WIN32;__WIN32__</PreprocessorDefinitions> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions);WIN32;__WIN32__</PreprocessorDefinitions> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <PrecompiledHeader> + </PrecompiledHeader> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>WIN32;__WIN32__;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);WIN32;__WIN32__</PreprocessorDefinitions> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <PrecompiledHeader> + </PrecompiledHeader> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions);WIN32;__WIN32__</PreprocessorDefinitions> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/gbdk/gbdk-support/lcc/lcc.vcxproj.filters b/gbdk/gbdk-support/lcc/lcc.vcxproj.filters new file mode 100644 index 00000000..828cb55b --- /dev/null +++ b/gbdk/gbdk-support/lcc/lcc.vcxproj.filters @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="lcc\gb.c" /> + <ClCompile Include="lcc\lcc.c" /> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/gbdk/gbdk-support/lcc/list.c b/gbdk/gbdk-support/lcc/list.c new file mode 100644 index 00000000..f58005f1 --- /dev/null +++ b/gbdk/gbdk-support/lcc/list.c @@ -0,0 +1,193 @@ +// list.c + +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +//#include <assert.h> +//#include <ctype.h> +//#include <signal.h> + +#include "list.h" + +// From lcc.c +extern void *alloc(int); +extern char *strsave(const char *str); +extern char *stringf(const char *fmt, ...); +extern char *path_stripext(char *name); +extern int matches_ext(const char * filename, const char * ext); +extern int verbose; + + + +/* append - append a node with string str onto list, return new list */ +List append(char *str, List list) { + List p = alloc(sizeof *p); + + p->str = str; + if (list) { + p->link = list->link; + list->link = p; + } + else + p->link = p; + return p; +} + + +/* find - find 1st occurrence of str in list, return list node or 0 */ +List find(char *str, List list) { + List b; + + b = list; + if (b) + do { + if (strcmp(str, b->str) == 0) + return b; + } while ((b = b->link) != list); + return 0; +} + + +/* path2list - convert a colon- or semicolon-separated list to a list */ +List path2list(const char *path) { + List list = NULL; + char sep = ':'; + + if (path == NULL) + return NULL; + if (strchr(path, ';')) + sep = ';'; + while (*path) { + char *p, buf[512]; + p = strchr(path, sep); + if (p) { + size_t len = p - path; + if(len >= sizeof(buf)) len = sizeof(buf)-1; + strncpy(buf, path, len); + buf[len] = '\0'; + } + else { + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + } + if (!find(buf, list)) + list = append(strsave(buf), list); + if (p == 0) + break; + path = p + 1; + } + return list; +} + + +// Replace extensions for filenames in a list +void list_rewrite_exts(List list_in, char * ext_match, char * ext_new) +{ + char * filepath_old; + + // Iterate through list and replace file extensions + if (list_in) { + List list_t = list_in; + do { + if (list_t->str) { + // Check to see if filname has desired extension + if (matches_ext(list_t->str, ext_match)) { + + // Save a copy to free after re-assignment + filepath_old = list_t->str; + // Create a new string with the replaced suffix (stringf() allocs) + list_t->str = stringf("%s%s", path_stripext(list_t->str), ext_new); + if (verbose > 0) fprintf(stderr,"lcc: rename link obj (from -autobank): %s -> %s\n", filepath_old, list_t->str); + } + } + // Move to next list item, exit if start of list is reached + list_t = list_t->link; + } while (list_t != list_in); + } +} + + +// Duplicate items with [ext_match] into new list items with [ext_new] +void list_duplicate_to_new_exts(List list_in, char * ext_match, char * ext_new) +{ + // List may have entries appended, cache original start + List list_start = list_in; + + // Iterate through list and replace file extensions + if (list_in) { + List list_t = list_in; + do { + if (list_t->str) { + // Check to see if filname has desired extension + if (matches_ext(list_t->str, ext_match)) { + // Make a copy with the replaced extension into the list + list_in = append(stringf("%s%s", path_stripext(list_t->str), ext_new), list_in); + if (verbose > 0) fprintf(stderr,"lcc: add to rmlist (from -autobank): %s -> %s\n", list_t->str, stringf("%s%s", path_stripext(list_t->str), ext_new)); + } + } + // Move to next list item, exit if start of list is reached + list_t = list_t->link; + } while (list_t != list_start); + } +} + + +// Remove all items from a list +List list_remove_all(List list_in) { + + // Well... the custom alloc() used for lists makes it hard to free their memory. + // Instead just set the list to NULL to erase it and let cleanup happen on program exit. + return NULL; + + /* + List list_next; + // Iterate through list and free memory + if (list_in) { + List list_t = list_in->link; // Advance to next item + list_in->link = NULL; // Break list to create a stopping point + + do { + // Copy next link before freeing the current item + list_next = list_t->link; + if (list_t) { + free(list_t); + list_t = NULL; + } + // Move to next list item, exit if start of list is reached + list_t = list_next; + } while (list_t && list_t->link); + } + return NULL; + */ +} + + +// Copies all items from [list_src] to [list_dest] +// * optionally prefix items from [list_src] with [str_prefix] +// * optionally append() a separate [str_add_before] for each item in [list_src] (use for space separated flags) +List list_add_to_another(List list_dest, List list_src, char * str_prefix, char * str_add_before) { + + if (list_src) { + List list_t = list_src->link; // Start at first list item (list usually points to END) + + // Iterate through [list_src] and copy into to [list_dest], exit once start is reached again + do { + if (list_t->str) + // Add str_add_before string as a separate item if present + if (str_add_before) + list_dest = append( stringf("%s", str_add_before), list_dest); + + // pre-pend string prefix if present + if (str_prefix) + list_dest = append( stringf("%s%s", str_prefix, list_t->str), list_dest); + else + list_dest = append( stringf("%s", list_t->str), list_dest); + // Move to next list item + list_t = list_t->link; + } while (list_t != list_src); + } + + return list_dest; // Return updated list +} diff --git a/gbdk/gbdk-support/lcc/list.h b/gbdk/gbdk-support/lcc/list.h new file mode 100644 index 00000000..cac291fc --- /dev/null +++ b/gbdk/gbdk-support/lcc/list.h @@ -0,0 +1,26 @@ +// list.h + +#include <stdbool.h> + +#ifndef _LCC_LIST_H +#define _LCC_LIST_H + + +typedef struct list *List; +struct list { /* circular list nodes: */ + char *str; /* option or file name */ + List link; /* next list element */ +}; + +List append(char *, List); +List find(char *, List); + +List path2list(const char *); + +void list_rewrite_exts(List, char *, char *); +void list_duplicate_to_new_exts(List, char *, char *); +List list_remove_all(List); +List list_add_to_another(List, List, char *, char *); + + +#endif // _LCC_LIST_H diff --git a/gbdk/gbdk-support/lcc/targets.c b/gbdk/gbdk-support/lcc/targets.c new file mode 100644 index 00000000..7e0d17b1 --- /dev/null +++ b/gbdk/gbdk-support/lcc/targets.c @@ -0,0 +1,143 @@ +// targets.c + +#include "gb.h" +#include "targets.h" + +// Default Linker list arguments +// +// These get processed in Fixllist() +// +// NOTE: Only -g and -b entries are supported by Fixllist() right now + +// Game Boy / Analogue Pocket / Megaduck +arg_entry llist0_defaults_gb[] = { + {.searchkey= "_shadow_OAM=", .addflag= "-g",.addvalue= "_shadow_OAM=0xC000", .found= false}, + {.searchkey= ".STACK=", .addflag= "-g",.addvalue= ".STACK=0xE000", .found= false}, + {.searchkey= ".refresh_OAM=",.addflag= "-g",.addvalue= ".refresh_OAM=0xFF80",.found= false}, + {.searchkey= "_DATA=", .addflag= "-b",.addvalue= "_DATA=0xC0A0", .found= false}, + {.searchkey= "_CODE=", .addflag= "-b",.addvalue= "_CODE=0x0200", .found= false}, +}; + +// SMS / GG +arg_entry llist0_defaults_sms[] = { + {.searchkey= "_shadow_OAM=", .addflag= "-g",.addvalue= "_shadow_OAM=0xC000", .found= false}, + {.searchkey= ".STACK=", .addflag= "-g",.addvalue= ".STACK=0xDFF0", .found= false}, + {.searchkey= "_DATA=", .addflag= "-b",.addvalue= "_DATA=0xC0C0", .found= false}, + {.searchkey= "_CODE=", .addflag= "-b",.addvalue= "_CODE=0x0100", .found= false}, +}; + +// MSX +arg_entry llist0_defaults_msxdos[] = { + {.searchkey= "_DATA=", .addflag= "-b",.addvalue= "_DATA=0x8000", .found= false}, + {.searchkey= "_CODE=", .addflag= "-b",.addvalue= "_CODE=0x0100", .found= false}, +}; + +// Port/Platform specific settings +// +// $1 are extra parameters passed using -W +// $2 is the list of objects passed as parameters +// $3 is the output file +CLASS classes[] = { + // GB + { "sm83", // port + "gb", // plat + "gb", // default_plat + EXT_GB, // ROM file extension + "%cpp% %cppdefault% -DINT_16_BITS $1 $2 $3", + "%includedefault%", + "%com% %comdefault% -Wa%asdefault% -DINT_16_BITS $1 %comflag% $2 -o $3", + "%as_gb% %asdefault% $1 $3 $2", + "%bankpack% $1 $2", + "%ld_gb% -n -i $1 %libs_include% $3 %crt0dir% $2", + "%ihxcheck% $2 $1", + "%mkbin% -yN -Z $1 $2 $3", // -yN: Don't paste in the Nintendo logo bytes for gameboy and clones (-Z) + "", + llist0_defaults_gb, ARRAY_LEN(llist0_defaults_gb), + }, + // Analogue Pocket + { "sm83", // port + "ap", // plat + "ap", // default_plat + EXT_AP, // ROM file extension + "%cpp% %cppdefault% -DINT_16_BITS $1 $2 $3", + "%includedefault%", + "%com% %comdefault% -Wa%asdefault% -DINT_16_BITS $1 %comflag% $2 -o $3", + "%as_gb% %asdefault% $1 $3 $2", + "%bankpack% $1 $2", + "%ld_gb% -n -i $1 %libs_include% $3 %crt0dir% $2", + "%ihxcheck% $2 $1", + "%mkbin% -yN -Z $1 $2 $3", // -yN: Don't paste in the Nintendo logo bytes for gameboy and clones (-Z) + "", + llist0_defaults_gb, ARRAY_LEN(llist0_defaults_gb), // Use GB linker list defaults + }, + // Megaduck + { "sm83", // port + "duck", // plat + "duck", // default_plat + EXT_DUCK, // ROM file extension + "%cpp% %cppdefault% -DINT_16_BITS $1 $2 $3", + "%includedefault%", + "%com% %comdefault% -Wa%asdefault% -DINT_16_BITS $1 %comflag% $2 -o $3", + "%as_gb% %asdefault% $1 $3 $2", + "%bankpack% $1 $2", + "%ld_gb% -n -i $1 %libs_include% $3 %crt0dir% $2", + "%ihxcheck% $2 $1", + "%mkbin% -yN -Z $1 $2 $3", // -yN: Don't paste in the Nintendo logo bytes for gameboy and clones (-Z) + "", + llist0_defaults_gb, ARRAY_LEN(llist0_defaults_gb), // Use GB linker list defaults + }, + + // SMS + { "z80", // port + "sms", // plat + "sms", // default_plat + EXT_SMS, // ROM file extension + "%cpp% %cppdefault% -DINT_16_BITS $1 $2 $3", + "%includedefault%", + "%com% %comdefault% -Wa%asdefault% -DINT_16_BITS $1 %comflag% $2 -o $3", + "%as_z80% %asdefault% $1 $3 $2", + "%bankpack% -plat=sms $1 $2", + "%ld_z80% -a sms -n -i $1 %libs_include% $3 %crt0dir% $2", + "%ihxcheck% $2 $1", + "%mkbin% -S -xj 4 $1 $2 $3", + "", + llist0_defaults_sms, ARRAY_LEN(llist0_defaults_sms), + }, + // GG + { "z80", // port + "gg", // plat + "gg", // default_plat + EXT_GG, // ROM file extension + "%cpp% %cppdefault% -DINT_16_BITS $1 $2 $3", + "%includedefault%", + "%com% %comdefault% -Wa%asdefault% -DINT_16_BITS $1 %comflag% $2 -o $3", + "%as_z80% %asdefault% $1 $3 $2", + "%bankpack% -plat=sms $1 $2", + "%ld_z80% -a sms -n -i $1 %libs_include% $3 %crt0dir% $2", + "%ihxcheck% $2 $1", + "%mkbin% -S $1 $2 $3", + "", + llist0_defaults_sms, ARRAY_LEN(llist0_defaults_sms), // Use SMS linker list defaults + }, + + // MSX + { "z80", // port + "msxdos", // plat + "msxdos", // default_plat + EXT_MSXDOS, // ROM file extension + "%cpp% %cppdefault% -DINT_16_BITS $1 $2 $3", + "%includedefault%", + "%com% %comdefault% -Wa%asdefault% -DINT_16_BITS $1 %comflag% $2 -o $3", + "%as_z80% %asdefault% $1 $3 $2", + "%bankpack% -plat=sms $1 $2", + "%ld_z80% -a sms -n -i -j $1 %libs_include% $3 %crt0dir% $2", + "%ihxcheck% $2 $1", + "%mkbin% $1 $2 $3", + "%mkcom% $1 $2", + llist0_defaults_msxdos, ARRAY_LEN(llist0_defaults_msxdos), + } + +}; + +int classes_count = ARRAY_LEN(classes); + diff --git a/gbdk/gbdk-support/lcc/targets.h b/gbdk/gbdk-support/lcc/targets.h new file mode 100644 index 00000000..c355668f --- /dev/null +++ b/gbdk/gbdk-support/lcc/targets.h @@ -0,0 +1,39 @@ +// targets.h + +#include <stdbool.h> + +#ifndef _LCC_TARGETS_H +#define _LCC_TARGETS_H + +// For Linker list defaults (llist0) +typedef struct arg_entry { + char searchkey[255]; + char addflag[255]; + char addvalue[255]; + bool found; +} arg_entry; + +// For Port/Platform specific settings +typedef struct CLASS { + const char *port; + const char *plat; + const char *default_plat; + const char *rom_extension; + const char *cpp; + const char *include; + const char *com; + const char *as; + const char *bankpack; + const char *ld; + const char *ihxcheck; + const char *mkbin; + const char *postproc; + arg_entry *llist0_defaults; + int llist0_defaults_len; +} CLASS; + + +extern CLASS classes[]; +extern int classes_count; + +#endif // _TARGETS_H diff --git a/gbdk/gbdk-support/makebin/Makefile b/gbdk/gbdk-support/makebin/Makefile new file mode 100644 index 00000000..e1bf87c4 --- /dev/null +++ b/gbdk/gbdk-support/makebin/Makefile @@ -0,0 +1,30 @@ +# bankcheck (auto bank tool) makefile + +ifndef TARGETDIR +TARGETDIR = /opt/gbdk +endif + +ifeq ($(OS),Windows_NT) + BUILD_OS := Windows_NT +else + BUILD_OS := $(shell uname -s) +endif + +# Target older macOS version than whatever build OS is for better compatibility +ifeq ($(BUILD_OS),Darwin) + export MACOSX_DEPLOYMENT_TARGET=10.10 +endif + +CC = $(TOOLSPREFIX)gcc +CFLAGS = -ggdb -O -Wno-incompatible-pointer-types -DGBDKLIBDIR=\"$(TARGETDIR)\" +OBJ = makebin.o +BIN = makebin + +all: $(BIN) + +$(BIN): $(OBJ) + +clean: + rm -f *.o $(BIN) *~ + rm -f *.exe + diff --git a/gbdk/gbdk-support/makebin/makebin.c b/gbdk/gbdk-support/makebin/makebin.c new file mode 100644 index 00000000..b279a539 --- /dev/null +++ b/gbdk/gbdk-support/makebin/makebin.c @@ -0,0 +1,1022 @@ +/* + makebin - turn a .ihx file into a binary image or GameBoy format binaryimage + + Copyright (c) 2000 Michael Hope + Copyright (c) 2010 Borut Razem + Copyright (c) 2012 Noel Lemouel + Copyright (c) 2020-2021 Sebastian 'basxto' Riedel + Copyright (c) 2020 'bbbbbr' + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> + +#if defined(_WIN32) +#include <fcntl.h> +#include <io.h> +#else +#include <unistd.h> +#endif + + +typedef unsigned char BYTE; + +#define FILL_BYTE 0xff + +int +getnibble (FILE *fin) +{ + int ret; + int c = getc (fin); + + if (feof (fin) || ferror (fin)) + { + fprintf (stderr, "error: unexpected end of file.\n"); + exit (6); + } + + ret = c - '0'; + if (ret > 9) + { + ret -= 'A' - '9' - 1; + } + + if (ret > 0xf) + { + ret -= 'a' - 'A'; + } + + if (ret < 0 || ret > 0xf) + { + fprintf (stderr, "error: character %02x.\n", ret); + exit (7); + } + return ret; +} + +int +getbyte (FILE *fin, int *sum) +{ + int b = (getnibble (fin) << 4) | getnibble (fin); + *sum += b; + return b; +} + +void +usage (void) +{ + fprintf (stderr, + "makebin: convert a Intel IHX file to binary or GameBoy format binary.\n" + "Usage: makebin [options] [<in_file> [<out_file>]]\n" + "Options:\n" + " -p pack mode: the binary file size will be truncated to the last occupied byte\n" + " -s romsize size of the binary file (default: rom banks * 16384)\n" + " -Z generate GameBoy format binary file\n" + " -S generate Sega Master System format binary file\n" + " -t size skip size bytes from the beginning of the rom" + + "SMS format options (applicable only with -S option):\n" + " -xo n rom size (0xa-0x2)\n" + " -xj n set region code (3-7)\n" + //" -xc n product code (0-159999)\n" + " -xv n version number (0-15)\n" + //" -xV n SDSC version number\n" + //" -xd n SDSC date\n" + //" -xA n SDSC author pointer\n" + //" -xn n SDSC program name pointer\n" + //" -xD n SDSC description pointer\n" + + "GameBoy format options (applicable only with -Z option):\n" + " -yo n number of rom banks (default: 2) (autosize: A)\n" + " -ya n number of ram banks (default: 0)\n" + " -yt n MBC type (default: no MBC)\n" + " -yl n old licensee code (default: 0x33)\n" + " -yk cc new licensee string (default: 00)\n" + " -yn name cartridge name (default: none)\n" + " -yc GameBoy Color compatible\n" + " -yC GameBoy Color only\n" + " -ys Super GameBoy\n" + " -yS Convert .noi file named like input file to .sym\n" + " -yj set non-Japanese region flag\n" + " -yN do not copy big N validation logo into ROM header\n" + " -yp addr=value Set address in ROM to given value (address 0x100-0x1FE)\n" + "Arguments:\n" + " <in_file> optional IHX input file, '-' means stdin. (default: stdin)\n" + " <out_file> optional output file, '-' means stdout. (default: stdout)\n"); +} + +#define CART_NAME_LEN 16 + +struct gb_opt_s +{ + char cart_name[CART_NAME_LEN]; /* cartridge name buffer */ + char licensee_str[2]; /* new licensee string */ + BYTE mbc_type; /* MBC type (default: no MBC) */ + short nb_rom_banks; /* Number of rom banks (default: 2) */ + BYTE nb_ram_banks; /* Number of ram banks (default: 0) */ + BYTE licensee_id; /* old licensee code */ + BYTE is_gbc; /* 1 if GBC compatible, 2 if GBC only, false for all other*/ + BYTE is_sgb; /* True if SGB, false for all other*/ + BYTE sym_conversion; /* True if .noi file should be converted to .sym (default false)*/ + BYTE non_jp; /* True if non-Japanese region, false for all other*/ + BYTE rom_banks_autosize; /* True if rom banks should be auto-sized (default false)*/ + bool do_logo_copy; /* True if the nintendo logo should be copied into the ROM (default true) */ + BYTE address_overwrite[16]; /* For limited compatibility with very old versions */ +}; + +struct sms_opt_s +{ + BYTE rom_size; /* Doesn't have to be the real size, needed for checksum */ + BYTE region_code; /* Region code Japan/Export/International and SMS/GG */ + BYTE version; /* Game version */ +}; + +void +gb_postproc (BYTE * rom, int size, int *real_size, struct gb_opt_s *o) +{ + int i, chk; + static const BYTE gb_logo[] = + { + 0xce, 0xed, 0x66, 0x66, 0xcc, 0x0d, 0x00, 0x0b, + 0x03, 0x73, 0x00, 0x83, 0x00, 0x0c, 0x00, 0x0d, + 0x00, 0x08, 0x11, 0x1f, 0x88, 0x89, 0x00, 0x0e, + 0xdc, 0xcc, 0x6e, 0xe6, 0xdd, 0xdd, 0xd9, 0x99, + 0xbb, 0xbb, 0x67, 0x63, 0x6e, 0x0e, 0xec, 0xcc, + 0xdd, 0xdc, 0x99, 0x9f, 0xbb, 0xb9, 0x33, 0x3e + }; + + /* $0104-$0133: Nintendo logo + * If missing, an actual Game Boy won't run the ROM. + */ + + if (o->do_logo_copy) + { + memcpy (&rom[0x104], gb_logo, sizeof (gb_logo)); + } + + rom[0x144] = o->licensee_str[0]; + rom[0x145] = o->licensee_str[1]; + + /* + * 0134-0142: Title of the game in UPPER CASE ASCII. If it + * is less than 16 characters then the + * remaining bytes are filled with 00's. + */ + + /* capitalize cartridge name */ + for (i = 0; i < CART_NAME_LEN; ++i) + { + rom[0x134 + i] = toupper (o->cart_name[i]); + } + + if (o->is_gbc == 1) + { + rom[0x143] = 0x80; + } + + if (o->is_gbc == 2) + { + rom[0x143] = 0xC0; + } + + if (o->is_sgb) + { + rom[0x146] = 0x03; + } + + /* + * 0147: Cartridge type: + * 0-ROM ONLY 12-ROM+MBC3+RAM + * 1-ROM+MBC1 13-ROM+MBC3+RAM+BATT + * 2-ROM+MBC1+RAM 19-ROM+MBC5 + * 3-ROM+MBC1+RAM+BATT 1A-ROM+MBC5+RAM + * 5-ROM+MBC2 1B-ROM+MBC5+RAM+BATT + * 6-ROM+MBC2+BATTERY 1C-ROM+MBC5+RUMBLE + * 8-ROM+RAM 1D-ROM+MBC5+RUMBLE+SRAM + * 9-ROM+RAM+BATTERY 1E-ROM+MBC5+RUMBLE+SRAM+BATT + * B-ROM+MMM01 1F-Pocket Camera + * C-ROM+MMM01+SRAM FD-Bandai TAMA5 + * D-ROM+MMM01+SRAM+BATT FE - Hudson HuC-3 + * F-ROM+MBC3+TIMER+BATT FF - Hudson HuC-1 + * 10-ROM+MBC3+TIMER+RAM+BATT + * 11-ROM+MBC3 + */ + rom[0x147] = o->mbc_type; + + /* + * 0148 ROM size: + * 0 - 256Kbit = 32KByte = 2 banks + * 1 - 512Kbit = 64KByte = 4 banks + * 2 - 1Mbit = 128KByte = 8 banks + * 3 - 2Mbit = 256KByte = 16 banks + * 4 - 4Mbit = 512KByte = 32 banks + * 5 - 8Mbit = 1MByte = 64 banks + * 6 - 16Mbit = 2MByte = 128 banks + * $52 - 9Mbit = 1.1MByte = 72 banks + * $53 - 10Mbit = 1.2MByte = 80 banks + * $54 - 12Mbit = 1.5MByte = 96 banks + */ + switch (o->nb_rom_banks) + { + case 2: + rom[0x148] = 0; + break; + + case 4: + rom[0x148] = 1; + break; + + case 8: + rom[0x148] = 2; + break; + + case 16: + rom[0x148] = 3; + break; + + case 32: + rom[0x148] = 4; + break; + + case 64: + rom[0x148] = 5; + break; + + case 128: + rom[0x148] = 6; + break; + + case 256: + rom[0x148] = 7; + break; + + case 512: + rom[0x148] = 8; + break; + + default: + fprintf (stderr, "warning: unsupported number of ROM banks (%d)\n", o->nb_rom_banks); + rom[0x148] = 0; + break; + } + + /* + * 0149 RAM size: + * 0 - None + * 1 - 16kBit = 2kB = 1 bank + * 2 - 64kBit = 8kB = 1 bank + * 3 - 256kBit = 32kB = 4 banks + * 4 - 1MBit =128kB =16 banks + */ + switch (o->nb_ram_banks) + { + case 0: + rom[0x149] = 0; + break; + + case 1: + rom[0x149] = 2; + break; + + case 4: + rom[0x149] = 3; + break; + + case 16: + rom[0x149] = 4; + break; + + default: + fprintf (stderr, "warning: unsupported number of RAM banks (%d)\n", o->nb_ram_banks); + rom[0x149] = 0; + break; + } + + rom[0x14A] = o->non_jp; + + rom[0x14B] = o->licensee_id; + + for (i = 0; i < 16; i+=2) + { + if(o->address_overwrite[i] != 0xFF) + { + rom[0x0100 & o->address_overwrite[i]] = o->address_overwrite[i+1]; + // warnings for builds ported from ancient GBDK + fprintf (stderr, "caution: -yp0x01%02x=0x%02x is outdated", o->address_overwrite[i], o->address_overwrite[i+1]); + if(o->address_overwrite[i] == 0x43) + switch(o->address_overwrite[i+1]&0xC0) + { + case 0x80: + fprintf (stderr, ", please use -yc instead"); + break; + case 0xC0: + fprintf (stderr, ", please use -yC instead"); + break; + default: + o->address_overwrite[i] = 0xFF; + } + if(o->address_overwrite[i] == 0x44 || o->address_overwrite[i] == 0x45) + fprintf (stderr, ", please use -yk cc instead"); + if(o->address_overwrite[i] == 0x46) + if(o->address_overwrite[i+1] == 0x03) + fprintf (stderr, ", please use -ys instead"); + else + o->address_overwrite[i] = 0xFF; + if(o->address_overwrite[i] == 0x47) + fprintf (stderr, ", please use -yt 0x%02x instead", o->address_overwrite[i+1]); + if(o->address_overwrite[i] == 0x4A) + fprintf (stderr, ", please use -yl 0x%02x instead", o->address_overwrite[i+1]); + if(o->address_overwrite[i] == 0x4B && o->address_overwrite[i+1] == 1) + fprintf (stderr, ", please use -yj instead"); + if(o->address_overwrite[i] == 0xFF) + fprintf (stderr, ", this setting is the default"); + fprintf (stderr, ".\n"); + } + } + + /* Update complement checksum */ + chk = 0; + for (i = 0x134; i < 0x14d; ++i) + chk += rom[i]; + rom[0x014d] = (unsigned char) (0xe7 - (chk & 0xff)); + + /* Update checksum */ + chk = 0; + rom[0x14e] = 0; + rom[0x14f] = 0; + for (i = 0; i < size; ++i) + chk += rom[i]; + rom[0x14e] = (unsigned char) ((chk >> 8) & 0xff); + rom[0x14f] = (unsigned char) (chk & 0xff); + + if (*real_size < 0x150) + *real_size = 0x150; +} + +void +sms_postproc (BYTE * rom, int size, int *real_size, struct sms_opt_s *o) +{ + // based on https://www.smspower.org/Development/ROMHeader + // 0x1ff0 and 0x3ff0 are also possible, but never used + static const char tmr_sega[] = "TMR SEGA "; + short header_base = 0x7ff0; + int chk = 0; + unsigned long i; + // choose earlier positions for smaller roms + if (header_base > size) + header_base = 0x3ff0; + if (header_base > size) + header_base = 0x1ff0; + + memcpy (&rom[header_base], tmr_sega, sizeof (tmr_sega) - 1); + // configure amounts of bytes to check + switch(o->rom_size) + { + case 0xa: + default: + i = 0x1FEF; + break; + case 0xb: + i = 0x3FEF; + break; + case 0xc: + i = 0x7FEF; + break; + case 0xd: + i = 0xBFEF; + break; + case 0xe: + i = 0xFFFF; + break; + case 0xf: + i = 0x1FFFF; + break; + case 0x0: + i = 0x3FFFF; + break; + case 0x1: + i = 0x7FFFF; + break; + case 0x2: + i = 0xFFFFF; + break; + } + // calculate checksum + for(;i > 0; --i) + { + chk += rom[i]; + // 0x7FF0 - 0x7FFF is skipped + if(i == 0x8000) + i = 0x7FF0; + } + // we skipped index 0 + chk += rom[0]; + // little endian + rom[header_base + 0xa] = chk & 0xff; + rom[header_base + 0xb] = (chk>>8) & 0xff; + // game version + rom[header_base + 0xe] &= 0xF0; + rom[header_base + 0xe] |= o->version; + // rom size + rom[header_base + 0xf] = (o->region_code << 4) | o->rom_size; +} + +int +rom_autosize_grow(BYTE **rom, int test_size, int *size, struct gb_opt_s *o) +{ + int last_size = *size; + + while ((test_size > *size) && (o->nb_rom_banks <= 512)) + { + o->nb_rom_banks *= 2; + // banks work differently for mbc6, they have half the size + // but this in general ignored by -yo + *size = o->nb_rom_banks * 0x4000; + } + + if (o->nb_rom_banks > 512) + { + fprintf (stderr, "error: auto-size banks exceeded max of 512 banks.\n"); + return 0; + } + else + { + BYTE * t_rom = *rom; + *rom = realloc (*rom, *size); + if (*rom == NULL) + { + free(t_rom); + fprintf (stderr, "error: couldn't re-allocate size for larger rom image.\n"); + return 0; + } + memset (*rom + last_size, FILL_BYTE, *size - last_size); + } + + return 1; +} + +int +noi2sym (char *filename) +{ + FILE *noi, *sym; + char *nname, *sname; + //ssize_t read; + char read = ' '; + // no$gmb's implementation is limited to 32 character labels + // we can safely throw away the rest + char label[33]; + // 0x + 6 digit hex number + // -> 65536 rom banks is the maximum homebrew cartrideges support (TPP1) + char value[9]; + int name_len = strlen(filename); + int i = 0; + // copy filename's value to nname and sname + nname = malloc((name_len+1) * sizeof(char)); + strcpy (nname, filename); + sname = malloc((name_len+1) * sizeof(char)); + strcpy (sname, filename); + // change the extensions + nname[name_len-1]='i'; + nname[name_len-2]='o'; + nname[name_len-3]='n'; + sname[name_len-1]='m'; + sname[name_len-2]='y'; + sname[name_len-3]='s'; + + if (NULL == (noi = fopen (nname, "r"))) + { + fprintf (stderr, "error: can't open %s: ", nname); + perror(NULL); + return 1; + } + if (NULL == (sym = fopen (sname, "w"))) + { + fprintf (stderr, "error: can't create %s: ", sname); + perror(NULL); + return 1; + } + // write header + fprintf (sym, "; no$gmb compatible .sym file\n; Generated automagically by makebin\n"); + // iterate through .noi file + while (read != EOF && (read = fgetc(noi)) != EOF) + { + // just skip line breaks + if (read == '\r' || read == '\n') + continue; + // read first 4 chars + for (i = 0; i < 4; ++i) + { + value[i] = read; + if ((read = fgetc(noi)) == EOF || read == '\r' || read == '\n') + { + // leave for-loop + break; + } + } + // we left loop early + if (i != 4) + continue; + // only accept if line starts with this + if (strncmp(value, "DEF ", 4) == 0) + { + // read label + for (i = 0; i < 32; ++i) + { + label[i] = read; + if ((read = fgetc(noi)) == EOF || read == '\r' || read == '\n' || read == ' ') + { + // leave for-loop + break; + } + } + // skip rest of the label + while (read != EOF && read != '\r' && read != '\n' && read != ' ') + read = fgetc(noi); + // it has to be end of file or line if it's not space + if (read != ' ') + continue; + // strings have to end with \0 + label[i+1] = '\0'; + // read value + for (i = 0; i < 8; ++i) + { + value[i] = read; + if ((read = fgetc(noi)) == EOF || read == '\r' || read == '\n') + { + // leave for-loop + break; + } + } + // number is too long; ignore + if (read != EOF && read != '\r' && read != '\n') + continue; + value[i+1] = '\0'; + // we successfully read label and value + + // but filter out some invalid symbols + if (strcmp(label, ".__.ABS.") != 0 && strncmp(label, "l__", 3) != 0) + fprintf (sym, "%02X:%04X %s\n", (unsigned int)(strtoul(value, NULL, 0)>>16), (unsigned int)strtoul(value, NULL, 0)&0xFFFF, label); + } + else + // skip until file/line end + while ((read = fgetc(noi))!= EOF && read != '\r' && read != '\n'); + } + + // free close files + fclose (noi); + fclose (sym); + + fprintf (stderr, "Converted %s to %s.\n", nname, sname); + return 0; +} + +int +read_ihx (FILE *fin, BYTE **rom, int *size, int *real_size, struct gb_opt_s *o) +{ + int record_type; + + int extaddr = 0; + do + { + int nbytes; + int addr; + int checksum, sum = 0; + + if (getc (fin) != ':') + { + fprintf (stderr, "error: invalid IHX line.\n"); + return 0; + } + nbytes = getbyte (fin, &sum); + addr = getbyte (fin, &sum) << 8 | getbyte (fin, &sum); + record_type = getbyte (fin, &sum); + if(record_type == 4) + { + extaddr = getbyte (fin, &sum) << 8 | getbyte (fin, &sum); + extaddr <<= 16; // those are the upper 16 bits + checksum = getbyte (fin, &sum); + // move to the next record + if (0 != (sum & 0xff)) + { + fprintf (stderr, "error: bad checksum: %02x.\n", checksum); + return 0; + } + while (isspace (sum = getc (fin))) /* skip all kind of spaces */ + ; + ungetc (sum, fin); + if (getc (fin) != ':') + { + fprintf (stderr, "error: invalid IHX line.\n"); + return 0; + } + // parse real data part + checksum = sum = 0; + nbytes = getbyte (fin, &sum); + // lower 16 bits + addr = getbyte (fin, &sum) << 8 | getbyte (fin, &sum); + record_type = getbyte (fin, &sum); + } + // add linear address extension + addr |= extaddr; + // TODO: warn for unreachable banks according to chosen MBC + if (record_type > 1) + { + fprintf (stderr, "error: unsupported record type: %02x.\n", record_type); + return 0; + } + + if (addr + nbytes > *size) + { + // If auto-size is enabled, grow rom bank size by power of 2 when needed + if (o->rom_banks_autosize) + { + if (rom_autosize_grow(rom, addr + nbytes, size, o) == 0) + return 0; + } + else + { + fprintf (stderr, "error: size of the buffer is too small.\n"); + return 0; + } + } + + while (nbytes--) + { + if (addr < *size) + (*rom)[addr++] = getbyte (fin, &sum); + } + + if (addr > *real_size) + *real_size = addr; + + checksum = getbyte (fin, &sum); + if (0 != (sum & 0xff)) + { + fprintf (stderr, "error: bad checksum: %02x.\n", checksum); + return 0; + } + + while (isspace (sum = getc (fin))) /* skip all kind of spaces */ + ; + ungetc (sum, fin); + } + while (1 != record_type); /* EOF record */ + + return 1; +} + +int +main (int argc, char **argv) +{ + int size = 32768, skipsize = 0, pack = 0, real_size = 0, i = 0; + char *token; + BYTE *rom; + FILE *fin, *fout; + char *filename = NULL; + int ret; + int gb = 0; + int sms = 0; + + struct gb_opt_s gb_opt = {.cart_name="", + .licensee_str={'0', '0'}, + .mbc_type=0, + .nb_rom_banks=2, + .nb_ram_banks=0, + .licensee_id=0x33, + .is_gbc=0, + .is_sgb=0, + .sym_conversion=0, + .non_jp=0, + .rom_banks_autosize=0, + .do_logo_copy=true, + .address_overwrite={0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0, 0xFF, 0} }; + + struct sms_opt_s sms_opt = {.rom_size=0xa, + .region_code=7, + .version=0 }; + +#if defined(_WIN32) + setmode (fileno (stdout), O_BINARY); +#endif + + while (*++argv && '-' == argv[0][0] && '\0' != argv[0][1]) + { + switch (argv[0][1]) + { + case 's': + if (!*++argv) + { + usage (); + return 1; + } + size = strtoul (*argv, NULL, 0); + break; + + case 't': + if (!*++argv) + { + usage (); + return 1; + } + skipsize = strtoul (*argv, NULL, 0); + break; + + case 'h': + usage (); + return 0; + + case 'p': + pack = 1; + break; + + case 'Z': + /* generate GameBoy binary file */ + gb = 1; + break; + + case 'y': + /* GameBoy options: + * -yo Number of rom banks (default: 2) + * -ya Number of ram banks (default: 0) + * -yt MBC type (default: no MBC) + * -yn Name of program (default: name of output file) + */ + switch (argv[0][2]) + { + case 'o': + if (!*++argv) + { + usage (); + return 1; + } + // Use auto-size for rom banks if -yto size param is 'A' + if ((*argv)[0] == 'A' || (*argv)[0] == 'a') + gb_opt.rom_banks_autosize = 1; + else + { + gb_opt.nb_rom_banks = strtoul (*argv, NULL, 0); + size = gb_opt.nb_rom_banks * 0x4000; + } + break; + + case 'a': + if (!++argv) + { + usage (); + return 1; + } + gb_opt.nb_ram_banks = strtoul (*argv, NULL, 0); + break; + + case 't': + if (!*++argv) + { + usage (); + return 1; + } + gb_opt.mbc_type = strtoul (*argv, NULL, 0); + break; + + case 'n': + if (!*++argv) + { + usage (); + return 1; + } + strncpy (gb_opt.cart_name, *argv, CART_NAME_LEN-1); + gb_opt.cart_name[CART_NAME_LEN-1] = '\0'; + break; + + case 'k': + if (!*++argv) + { + usage (); + return 1; + } + strncpy (gb_opt.licensee_str, *argv, 2); + break; + + case 'l': + if (!*++argv) + { + usage (); + return 1; + } + gb_opt.licensee_id = strtoul (*argv, NULL, 0); + break; + + case 'c': + gb_opt.is_gbc = 1; + break; + + case 'C': + gb_opt.is_gbc = 2; + break; + + case 'N': + gb_opt.do_logo_copy = false; // when switch is present, turn off logo copy + break; + + case 's': + gb_opt.is_sgb = 1; + break; + + case 'S': + gb_opt.sym_conversion = 1; + break; + + case 'j': + gb_opt.non_jp = 1; + break; + + // like -yp0x143=0x80 + case 'p': + // remove "-yp" + *argv += 3; + // effectively split string into argv and token + strtok(*argv, "="); + token = strtok(NULL, "="); + for (i = 0; i < 16; i+=2) + { + if(gb_opt.address_overwrite[i] == 0xFF) + { + gb_opt.address_overwrite[i] = strtoul (*argv, NULL, 0); + gb_opt.address_overwrite[i+1] = strtoul (token, NULL, 0); + break; + } + } + break; + + default: + usage (); + return 1; + } + break; + + case 'S': + /* generate SMS binary file */ + sms = 1; + break; + + case 'x': + + switch (argv[0][2]) + { + case 'o': + if (!*++argv) + { + usage (); + return 1; + } + sms_opt.rom_size = strtoul (*argv, NULL, 0); + if ( sms_opt.rom_size > 2 && (sms_opt.rom_size < 0xa || sms_opt.rom_size > 0xf ) ) + { + fprintf (stderr, "error: invalid rom size (0x%X)", sms_opt.rom_size); + perror(NULL); + return 1; + } + if ( sms_opt.rom_size == 0xd || sms_opt.rom_size == 0x2 ) + { + fprintf (stderr, "warning: this rom size (0x%X) is bugged in some BIOSes\n", sms_opt.rom_size); + } + break; + + case 'j': + if (!*++argv) + { + usage (); + return 1; + } + sms_opt.region_code = strtoul (*argv, NULL, 0); + if ( sms_opt.region_code < 3 && sms_opt.region_code > 7 ) + { + fprintf (stderr, "error: invalid region code (0x%X)", sms_opt.region_code); + perror(NULL); + return 1; + } + break; + + case 'v': + if (!*++argv) + { + usage (); + return 1; + } + sms_opt.version = strtoul (*argv, NULL, 0); + if ( sms_opt.version > 0xf ) + { + fprintf (stderr, "error: invalid version (0x%X)", sms_opt.version); + perror(NULL); + return 1; + } + break; + + default: + usage (); + return 1; + } + break; + + default: + usage (); + return 1; + } + } + + fin = stdin; + fout = stdout; + if (*argv) + { + if ('-' != argv[0][0] || '\0' != argv[0][1]) + { + if (NULL == (fin = fopen (*argv, "r"))) + { + fprintf (stderr, "error: can't open %s: ", *argv); + perror(NULL); + return 1; + } + filename = *argv; + } + ++argv; + } + + if (NULL != argv[0] && NULL != argv[1]) + { + usage (); + return 1; + } + + rom = malloc (size); + if (rom == NULL) + { + fclose (fin); + fprintf (stderr, "error: couldn't allocate room for the image.\n"); + return 1; + } + memset (rom, FILL_BYTE, size); + + if (gb_opt.sym_conversion == 1) + { + if (filename) + noi2sym(filename); + else + { + fprintf (stderr, "error: .noi to .sym conversion needs an input file.\n"); + } + } + + ret = read_ihx (fin, &rom, &size, &real_size, &gb_opt); + + fclose (fin); + + if (ret) + { + if (gb) + gb_postproc (rom, size, &real_size, &gb_opt); + else if (sms) + sms_postproc (rom, size, &real_size, &sms_opt); + + if (*argv) + { + if ('-' != argv[0][0] || '\0' != argv[0][1]) + { + if (NULL == (fout = fopen (*argv, "wb"))) + { + fprintf (stderr, "error: can't create %s: ", *argv); + perror(NULL); + return 1; + } + } + } + + int writesize = (pack ? real_size : size) - skipsize; + if (writesize > 0) fwrite (rom + skipsize, 1, writesize, fout); + + fclose (fout); + + return 0; + } + else + return 1; +} + diff --git a/gbdk/gbdk-support/makecom/Makefile b/gbdk/gbdk-support/makecom/Makefile new file mode 100644 index 00000000..7e46e311 --- /dev/null +++ b/gbdk/gbdk-support/makecom/Makefile @@ -0,0 +1,32 @@ +# makecom makefile + +ifndef TARGETDIR +TARGETDIR = /opt/gbdk +endif + +ifeq ($(OS),Windows_NT) + BUILD_OS := Windows_NT +else + BUILD_OS := $(shell uname -s) +endif + +# Target older macOS version than whatever build OS is for better compatibility +ifeq ($(BUILD_OS),Darwin) + export MACOSX_DEPLOYMENT_TARGET=10.10 +endif + +CC = $(TOOLSPREFIX)gcc +CFLAGS = -ggdb -O -Wno-incompatible-pointer-types -DGBDKLIBDIR=\"$(TARGETDIR)\" +OBJ = makecom.o noi_file.o files.o bin_to_com.o list.o path_ops.o +BIN = makecom + +all: $(BIN) + +$(BIN): $(OBJ) + +clean: + rm -f *.o $(BIN) *~ + rm -f tmp.* + rm -f *.exe + + diff --git a/gbdk/gbdk-support/makecom/bin_to_com.c b/gbdk/gbdk-support/makecom/bin_to_com.c new file mode 100644 index 00000000..dd3b7722 --- /dev/null +++ b/gbdk/gbdk-support/makecom/bin_to_com.c @@ -0,0 +1,164 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "list.h" +#include "files.h" +#include "noi_file.h" +#include "bin_to_com.h" + + +uint8_t * banks[BANKS_MAX_COUNT] = {NULL}; +uint16_t banks_len[BANKS_MAX_COUNT] = {0}; +uint16_t banks_count = 0; + + +void banks_cleanup(void) { + for (int c = 0; c <= BANKS_MAX_ID; c++) { + if (banks[c] != NULL) { + + free(banks[c]); + banks[c] = NULL; + } + } +} + + +// Allocate memory for banks as banks are requested +// If preceding banks are not yet allocated then do those too +uint8_t * bank_get_ptr(uint16_t bank_num) { + + if (bank_num > BANKS_MAX_ID) { + printf("makecom: ERROR: Requested bank %d is larger than max bank num %d!\n", bank_num, BANKS_MAX_ID); + exit(EXIT_FAILURE); + } + + if (banks[bank_num] == NULL) { + + // Make sure all banks leading up to requested are also allocated + for (int c = 0; c <= bank_num; c++) { + if (banks[c] == NULL) { + + banks[c] = malloc(BANK_SIZE); + + if (!banks[c]) { + printf("makecom: ERROR: Failed to allocate memory for bank %d!\n", c); + exit(EXIT_FAILURE); + } + + // zero out buffer (though unused space will not get written out) + memset(banks[c], 0x00u, BANK_SIZE); + } + } + } + + return banks[bank_num]; +} + + + +// Copy data from source bin file at offset address into requested bank buffer +void copy_to_bank(uint16_t bank_num, uint32_t rom_src_addr, uint32_t bank_out_addr, uint32_t length) { + + // TODO: make sure bin src addr doesn't exceed source file buffer length + if ((rom_src_addr + length) > rom_buf_in_len) { + printf("makecom: ERROR: Can't copy %d bytes from %x for bank %d, would overflow past end of ROM source buffer of size %d!\n", length, rom_src_addr, bank_num, (uint32_t)rom_buf_in_len); + exit(EXIT_FAILURE); + } + + if ((bank_out_addr + length) > BANK_SIZE) { + printf("makecom: ERROR: Can't copy %d bytes to %04x for bank %d, would overflow past end of bank buffer of size %d!\n",length, bank_out_addr, bank_num, BANK_SIZE); + exit(EXIT_FAILURE); + } + + if (bank_num > banks_count) + banks_count = bank_num; + + // Update length of bank data buffers (to allow for truncating output later) + if ((bank_out_addr + length) > banks_len[bank_num]) + banks_len[bank_num] = (bank_out_addr + length); + + // printf("* copying... bank:%d from:%x to:%x len:%d\n", + // bank_num, rom_src_addr, bank_out_addr, length); + + memcpy(bank_get_ptr(bank_num) + bank_out_addr, p_rom_buf_in + rom_src_addr, length); +} + + +void banks_write_out(void) { + + // If any of below fails it will trigger exit() and cleanup() will be called + + // Write first bank out, handled differently than rest + if ((banks[0] != NULL) && (banks_len[0] > 0)) { + // printf("Bank %d: Writing %d bytes to %s\n",0, banks_len[0], filename_out_com); + file_write_from_buffer(filename_out_com, banks[0], banks_len[0]); + } else { + printf("makecom: Error: Bank 0 was empty, no data written\n"); + exit; + } + + // Write out remaining banks if applicable + char bank_fname[MAX_STR_LEN] = ""; + for (int c = 1; c <= BANKS_MAX_ID; c++) { + if ((banks[c] != NULL) && (banks_len[0] > 0)) { + // Format to 8.3 filename with bank num as zero padded extension + sprintf(bank_fname, "%s.%03d", filename_banks_base, c); + // printf("Bank %d: Writing %d bytes to %s\n",c, banks_len[c], bank_fname); + file_write_from_buffer(bank_fname, banks[c], banks_len[c]); + } else + break; // Exit loop on first unpopulated bank + } + +} + + + +int bin2com(void) { + + symbol_item * symbols = (symbol_item *)symbol_list.p_array; + + for(int c = 0; c < symbol_list.count; c++) { + + // Only process completed symbols (start and length both set, lenth not zero) + if ((symbols[c].addr_start != SYM_VAL_UNSET) && + (symbols[c].length != SYM_VAL_UNSET) && + (symbols[c].length > 0)) { + + // If symbol is for a bank > 0 + if ((strncmp(symbols[c].name, "_CODE_", (sizeof("_CODE_") - 1)) == 0) && (symbols[c].bank_num > 0)) { + // Copy data from bank addr in source ROM to start of separate bank buffer + copy_to_bank(symbols[c].bank_num, symbols[c].src_rom_addr, BANK_START_ADDR, symbols[c].length); + } else { + // For remaining symbols (assume bank 0), relocate them by -100 when possible (addr -> addr - 100) + copy_to_bank(BANK_0, symbols[c].addr_start, BANK_0_RELOC_ADDR(symbols[c].addr_start), symbols[c].length); + } + } + } + + // Patch in updated bank / overlay count and bank filename + if ((banks_count > 0) && (overlay_count_addr != SYM_VAL_UNSET) && (overlay_name_addr != SYM_VAL_UNSET)) { + + if (overlay_count_addr > BANK_0_ADDR_OFFSET) + *(banks[0] + (overlay_count_addr - BANK_0_ADDR_OFFSET)) = banks_count; + + if (overlay_name_addr > BANK_0_ADDR_OFFSET) + memcpy(banks[0] + (overlay_name_addr - BANK_0_ADDR_OFFSET), filename_overlay, COM_OVERLAY_NAME_LEN-1); + } + + // No write the data out + banks_write_out(); + + return EXIT_SUCCESS; +} + + + diff --git a/gbdk/gbdk-support/makecom/bin_to_com.h b/gbdk/gbdk-support/makecom/bin_to_com.h new file mode 100644 index 00000000..79d38c2b --- /dev/null +++ b/gbdk/gbdk-support/makecom/bin_to_com.h @@ -0,0 +1,31 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _BIN_TO_COM_H +#define _BIN_TO_COM_H + +#define MAX(a, b) ((a > b) ? a : b) +#define BANKS_MAX_COUNT 256 +#define BANKS_MAX_ID (BANKS_MAX_COUNT - 1) +#define BANK_SIZE (0x4000u) +#define BANK_START_ADDR (0x0000u) +#define BANK_0 0 +#define BANK_0_ADDR_OFFSET 0x100 + +#define BANK_GET_NUM(addr) ((addr & 0xFFFF0000U) >> 16) +#define WITHOUT_BANK(addr) (addr & 0x0000FFFFU) +// Converts a symbol bank address such as 0x34000 to a bin/rom address such as 0x12000 (i.e: ~0x4000 * 3) +// Make sure base bank addess doesn't go negative +#define BANK_GET_ROM_ADDR(addr) ( (MAX(WITHOUT_BANK(addr), BANK_SIZE) - BANK_SIZE) + (BANK_SIZE * BANK_GET_NUM(addr)) ) + +// Apply bank 0 address offset and make sure it doesn't go negative +#define BANK_0_RELOC_ADDR(addr) (MAX((addr), BANK_0_ADDR_OFFSET) - BANK_0_ADDR_OFFSET) + +void copy_data(uint16_t bank_num, uint32_t rom_src_addr, uint32_t bank_out_addr, uint32_t length); +uint8_t * bank_get_ptr(uint16_t bank_num); +void copy_to_bank(uint16_t bank_num, uint32_t rom_src_addr, uint32_t bank_out_addr, uint32_t length); +void banks_cleanup(void); +int bin2com(void); + +#endif // _BIN_TO_COM_H
\ No newline at end of file diff --git a/gbdk/gbdk-support/makecom/common.h b/gbdk/gbdk-support/makecom/common.h new file mode 100644 index 00000000..b38600aa --- /dev/null +++ b/gbdk/gbdk-support/makecom/common.h @@ -0,0 +1,25 @@ +#ifndef _COMMON_H +#define _COMMON_H + +#include <stdint.h> + +#define ARRAY_LEN(A) (sizeof(A) / sizeof(A[0])) + +#define MAX_STR_LEN 4096 +#define MAX_FILE_STR (MAX_STR_LEN) +#define SYM_MAX_STR_LEN 1024 + +#define BANK_FNAME_LEN (8+1+3 +1) // 8.3 filename style + terminator, ex: MYCOMFIL.001 (from "mycomfile.com") +#define COM_OVERLAY_NAME_LEN (8 + 1) // 8 chars for overlay string + terminator "MYCOMFIL" + + +extern char filename_in_bin[]; +extern char filename_in_noi[]; +extern char filename_out_com[]; +extern char filename_banks_base[]; +extern char filename_overlay[]; + +extern uint8_t * p_rom_buf_in; +extern size_t rom_buf_in_len; + +#endif // _COMMON_H
\ No newline at end of file diff --git a/gbdk/gbdk-support/makecom/files.c b/gbdk/gbdk-support/makecom/files.c new file mode 100644 index 00000000..c6f78e9d --- /dev/null +++ b/gbdk/gbdk-support/makecom/files.c @@ -0,0 +1,62 @@ +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + + + +// Read from a file into a buffer (will allocate needed memory) +// Returns NULL if reading file didn't succeed +uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size) { + + long fsize; + FILE * file_in = fopen(filename, "rb"); + uint8_t * filedata = NULL; + + if (file_in) { + // Get file size + fseek(file_in, 0, SEEK_END); + fsize = ftell(file_in); + if (fsize != -1L) { + fseek(file_in, 0, SEEK_SET); + + filedata = malloc(fsize); + if (filedata) { + if (fsize != fread(filedata, 1, fsize, file_in)) { + printf("makecom: Warning: File read size didn't match expected for %s\n", filename); + filedata = NULL; + } + // Read was successful, set return size + *ret_size = fsize; + } else printf("makecom: ERROR: Failed to allocate memory to read file %s\n", filename); + + } else printf("makecom: ERROR: Failed to read size of file %s\n", filename); + + fclose(file_in); + } else printf("makecom: ERROR: Failed to open input file %s\n", filename); + + return filedata; +} + + + +// Writes a buffer to a file +bool file_write_from_buffer(char * filename, uint8_t * p_buf, uint32_t data_len) { + + bool status = false; + size_t wrote_bytes; + FILE * file_out = fopen(filename, "wb"); + if (file_out) { + if (data_len == fwrite(p_buf, 1, data_len, file_out)) + status = true; + else + printf("makecom: Warning: File write size didn't match expected for %s\n", filename); + + fclose(file_out); + } else { + printf("makecom: ERROR: Failed to open output file %s!\n",filename); + exit(EXIT_FAILURE); + } + + return status; +} diff --git a/gbdk/gbdk-support/makecom/files.h b/gbdk/gbdk-support/makecom/files.h new file mode 100644 index 00000000..c027b173 --- /dev/null +++ b/gbdk/gbdk-support/makecom/files.h @@ -0,0 +1,7 @@ +#ifndef _FILES_H +#define _FILES_H + +uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size); +bool file_write_from_buffer(char * filename, uint8_t * p_buf, uint32_t data_len); + +#endif // _FILES_H diff --git a/gbdk/gbdk-support/makecom/list.c b/gbdk/gbdk-support/makecom/list.c new file mode 100644 index 00000000..868bee3a --- /dev/null +++ b/gbdk/gbdk-support/makecom/list.c @@ -0,0 +1,70 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> + +#include "common.h" +#include "list.h" + +#define LIST_GROW_SIZE 100 // 50 // grow array by N entries at a time + +// Initialize the list and it's array +// typesize *must* match the type that will be used with the array +void list_init(list_type * p_list, size_t array_typesize) { + p_list->typesize = array_typesize; + p_list->count = 0; + p_list->size = LIST_GROW_SIZE; + p_list->p_array = (void *)malloc(p_list->size * p_list->typesize); + + if (!p_list->p_array) { + printf("makecom: ERROR: Failed to allocate memory for list!\n"); + exit(EXIT_FAILURE); + } +} + + +// Free the array memory allocated for the list +void list_cleanup(list_type * p_list) { + if (p_list->p_array) { + free (p_list->p_array); + p_list->p_array = NULL; + } +} + + +// Add a new item to the lists array, resize if needed +// p_newitem *must* be the same type the list was initialized with +void list_additem(list_type * p_list, void * p_newitem) { + + void * tmp_list; + + p_list->count++; + + // Grow array if needed + if (p_list->count == p_list->size) { + // Save a copy in case reallocation fails + tmp_list = p_list->p_array; + + p_list->size += p_list->typesize * LIST_GROW_SIZE; + p_list->p_array = (void *)realloc(p_list->p_array, p_list->size * p_list->typesize); + // If realloc failed, free original buffer before quitting + if (!p_list->p_array) { + printf("makecom: ERROR: Failed to reallocate memory for list!\n"); + if (tmp_list) { + free(tmp_list); + tmp_list = NULL; + } + exit(EXIT_FAILURE); + } + } + + // Copy new entry + memcpy((uint_least8_t *)p_list->p_array + ((p_list->count - 1) * p_list->typesize), + p_newitem, + p_list->typesize); +} + diff --git a/gbdk/gbdk-support/makecom/list.h b/gbdk/gbdk-support/makecom/list.h new file mode 100644 index 00000000..ef9d3d52 --- /dev/null +++ b/gbdk/gbdk-support/makecom/list.h @@ -0,0 +1,20 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _LIST_H +#define _LIST_H + + +typedef struct list_type { + void * p_array; + uint32_t size; + uint32_t count; + size_t typesize; +} list_type; + +void list_init(list_type *, size_t); +void list_cleanup(list_type *); +void list_additem(list_type *, void *); + +#endif // _LIST_H
\ No newline at end of file diff --git a/gbdk/gbdk-support/makecom/makecom.c b/gbdk/gbdk-support/makecom/makecom.c new file mode 100644 index 00000000..a6abd51d --- /dev/null +++ b/gbdk/gbdk-support/makecom/makecom.c @@ -0,0 +1,147 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2022 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> +#include <ctype.h> + +#include "path_ops.h" +#include "common.h" +#include "files.h" +#include "noi_file.h" +#include "bin_to_com.h" + +#define EXT_NOI "noi" + +char filename_in_bin[MAX_STR_LEN] = ""; +char filename_in_noi[MAX_STR_LEN] = ""; +char filename_out_com[MAX_STR_LEN] = ""; +char filename_banks_base[MAX_STR_LEN] = ""; +char filename_overlay[COM_OVERLAY_NAME_LEN] = ""; + +uint8_t * p_rom_buf_in = NULL; +size_t rom_buf_in_len = 0; + +static void str_to_upper(char * str); +static void filenames_out_prepare(void); +static void display_help(void); +static int handle_args(int argc, char * argv[]); +void cleanup(void); + + +static void str_to_upper(char * str) { + while (*str) { + *str = toupper(*str); + str++; + } +} + + + +// Generate names for the exported bank/overlay files +// based on the output COM filename and path +static void filenames_out_prepare(void) { + + char str_banks_name[MAX_STR_LEN]; + char str_path_only[MAX_STR_LEN]; + + snprintf(str_banks_name, sizeof(str_banks_name), "%s", filename_out_com); + + if (!get_path_without_filename(filename_out_com, str_path_only, sizeof(str_path_only))) { + printf("makecom: Error: source path %s exceeds max length\n", filename_out_com); + exit; + } + + // Convert filename to uppercase and remove extension + str_to_upper(str_banks_name); + filename_remove_extension(str_banks_name); + + // Then truncate it to 8 characters with no padding for bank filenames + int ret = snprintf(filename_banks_base, sizeof(filename_banks_base), "%s%.8s", str_path_only, get_filename_from_path(str_banks_name)); + if (ret < 0) printf("makecom: Warning, banks output path truncated\n"); + // Then with trailing space char padding for the overlay filename patch + snprintf(filename_overlay, sizeof(filename_overlay), "%-8.8s", get_filename_from_path(str_banks_name)); +} + + +static void display_help(void) { + + fprintf(stdout, + "makecom image.rom image.noi output.com\n" + "Use: convert a binary .rom file to .msxdos com format.\n" + ); +} + + +int handle_args(int argc, char * argv[]) { + + if (argc == 3) { + // Copy input and output filenames from arguments + snprintf(filename_in_bin, sizeof(filename_in_bin), "%s", argv[1]); + snprintf(filename_out_com, sizeof(filename_out_com), "%s", argv[2]); + // Generate .noi file name from input .bin file name + snprintf(filename_in_noi, sizeof(filename_in_noi), "%s", argv[1]); + filename_replace_extension(filename_in_noi, EXT_NOI, MAX_STR_LEN); + } else if (argc == 4) { + // Copy input and output filenames from arguments + snprintf(filename_in_bin, sizeof(filename_in_bin), "%s", argv[1]); + snprintf(filename_in_noi, sizeof(filename_in_noi), "%s", argv[2]); + snprintf(filename_out_com, sizeof(filename_out_com), "%s", argv[3]); + } else { + display_help(); + return false; + } + + filenames_out_prepare(); + + + // printf("bin: %s\n",filename_in_bin); + // printf("noi: %s\n",filename_in_noi); + // printf("com: %s\n",filename_out_com); + + // printf("banks base: _%s_\n",filename_banks_base); + // printf("overlay : _%s_\n",filename_overlay); + + + return true; +} + + +// Registered as atexit() handler +// Free resources during normal shutdown or from a call to exit() +void cleanup(void) { + + banks_cleanup(); + noi_cleanup(); + + if (p_rom_buf_in != NULL) { + free(p_rom_buf_in); + p_rom_buf_in = NULL; + } +} + + +int main( int argc, char *argv[] ) { + + // Exit with failure by default + int ret = EXIT_FAILURE; + + // Register cleanup with exit handler + atexit(cleanup); + + if (handle_args(argc, argv)) { + + p_rom_buf_in = file_read_into_buffer(filename_in_bin, &rom_buf_in_len); + + if (p_rom_buf_in) + if (noi_file_load_symbols(filename_in_noi)) + ret = bin2com(); + } + cleanup(); + + return ret; // Exit with failure by default +} diff --git a/gbdk/gbdk-support/makecom/noi_file.c b/gbdk/gbdk-support/makecom/noi_file.c new file mode 100644 index 00000000..f2fc935d --- /dev/null +++ b/gbdk/gbdk-support/makecom/noi_file.c @@ -0,0 +1,164 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdint.h> + +#include "common.h" +#include "noi_file.h" +#include "bin_to_com.h" + +// Example data to parse from a .noi file: +/* +DEF s__CODE 0x200 +DEF l__BASE 0x2A3 +DEF __shadow_OAM_OFF 0x455 +DEF .mode 0x456 +*/ + +const char * known_section_names[] = + {"_CODE","_HOME","_BASE","_CODE_0","_INITIALIZER","_LIT","_GSINIT","_GSFINAL"}; + + +list_type symbol_list; +uint32_t overlay_count_addr = SYM_VAL_UNSET; +uint32_t overlay_name_addr = SYM_VAL_UNSET; + + +// Initialize the symbol list +void noi_init(void) { + + list_init(&symbol_list, sizeof(symbol_item)); +} + + +// Free the symbol list +void noi_cleanup(void) { + + list_cleanup(&symbol_list); +} + + +// Find a matching symbol, if none matches a new one is added and returned +static int symbollist_get_id_by_name(char * symbol_name) { + + symbol_item * symbols = (symbol_item *)symbol_list.p_array; + + // Check for matching symbol name + for(int c = 0;c < symbol_list.count; c++) { + // Return matching symbol index if present + if (strcmp(symbol_name, symbols[c].name) == 0) { + return c; + } + } + + // no match was found, add symbol + symbol_item new_symbol = {.name = "", .addr_start = SYM_VAL_UNSET, .length = SYM_VAL_UNSET, .bank_num = 0x00, .src_rom_addr = SYM_VAL_UNSET}; + snprintf(new_symbol.name, sizeof(new_symbol.name), "%s", symbol_name); + list_additem(&symbol_list, &new_symbol); + + return (symbol_list.count - 1); +} + + +// Add the start address or length for a s_ or l_ symbol record +static void noi_symbollist_add(char rec_type, char * name, char * value) { + + // Only add symbols if they're in the known section name list + for (int c = 0; c < ARRAY_LEN(known_section_names); c++) { + + if ((strcmp(name, known_section_names[c]) == 0) || + (strncmp(name, "_CODE_", (sizeof("_CODE_") - 1)) == 0)) { + + symbol_item * symbols = (symbol_item *)symbol_list.p_array; + + // Check to see if there is an existing record to update + // If one isn't found a new record will have been created automatically + int symbol_id = symbollist_get_id_by_name(name); + if (symbol_id != ERR_NO_SYMBOLS_LEFT) { + + // Handle whether it's a start-of-address or a length record for the given symbol + if (rec_type == NOI_REC_START) { + symbols[symbol_id].addr_start = strtol(value, NULL, 16); + symbols[symbol_id].bank_num = BANK_GET_NUM(symbols[symbol_id].addr_start); + symbols[symbol_id].src_rom_addr = BANK_GET_ROM_ADDR(symbols[symbol_id].addr_start); + } + else if (rec_type == NOI_REC_LENGTH) { + symbols[symbol_id].length = strtol(value, NULL, 16); + } + } + + return; + } + } +} + + +// Load symbols from a .noi file and create paired records for start & length entries +// Plus, look for and store for overlay symbol information +int noi_file_load_symbols(char * filename_in) { + + char cols; + char * p_str; + char * p_words[MAX_SPLIT_WORDS]; + char strline_in[MAX_STR_LEN] = ""; + FILE * noi_file = fopen(filename_in, "r"); + symbol_item symbol; + int symbol_id; + + noi_init(); + + if (noi_file) { + + // Read one line at a time into \0 terminated string + while ( fgets(strline_in, sizeof(strline_in), noi_file) != NULL) { + + // Require minimum length to match + if (strlen(strline_in) >= NOI_REC_START_LEN) { + + // Split string into words separated by spaces + cols = 0; + p_str = strtok(strline_in," "); + while (p_str != NULL) + { + p_words[cols++] = p_str; + // Only split on underscore for the second match + p_str = strtok(NULL, " "); + if (cols >= MAX_SPLIT_WORDS) break; + } + + // [0] = "DEF" + // [1] = symbol name + // [2] = symbol value + if (cols >= NOI_REC_COUNT_MATCH) { + + // If it matches either _s_egment or _l_ength records (first two chars) + // then add a record for it with the first two chars truncated + if ((strncmp(p_words[1], "l_", NOI_REC_S_L_CHK) == 0) || + (strncmp(p_words[1], "s_", NOI_REC_S_L_CHK) == 0)) { + noi_symbollist_add(p_words[1][0], p_words[1] + NOI_REC_S_L_CHK, p_words[2]); + } + // Otherwise check for records of interest and store them + else if (strcmp(p_words[1], "___overlay_count") == 0) { + overlay_count_addr = strtol(p_words[2], NULL, 16); + } + else if (strcmp(p_words[1], "___overlay_name") == 0) { + overlay_name_addr = strtol(p_words[2], NULL, 16); + } + } + } // end: valid min chars to process line + + } // end: while still lines to process + + fclose(noi_file); + + } // end: if valid file + else return (false); + + return true; +} diff --git a/gbdk/gbdk-support/makecom/noi_file.h b/gbdk/gbdk-support/makecom/noi_file.h new file mode 100644 index 00000000..b6d873b7 --- /dev/null +++ b/gbdk/gbdk-support/makecom/noi_file.h @@ -0,0 +1,40 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _NOI_FILE_H +#define _NOI_FILE_H + +#include "list.h" + +#define MAX_STR_LEN 4096 +#define MAX_SPLIT_WORDS 4 + +#define NOI_REC_COUNT_MATCH 3 // "DEF ...symbol_name... ...symbol_value... + +#define ERR_NO_SYMBOLS_LEFT -1 +#define SYM_VAL_UNSET 0xFFFFFFFF + +#define NOI_REC_START_LEN 4 // length of "DEF " +#define NOI_REC_S_L_CHK 2 // length of "s_" or "l_" +#define NOI_REC_START 's' +#define NOI_REC_LENGTH 'l' + +typedef struct symbol_item { + char name[SYM_MAX_STR_LEN]; + uint32_t addr_start; + uint32_t length; + uint16_t bank_num; + uint32_t src_rom_addr; +} symbol_item; + +extern list_type symbol_list; +extern uint32_t overlay_count_addr; +extern uint32_t overlay_name_addr; + + +int noi_file_load_symbols(char * filename_in); +void noi_init(void); +void noi_cleanup(void); + +#endif // _NOI_FILE_H
\ No newline at end of file diff --git a/gbdk/gbdk-support/makecom/pascal/Makefile b/gbdk/gbdk-support/makecom/pascal/Makefile new file mode 100644 index 00000000..5a1d29ed --- /dev/null +++ b/gbdk/gbdk-support/makecom/pascal/Makefile @@ -0,0 +1,31 @@ +# bankcheck (auto bank tool) makefile + +ifndef TARGETDIR +TARGETDIR = /opt/gbdk +endif + +ifeq ($(OS),Windows_NT) + BUILD_OS := Windows_NT +else + BUILD_OS := $(shell uname -s) +endif + +# Target older macOS version than whatever build OS is for better compatibility +ifeq ($(BUILD_OS),Darwin) + export MACOSX_DEPLOYMENT_TARGET=10.10 +endif + +CC = +CFLAGS = +OBJ = +BIN = makecom + +all: $(BIN) + +$(BIN): makecom.dpr + fpc -B $< + +clean: + rm -f *.o $(BIN) *~ *.dof *.cfg *.dsk + rm -f *.exe + diff --git a/gbdk/gbdk-support/makecom/pascal/makecom.dpr b/gbdk/gbdk-support/makecom/pascal/makecom.dpr new file mode 100644 index 00000000..c2164095 --- /dev/null +++ b/gbdk/gbdk-support/makecom/pascal/makecom.dpr @@ -0,0 +1,184 @@ +{$APPTYPE CONSOLE} +{$IFDEF FPC} + {$MODE DELPHI} +{$ENDIF} +uses classes, sysutils, math; + +procedure ERROR(const msg: ansistring; const params: array of const); +begin + writeln(format(msg, params)); + writeln('USAGE: makecom <image.bin> [<image.noi>] <output.com>'); + halt(1); +end; + +procedure DecodeCommaText(const Value: ansistring; result: tStringList; adelimiter: ansichar); +var P, P1 : PAnsiChar; + S : ansistring; +begin + if assigned(result) then begin + result.BeginUpdate; + try + result.Clear; + P := PChar(Value); + while P^ in [#1..#31] do inc(P); + while P^ <> #0 do + begin + if P^ = '"' then + S := AnsiExtractQuotedStr(P, '"') + else + begin + P1 := P; + while (P^ >= ' ') and (P^ <> adelimiter) do inc(P); + SetString(S, P1, P - P1); + end; + result.Add(S); + while P^ in [#1..#31] do inc(P); + if P^ = adelimiter then + repeat + inc(P); + until not (P^ in [#1..#31]); + end; + finally + result.EndUpdate; + end; + end; +end; + +function load_symbols(const filename: ansistring; symbols: tStringList): boolean; +var s : textfile; + str : ansistring; + row : tStringList; +begin + result:= assigned(symbols); + if result then begin + assignfile(s, filename); reset(s); + try + row := tStringList.create; + try + while not eof(s) do begin + readln(s, str); + DecodeCommaText(str, row, ' '); + if (row[0] = 'DEF') then begin + symbols.Values[row[1]] := row[2]; + end; + end; + result:= true; + finally freeandnil(row); end; + finally closefile(s); end; + end; +end; + +function CopyData(banks: tList; bank: longint; image: tMemoryStream; source_ofs, dest_ofs: longint; len: longint): boolean; +var i : longint; + data : tMemoryStream; +begin + result:= (bank < 255); + if result then begin + if (banks.count <= bank) then + for i:= banks.count to bank do banks.Add(tMemoryStream.Create()); + data:= banks[bank]; + data.Seek(dest_ofs, soFromBeginning); + data.Write(pAnsiChar(image.Memory)[source_ofs], len); + end; +end; +procedure WriteData(banks: tList; const destname: ansistring); +var i : longint; + ovr : ansistring; +begin + if (banks.count > 0) then begin + with tMemoryStream(banks[0]) do try + writeln(format('writing program: %s', [destname])); + SaveToFile(destname); + finally free; end; + for i:= 1 to banks.count - 1 do + with tMemoryStream(banks[i]) do try + ovr:= ChangeFileExt(destname, format('.%.3d', [i])); + writeln(format('writing overlay: %s', [ovr])); + SaveToFile(ovr); + finally free; end; + end; +end; + +function Hex2Int(value: ansistring): longint; +begin + if (copy(value, 1, 2)) = '0x' then begin value[1]:= ' '; value[2]:= '$'; end; + result:= StrToIntDef(value, 0); +end; + +const section_names = '_CODE,_HOME,_BASE,_CODE_0,_INITIALIZER,_LIT,_GSINIT,_GSFINAL'; +var name_bin : ansistring; + name_noi : ansistring; + name_out : ansistring; + symbols : tStringList; + known : tStringList; + banks : tList; + source : tMemoryStream; + i, bank : longint; + name, v, l : ansistring; + addr, len : longint; +begin + if (paramcount() = 2) then begin + name_bin:= paramstr(1); name_noi:= changefileext(name_bin, '.noi'); name_out:= paramstr(2); + end else begin + if (paramcount() < 3) then ERROR('ERROR: Not sufficient parameters', []); + name_bin:= paramstr(1); name_noi:= paramstr(2); name_out:= paramstr(3); + end; + + if not fileexists(name_noi) then ERROR('ERROR: symbol file: "%s" not found', [name_noi]); + if not fileexists(name_bin) then ERROR('ERROR: binary image: "%s" not found', [name_bin]); + + known:= tStringList.create; + symbols := tStringList.create; + try + DecodeCommaText(section_names, known, ','); + if load_symbols(name_noi, symbols) then begin + source:= tMemoryStream.Create; + try + source.LoadFromFile(name_bin); + banks:= tList.Create; + with banks do try + for i:= 0 to symbols.count - 1 do begin + name:= symbols.Names[i]; + v:= symbols.Values[name]; + if (copy(name, 1, 2) = 's_') then begin + name:= copy(name, 3, length(name)); + l:= symbols.Values[format('l_%s',[name])]; + if length(l) > 0 then begin + addr:= Hex2Int(v); + len:= Hex2Int(l); + + if (len > 0) then begin + if (known.IndexOf(name) >= 0) then begin + CopyData(banks, 0, source, addr, math.max(0, addr - $100), len); + end; + if (copy(Name, 1, 6) = '_CODE_') then begin + bank:= addr shr 16; + CopyData(banks, bank, source, math.max(0, (addr and $ffff) - $4000) + $4000 * bank, 0, len); + end; + end; + end; + end; + end; + + if (banks.Count > 0) then begin + addr:= Hex2Int(symbols.Values['___overlay_count']); + if (addr > $100) then pAnsiChar(tMemoryStream(banks[0]).Memory)[addr - $100]:= chr(banks.Count - 1); + addr:= Hex2Int(symbols.Values['___overlay_name']); + if (addr > $100) then begin + name:= format('%-8.8s', [uppercase(changefileext(extractfilename(name_out), ''))]); + system.move(name[1], pAnsiChar(tMemoryStream(banks[0]).Memory)[addr - $100], 8); + end; + end; + + writeln('writing...'); + WriteData(banks, name_out); + writeln('done!'); + finally banks.free; end; + finally freeandnil(source); end; + end; + finally + freeandnil(symbols); + freeandnil(known); + end; + +end.
\ No newline at end of file diff --git a/gbdk/gbdk-support/makecom/path_ops.c b/gbdk/gbdk-support/makecom/path_ops.c new file mode 100644 index 00000000..974a4870 --- /dev/null +++ b/gbdk/gbdk-support/makecom/path_ops.c @@ -0,0 +1,145 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include "common.h" +#include "path_ops.h" + +const char kExtensionSeparator = '.'; +const char kPathSeparator = + +#ifdef _WIN32 + #ifndef _WIN32 + #define __WIN32__ + #endif +#endif + +#ifdef __WIN32__ + '\\'; +#else + '/'; +#endif + +const char kPathSeparator_unix = '/'; + + +void filename_replace_extension(char * filename, char * new_ext, size_t maxlen) { + + // Use a temp work string in case out and in filename are the same pointer + char temp[MAX_FILE_STR]; + char ext_sep[2] = {'\0'}; // default to empty string + + // Add leading . to path if needed + if (new_ext[0] != kExtensionSeparator) { + ext_sep[0] = kExtensionSeparator; + ext_sep[1] = '\0'; + } + + // Strip extension from filename, append new extension + filename_remove_extension(filename); + snprintf(temp, maxlen, "%s%s%s", filename, ext_sep, new_ext); + snprintf(filename, maxlen, "%s", temp); +} + + +void filename_replace_path(char * filename, char * new_path, size_t maxlen) { + + // Use a temp work string in case out and in filename are the same pointer + char temp[MAX_FILE_STR]; + char path_sep[2] = {'\0'}; // default to empty string + + // Add trailing slash to path if needed (Windows needs both for when running under linix like env) +#ifdef __WIN32__ + if (((new_path[(strlen(new_path)-1)] != kPathSeparator)) && + ((new_path[(strlen(new_path)-1)] != kPathSeparator_unix))) +#else + if ((new_path[(strlen(new_path)-1)] != kPathSeparator)) +#endif + { + path_sep[0] = kPathSeparator; + path_sep[1] = '\0'; + } + + // Strip path from path+filename, pre-pend new path + snprintf(temp, maxlen, "%s%s%s", new_path, path_sep, get_filename_from_path(filename)); + snprintf(filename, maxlen, "%s", temp); +} + + +const char * get_filename_from_path(const char * path) +{ + size_t i; + + // Returns string starting at last occurrance of path separator char + for(i = strlen(path) - 1; i; i--) { + + // Add trailing slash to path if needed (Windows needs both for when running under linix like env) + #ifdef __WIN32__ + if ((path[i] == kPathSeparator) || (path[i] == kPathSeparator_unix)) + #else + if (path[i] == kPathSeparator) + #endif + { + return &path[i+1]; + } + } + return path; +} + + +void filename_remove_extension(char * path) +{ + char * last_ext; + char * last_slash; + + // Find the last path separator if present + // Starting from here ensures that no path ".." characters + // get mistaken as extension delimiters. + last_slash = strrchr (path, kExtensionSeparator); + if (!last_slash) + last_slash = path; + + // Then check to see if there is an extension (starting with the last occurance of '.') + // (tries to remove *all* trailing extensions backward until the last slash) + last_ext = strrchr (last_slash, kExtensionSeparator); + while (last_ext) { + if (last_ext != NULL) { + // If an extension is found then overwrite it with a string terminator + *last_ext = '\0'; + } + last_ext = strrchr (last_slash, kExtensionSeparator); + } +} + + +bool get_path_without_filename(const char * path, char * path_only, uint32_t str_max) +{ + size_t i; + + if (strlen(path) + 1 > str_max) + return false; + + for(i = strlen(path) - 1; i; i--) { + + // Add trailing slash to path if needed (Windows needs both for when running under linix like env) + #ifdef __WIN32__ + if ((path[i] == kPathSeparator) || (path[i] == kPathSeparator_unix)) + #else + if (path[i] == kPathSeparator) + #endif + { + memcpy(path_only, path, i+1 ); + path_only[i+1] = '\0'; + return true; + } + } + + // memcpy(path_only, path, strlen(path)); + // No separater found, so no path + path_only[0] = '\0'; + return true; +} + diff --git a/gbdk/gbdk-support/makecom/path_ops.h b/gbdk/gbdk-support/makecom/path_ops.h new file mode 100644 index 00000000..2ae8e3a0 --- /dev/null +++ b/gbdk/gbdk-support/makecom/path_ops.h @@ -0,0 +1,15 @@ +// This is free and unencumbered software released into the public domain. +// For more information, please refer to <https://unlicense.org> +// bbbbbr 2020 + +#ifndef _PATH_OPS_H +#define _PATH_OPS_H + +void filename_replace_extension(char * filename, char * new_ext, size_t maxlen); +void filename_replace_path(char * filename, char * new_path, size_t maxlen); +const char * get_filename_from_path(const char * path); +void filename_remove_extension(char * path); +bool get_path_without_filename(const char * path, char * path_only, uint32_t str_max); + + +#endif // _PATH_OPS_H
\ No newline at end of file diff --git a/gbdk/gbdk-support/png2asset/.gitignore b/gbdk/gbdk-support/png2asset/.gitignore new file mode 100644 index 00000000..34c8dee4 --- /dev/null +++ b/gbdk/gbdk-support/png2asset/.gitignore @@ -0,0 +1,388 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Nuget personal access tokens and Credentials +nuget.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +.idea/ +*.sln.iml diff --git a/gbdk/gbdk-support/png2asset/Makefile b/gbdk/gbdk-support/png2asset/Makefile new file mode 100644 index 00000000..fad5212f --- /dev/null +++ b/gbdk/gbdk-support/png2asset/Makefile @@ -0,0 +1,60 @@ +# ***************************************************** +# Variables to control Makefile operation + +# Might want when doing linux -> win cross build +# LFLAGS = -s -static + +CXX = $(TOOLSPREFIX)g++ +CXXFLAGS = -Os -Wall -g +LFLAGS = -g + +ifeq ($(OS),Windows_NT) + BUILD_OS := Windows_NT +else + BUILD_OS := $(shell uname -s) +endif + +# Target older macOS version than whatever build OS is for better compatibility +ifeq ($(BUILD_OS),Darwin) + export MACOSX_DEPLOYMENT_TARGET=10.10 +endif + + +# Static flag for windows cross and native builds +ifeq ($(TOOLSPREFIX),i686-w64-mingw32-) +# prefix will automatically be .exe for cross build + EXT = .exe + LFLAGS += -static +endif + +ifeq ($(OS),Windows_NT) + EXT = .exe + LFLAGS += -static +endif + + + + +# **************************************************** +# Targets needed to bring the executable up to date + +TARGET = png2asset + + +SRCS := lodepng.cpp png2asset.cpp +OBJS := $(SRCS:%.cpp=%.o) + +$(TARGET): $(OBJS) + $(CXX) $(LFLAGS) -o $(TARGET) $(OBJS) + strip $@$(EXT) + +# The main.o target can be written more simply + +%.cpp.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + rm -f *.o + rm -f *.exe + rm -f TARGET + diff --git a/gbdk/gbdk-support/png2asset/lodepng.cpp b/gbdk/gbdk-support/png2asset/lodepng.cpp new file mode 100644 index 00000000..c6e7f384 --- /dev/null +++ b/gbdk/gbdk-support/png2asset/lodepng.cpp @@ -0,0 +1,6497 @@ +/* +LodePNG version 20210627 + +Copyright (c) 2005-2021 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" + +#ifdef LODEPNG_COMPILE_DISK +#include <limits.h> /* LONG_MAX */ +#include <stdio.h> /* file handling */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +#include <stdlib.h> /* allocations */ +#endif /* LODEPNG_COMPILE_ALLOCATORS */ + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20210627"; + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) { +#ifdef LODEPNG_MAX_ALLOC + if(size > LODEPNG_MAX_ALLOC) return 0; +#endif + return malloc(size); +} + +/* NOTE: when realloc returns NULL, it leaves the original memory untouched */ +static void* lodepng_realloc(void* ptr, size_t new_size) { +#ifdef LODEPNG_MAX_ALLOC + if(new_size > LODEPNG_MAX_ALLOC) return 0; +#endif + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) { + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +/* TODO: support giving additional void* payload to the custom allocators */ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* convince the compiler to inline a function, for use when this measurably improves performance */ +/* inline is not available in C90, but use it when supported by the compiler */ +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) +#define LODEPNG_INLINE inline +#else +#define LODEPNG_INLINE /* not available */ +#endif + +/* restrict is not available in C90, but use it when supported by the compiler */ +#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ + (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ + (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) +#define LODEPNG_RESTRICT __restrict +#else +#define LODEPNG_RESTRICT /* not available */ +#endif + +/* Replacements for C library functions such as memcpy and strlen, to support platforms +where a full C library is not available. The compiler can recognize them and compile +to something as fast. */ + +static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, + const void* LODEPNG_RESTRICT src, size_t size) { + size_t i; + for(i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i]; +} + +static void lodepng_memset(void* LODEPNG_RESTRICT dst, + int value, size_t num) { + size_t i; + for(i = 0; i < num; i++) ((char*)dst)[i] = (char)value; +} + +/* does not check memory out of bounds, do not use on untrusted data */ +static size_t lodepng_strlen(const char* a) { + const char* orig = a; + /* avoid warning about unused function in case of disabled COMPILE... macros */ + (void)(&lodepng_strlen); + while(*a) a++; + return (size_t)(a - orig); +} + +#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) +/* Safely check if adding two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_addofl(size_t a, size_t b, size_t* result) { + *result = a + b; /* Unsigned addition is well defined and safe in C90 */ + return *result < a; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ + +#ifdef LODEPNG_COMPILE_DECODER +/* Safely check if multiplying two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_mulofl(size_t a, size_t b, size_t* result) { + *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ + return (a != 0 && *result / a != b); +} + +#ifdef LODEPNG_COMPILE_ZLIB +/* Safely check if a + b > c, even if overflow could happen. */ +static int lodepng_gtofl(size_t a, size_t b, size_t c) { + size_t d; + if(lodepng_addofl(a, b, &d)) return 1; + return d > c; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code){\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code){\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call){\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code){\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*dynamic vector of unsigned ints*/ +typedef struct uivector { + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) { + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) { + size_t allocsize = size * sizeof(unsigned); + if(allocsize > p->allocsize) { + size_t newsize = allocsize + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static void uivector_init(uivector* p) { + p->data = NULL; + p->size = p->allocsize = 0; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) { + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector { + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) { + if(size > p->allocsize) { + size_t newsize = size + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static ucvector ucvector_init(unsigned char* buffer, size_t size) { + ucvector v; + v.data = buffer; + v.allocsize = v.size = size; + return v; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +/*free string pointer and set it to NULL*/ +static void string_cleanup(char** out) { + lodepng_free(*out); + *out = NULL; +} + +/*also appends null termination character*/ +static char* alloc_string_sized(const char* in, size_t insize) { + char* out = (char*)lodepng_malloc(insize + 1); + if(out) { + lodepng_memcpy(out, in, insize); + out[insize] = 0; + } + return out; +} + +/* dynamically allocates a new string with a copy of the null terminated input text */ +static char* alloc_string(const char* in) { + return alloc_string_sized(in, lodepng_strlen(in)); +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG) +static unsigned lodepng_read32bitInt(const unsigned char* buffer) { + return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | + ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); +} +#endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/ + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +/* returns negative value on error. This should be pure C compatible, so no fstat. */ +static long lodepng_filesize(const char* filename) { + FILE* file; + long size; + file = fopen(filename, "rb"); + if(!file) return -1; + + if(fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return -1; + } + + size = ftell(file); + /* It may give LONG_MAX as directory size, this is invalid for us. */ + if(size == LONG_MAX) size = -1; + + fclose(file); + return size; +} + +/* load file into buffer that already has the correct allocated size. Returns error code.*/ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) { + FILE* file; + size_t readsize; + file = fopen(filename, "rb"); + if(!file) return 78; + + readsize = fread(out, 1, size, file); + fclose(file); + + if(readsize != size) return 78; + return 0; +} + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { + long size = lodepng_filesize(filename); + if(size < 0) return 78; + *outsize = (size_t)size; + + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(!(*out) && size > 0) return 83; /*the above malloc failed*/ + + return lodepng_buffer_file(*out, (size_t)size, filename); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite(buffer, 1, buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct { + ucvector* data; + unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/ +} LodePNGBitWriter; + +static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) { + writer->data = data; + writer->bp = 0; +} + +/*TODO: this ignores potential out of memory errors*/ +#define WRITEBIT(writer, bit){\ + /* append new byte */\ + if(((writer->bp) & 7u) == 0) {\ + if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\ + writer->data->data[writer->data->size - 1] = 0;\ + }\ + (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\ + ++writer->bp;\ +} + +/* LSB of value is written first, and LSB of bytes is used first */ +static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */ + WRITEBIT(writer, value); + } else { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + size_t i; + for(i = 0; i != nbits; ++i) { + WRITEBIT(writer, (unsigned char)((value >> i) & 1)); + } + } +} + +/* This one is to use for adding huffman symbol, the value bits are written MSB first */ +static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + size_t i; + for(i = 0; i != nbits; ++i) { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u)); + } +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +typedef struct { + const unsigned char* data; + size_t size; /*size of data in bytes*/ + size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ + size_t bp; + unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ +} LodePNGBitReader; + +/* data size argument is in bytes. Returns error if size too large causing overflow */ +static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) { + size_t temp; + reader->data = data; + reader->size = size; + /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ + if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; + /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and + trying to ensure 32 more bits*/ + if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; + reader->bp = 0; + reader->buffer = 0; + return 0; /*ok*/ +} + +/* +ensureBits functions: +Ensures the reader can at least read nbits bits in one or more readBits calls, +safely even if not enough bits are available. +Returns 1 if there are enough bits available, 0 if not. +*/ + +/*See ensureBits documentation above. This one ensures exactly 1 bit */ +/*static unsigned ensureBits1(LodePNGBitReader* reader) { + if(reader->bp >= reader->bitsize) return 0; + reader->buffer = (unsigned)reader->data[reader->bp >> 3u] >> (reader->bp & 7u); + return 1; +}*/ + +/*See ensureBits documentation above. This one ensures up to 9 bits */ +static unsigned ensureBits9(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 1u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 17 bits */ +static unsigned ensureBits17(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 2u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 25 bits */ +static LODEPNG_INLINE unsigned ensureBits25(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 3u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 32 bits */ +static LODEPNG_INLINE unsigned ensureBits32(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 4u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ +static unsigned peekBits(LodePNGBitReader* reader, size_t nbits) { + /* The shift allows nbits to be only up to 31. */ + return reader->buffer & ((1u << nbits) - 1u); +} + +/* Must have enough bits available with ensureBits */ +static void advanceBits(LodePNGBitReader* reader, size_t nbits) { + reader->buffer >>= nbits; + reader->bp += nbits; +} + +/* Must have enough bits available with ensureBits */ +static unsigned readBits(LodePNGBitReader* reader, size_t nbits) { + unsigned result = peekBits(reader, nbits); + advanceBits(reader, nbits); + return result; +} + +/* Public for testing only. steps and result must have numsteps values. */ +unsigned lode_png_test_bitreader(const unsigned char* data, size_t size, + size_t numsteps, const size_t* steps, unsigned* result) { + size_t i; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, data, size); + if(error) return 0; + for(i = 0; i < numsteps; i++) { + size_t step = steps[i]; + unsigned ok; + if(step > 25) ok = ensureBits32(&reader, step); + else if(step > 17) ok = ensureBits25(&reader, step); + else if(step > 9) ok = ensureBits17(&reader, step); + else ok = ensureBits9(&reader, step); + if(!ok) return 0; + result[i] = readBits(&reader, step); + } + return 1; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static unsigned reverseBits(unsigned bits, unsigned num) { + /*TODO: implement faster lookup table based version when needed*/ + unsigned i, result = 0; + for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; + return result; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman +tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree { + unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ + unsigned* lengths; /*the lengths of the huffman codes*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ + /* for reading only */ + unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ + unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ +} HuffmanTree; + +static void HuffmanTree_init(HuffmanTree* tree) { + tree->codes = 0; + tree->lengths = 0; + tree->table_len = 0; + tree->table_value = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) { + lodepng_free(tree->codes); + lodepng_free(tree->lengths); + lodepng_free(tree->table_len); + lodepng_free(tree->table_value); +} + +/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ +/* values 8u and 9u work the fastest */ +#define FIRSTBITS 9u + +/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, +which is possible in case of only 0 or 1 present symbols. */ +#define INVALIDSYMBOL 65535u + +/* make table for huffman decoding */ +static unsigned HuffmanTree_makeTable(HuffmanTree* tree) { + static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ + static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; + size_t i, numpresent, pointer, size; /*total table size*/ + unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned)); + if(!maxlens) return 83; /*alloc fail*/ + + /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ + lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); + for(i = 0; i < tree->numcodes; i++) { + unsigned symbol = tree->codes[i]; + unsigned l = tree->lengths[i]; + unsigned index; + if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ + /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ + index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); + maxlens[index] = LODEPNG_MAX(maxlens[index], l); + } + /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ + size = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l > FIRSTBITS) size += (1u << (l - FIRSTBITS)); + } + tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len)); + tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value)); + if(!tree->table_len || !tree->table_value) { + lodepng_free(maxlens); + /* freeing tree->table values is done at a higher scope */ + return 83; /*alloc fail*/ + } + /*initialize with an invalid length to indicate unused entries*/ + for(i = 0; i < size; ++i) tree->table_len[i] = 16; + + /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ + pointer = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l <= FIRSTBITS) continue; + tree->table_len[i] = l; + tree->table_value[i] = pointer; + pointer += (1u << (l - FIRSTBITS)); + } + lodepng_free(maxlens); + + /*fill in the first table for short symbols, or secondary table for long symbols*/ + numpresent = 0; + for(i = 0; i < tree->numcodes; ++i) { + unsigned l = tree->lengths[i]; + unsigned symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ + /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ + unsigned reverse = reverseBits(symbol, l); + if(l == 0) continue; + numpresent++; + + if(l <= FIRSTBITS) { + /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ + unsigned num = 1u << (FIRSTBITS - l); + unsigned j; + for(j = 0; j < num; ++j) { + /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ + unsigned index = reverse | (j << l); + if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + tree->table_len[index] = l; + tree->table_value[index] = i; + } + } else { + /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ + /*the FIRSTBITS MSBs of the symbol are the first table index*/ + unsigned index = reverse & mask; + unsigned maxlen = tree->table_len[index]; + /*log2 of secondary table length, should be >= l - FIRSTBITS*/ + unsigned tablelen = maxlen - FIRSTBITS; + unsigned start = tree->table_value[index]; /*starting index in secondary table*/ + unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ + unsigned j; + if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + for(j = 0; j < num; ++j) { + unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ + unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); + tree->table_len[index2] = l; + tree->table_value[index2] = i; + } + } + } + + if(numpresent < 2) { + /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, + but deflate uses 1 bit instead. In case of 0 symbols, no symbols can + appear at all, but such huffman tree could still exist (e.g. if distance + codes are never used). In both cases, not all symbols of the table will be + filled in. Fill them in with an invalid symbol value so returning them from + huffmanDecodeSymbol will cause error. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) { + /* As length, use a value smaller than FIRSTBITS for the head table, + and a value larger than FIRSTBITS for the secondary table, to ensure + valid behavior for advanceBits when reading this symbol. */ + tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); + tree->table_value[i] = INVALIDSYMBOL; + } + } + } else { + /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + If that is not the case (due to too long length codes), the table will not + have been fully used, and this is an error (not all bit combinations can be + decoded): an oversubscribed huffman tree, indicated by error 55. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) return 55; + } + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { + unsigned* blcount; + unsigned* nextcode; + unsigned error = 0; + unsigned bits, n; + + tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ + + if(!error) { + for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) { + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) { + if(tree->lengths[n] != 0) { + tree->codes[n] = nextcode[tree->lengths[n]]++; + /*remove superfluous bits from the code*/ + tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); + } + } + } + + lodepng_free(blcount); + lodepng_free(nextcode); + + if(!error) error = HuffmanTree_makeTable(tree); + return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) { + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode { + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists { + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +/*sort the leaves with stable mergesort*/ +static void bpmnode_sort(BPMNode* leaves, size_t num) { + BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); + size_t width, counter = 0; + for(width = 1; width < num; width *= 2) { + BPMNode* a = (counter & 1) ? mem : leaves; + BPMNode* b = (counter & 1) ? leaves : mem; + size_t p; + for(p = 0; p < num; p += 2 * width) { + size_t q = (p + width > num) ? num : (p + width); + size_t r = (p + 2 * width > num) ? num : (p + 2 * width); + size_t i = p, j = q, k; + for(k = p; k < r; k++) { + if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; + else b[k] = a[j++]; + } + } + counter++; + } + if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num); + lodepng_free(mem); +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } else { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) { + if(frequencies[i] > 0) { + leaves[numpresent].weight = (int)frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + lodepng_memset(lengths, 0, numcodes * sizeof(*lengths)); + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } else if(numpresent == 1) { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } else { + BPMLists lists; + BPMNode* node; + + bpmnode_sort(leaves, numpresent); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code. The bit reader must already have been ensured at least 15 bits +*/ +static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) { + unsigned short code = peekBits(reader, FIRSTBITS); + unsigned short l = codetree->table_len[code]; + unsigned short value = codetree->table_value[code]; + if(l <= FIRSTBITS) { + advanceBits(reader, l); + return value; + } else { + unsigned index2; + advanceBits(reader, FIRSTBITS); + index2 = value + peekBits(reader, l - FIRSTBITS); + advanceBits(reader, codetree->table_len[index2] - FIRSTBITS); + return codetree->table_value[index2]; + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification +Returns error code.*/ +static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { + unsigned error = generateFixedLitLenTree(tree_ll); + if(error) return error; + return generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + LodePNGBitReader* reader) { + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if(!ensureBits17(reader, 14)) return 49; /*error: the bit pointer is or will go past the memory*/ + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBits(reader, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBits(reader, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBits(reader, 4) + 4; + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) return 83 /*alloc fail*/; + + HuffmanTree_init(&tree_cl); + + while(!error) { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { + ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ + } + for(i = 0; i != HCLEN; ++i) { + ensureBits9(reader, 3); /*out of bounds already checked above */ + bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); + } + for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { + bitlen_cl[CLCL_ORDER[i]] = 0; + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); + lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) { + unsigned code; + ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ + code = huffmanDecodeSymbol(reader, &tree_cl); + if(code <= 15) /*a length code*/ { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } else if(code == 16) /*repeat previous*/ { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + replength += readBits(reader, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } else if(code == 17) /*repeat "0" 3-10 times*/ { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else if(code == 18) /*repeat "0" 11-138 times*/ { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else /*if(code == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ +static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, + unsigned btype, size_t max_output_size) { + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); + else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); + + while(!error) /*decode all symbols until end reached, breaks at end code*/ { + /*code_ll is literal, length or end code*/ + unsigned code_ll; + ensureBits25(reader, 20); /* up to 15 for the huffman symbol, up to 5 for the length extra bits */ + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + if(code_ll <= 255) /*literal symbol*/ { + if(!ucvector_resize(out, out->size + 1)) ERROR_BREAK(83 /*alloc fail*/); + out->data[out->size - 1] = (unsigned char)code_ll; + } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if(numextrabits_l != 0) { + /* bits already ensured above */ + length += readBits(reader, numextrabits_l); + } + + /*part 3: get distance code*/ + ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ + code_d = huffmanDecodeSymbol(reader, &tree_d); + if(code_d > 29) { + if(code_d <= 31) { + ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ + } else /* if(code_d == INVALIDSYMBOL) */{ + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if(numextrabits_d != 0) { + /* bits already ensured above */ + distance += readBits(reader, numextrabits_d); + } + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = out->size; + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + if(!ucvector_resize(out, out->size + length)) ERROR_BREAK(83 /*alloc fail*/); + if(distance < length) { + size_t forward; + lodepng_memcpy(out->data + start, out->data + backward, distance); + start += distance; + for(forward = distance; forward < length; ++forward) { + out->data[start++] = out->data[backward++]; + } + } else { + lodepng_memcpy(out->data + start, out->data + backward, length); + } + } else if(code_ll == 256) { + break; /*end code, break the loop*/ + } else /*if(code_ll == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ + } + if(max_output_size && out->size > max_output_size) { + ERROR_BREAK(109); /*error, larger than max size*/ + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, + const LodePNGDecompressSettings* settings) { + size_t bytepos; + size_t size = reader->size; + unsigned LEN, NLEN, error = 0; + + /*go to first boundary of byte*/ + bytepos = (reader->bp + 7u) >> 3u; + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ + LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(!settings->ignore_nlen && LEN + NLEN != 65535) { + return 21; /*error: NLEN is not one's complement of LEN*/ + } + + if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ + + lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); + bytepos += LEN; + + reader->bp = bytepos << 3u; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned BFINAL = 0; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, in, insize); + + if(error) return error; + + while(!BFINAL) { + unsigned BTYPE; + if(!ensureBits9(&reader, 3)) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBits(&reader, 1); + BTYPE = readBits(&reader, 2); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ + else error = inflateHuffmanBlock(out, &reader, BTYPE, settings->max_output_size); /*compression, BTYPE 01 or 10*/ + if(!error && settings->max_output_size && out->size > settings->max_output_size) error = 109; + if(error) break; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + if(settings->custom_inflate) { + unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); + out->allocsize = out->size; + if(error) { + /*the custom inflate is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && out->size > settings->max_output_size) error = 109; + } + return error; + } else { + return lodepng_inflatev(out, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { + /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ + size_t left = 1; + size_t right = array_size - 1; + + while(left <= right) { + size_t mid = (left + right) >> 1; + if(array[mid] >= value) right = mid - 1; + else left = mid + 1; + } + if(left >= array_size || array[left] > value) left--; + return left; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) { + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + size_t pos = values->size; + /*TODO: return error when this fails (out of memory)*/ + unsigned ok = uivector_resize(values, values->size + 4); + if(ok) { + values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX; + values->data[pos + 1] = extra_length; + values->data[pos + 2] = dist_code; + values->data[pos + 3] = extra_distance; + } +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash { + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) { + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) { + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { + unsigned result = 0; + if(pos + 2 < size) { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= ((unsigned)data[pos + 0] << 0u); + result ^= ((unsigned)data[pos + 1] << 4u); + result ^= ((unsigned)data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = (int)wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = (int)wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) { + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) { + if(chainlength++ >= maxchainlength) break; + current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } else { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } else { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else if(length < minmatch || (length == 3 && offset > 4096)) { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, numdeflateblocks = (datasize + 65534u) / 65535u; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + size_t pos = out->size; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + LEN = 65535; + if(datasize - datapos < 65535u) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/ + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u)); + out->data[pos + 0] = firstbyte; + out->data[pos + 1] = (unsigned char)(LEN & 255); + out->data[pos + 2] = (unsigned char)(LEN >> 8u); + out->data[pos + 3] = (unsigned char)(NLEN & 255); + out->data[pos + 4] = (unsigned char)(NLEN >> 8u); + lodepng_memcpy(out->data + pos + 5, data + datapos, LEN); + datapos += LEN; + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) { + unsigned val = lz77_encoded->data[i]; + writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]); + if(val > 256) /*for a length code, 3 more things have to be added*/ { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + writeBits(writer, length_extra_bits, n_length_extra_bits); + writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]); + writeBits(writer, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lengths used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/ + unsigned* frequencies_d = 0; /*frequency of dist codes*/ + unsigned* frequencies_cl = 0; /*frequency of code length codes*/ + unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/ + unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/ + size_t datasize = dataend - datapos; + + /* + If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent + tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are + some analogies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t i; + size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + /* could fit on stack, but >1KB is on the larger side so allocate instead */ + frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll)); + frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d)); + frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/ + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) { + lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll)); + lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d)); + lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(settings->use_lz77) { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } else { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll[symbol]; + if(symbol > 256) { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d[dist]; + i += 3; + } + } + frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15); + if(error) break; + + numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286); + numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30); + /*store the code lengths of both generated trees in bitlen_lld*/ + numcodes_lld = numcodes_ll + numcodes_d; + bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld)); + /*numcodes_lld_e never needs more size than bitlen_lld*/ + bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e)); + if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/ + numcodes_lld_e = 0; + + for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i]; + for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i]; + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != numcodes_lld; ++i) { + unsigned j = 0; /*amount of repetitions*/ + while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j; + + if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { + bitlen_lld_e[numcodes_lld_e++] = 17; + bitlen_lld_e[numcodes_lld_e++] = j - 3; + } else /*repeat code 18 supports max 138 zeroes*/ { + if(j > 138) j = 138; + bitlen_lld_e[numcodes_lld_e++] = 18; + bitlen_lld_e[numcodes_lld_e++] = j - 11; + } + i += (j - 1); + } else if(j >= 3) /*repeat code for value other than zero*/ { + size_t k; + unsigned num = j / 6u, rest = j % 6u; + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + for(k = 0; k < num; ++k) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = 6 - 3; + } + if(rest >= 3) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = rest - 3; + } + else j -= rest; + i += j; + } else /*too short to benefit from repeat code*/ { + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + for(i = 0; i != numcodes_lld_e; ++i) { + ++frequencies_cl[bitlen_lld_e[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl, + NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*compute amount of code-length-code-lengths to output*/ + numcodes_cl = NUM_CODE_LENGTH_CODES; + /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/ + while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) { + numcodes_cl--; + } + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + writeBits(writer, BFINAL, 1); + writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/ + writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies + or in the loop for numcodes_cl above, which saves space. */ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)(numcodes_cl - 4); + writeBits(writer, HLIT, 5); + writeBits(writer, HDIST, 5); + writeBits(writer, HCLEN, 4); + + /*write the code lengths of the code length alphabet ("bitlen_cl")*/ + for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3); + + /*write the lengths of the lit/len AND the dist alphabet*/ + for(i = 0; i != numcodes_lld_e; ++i) { + writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]); + /*extra bits of repeat codes*/ + if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2); + else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3); + else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(tree_ll.lengths[256] == 0) ERROR_BREAK(64); + + /*write the end code*/ + writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + lodepng_free(frequencies_ll); + lodepng_free(frequencies_d); + lodepng_free(frequencies_cl); + lodepng_free(bitlen_lld); + lodepng_free(bitlen_lld_e); + + return error; +} + +static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + error = generateFixedLitLenTree(&tree_ll); + if(!error) error = generateFixedDistanceTree(&tree_d); + + if(!error) { + writeBits(writer, BFINAL, 1); + writeBits(writer, 1, 1); /*first bit of BTYPE*/ + writeBits(writer, 0, 1); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } else /*no LZ77, but still will be Huffman compressed*/ { + for(i = datapos; i < dataend; ++i) { + writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]); + } + } + /*add END code*/ + if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]); + } + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + Hash hash; + LodePNGBitWriter writer; + + LodePNGBitWriter_init(&writer, out); + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8u + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + + if(!error) { + for(i = 0; i != numdeflateblocks && !error; ++i) { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final); + } + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + if(settings->custom_deflate) { + unsigned error = settings->custom_deflate(out, outsize, in, insize, settings); + /*the custom deflate is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { + unsigned s1 = adler & 0xffffu; + unsigned s2 = (adler >> 16u) & 0xffffu; + + while(len != 0u) { + unsigned i; + /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5552u ? 5552u : len; + len -= amount; + for(i = 0; i != amount; ++i) { + s1 += (*data++); + s2 += s1; + } + s1 %= 65521u; + s2 %= 65521u; + } + + return (s2 << 16u) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) { + return update_adler32(1u, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +static unsigned lodepng_zlib_decompressv(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflatev(out, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(out->data, (unsigned)(out->size)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + unsigned error; + if(settings->custom_zlib) { + error = settings->custom_zlib(out, outsize, in, insize, settings); + if(error) { + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && *outsize > settings->max_output_size) error = 109; + } + } else { + ucvector v = ucvector_init(*out, *outsize); + if(expected_size) { + /*reserve the memory to avoid intermediate reallocations*/ + ucvector_resize(&v, *outsize + expected_size); + v.size = *outsize; + } + error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + } + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + *out = NULL; + *outsize = 0; + if(!error) { + *outsize = deflatesize + 6; + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!*out) error = 83; /*alloc fail*/ + } + + if(!error) { + unsigned ADLER32 = adler32(in, (unsigned)insize); + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + (*out)[0] = (unsigned char)(CMFFLG >> 8); + (*out)[1] = (unsigned char)(CMFFLG & 255); + for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i]; + lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32); + } + + lodepng_free(deflatedata); + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(settings->custom_zlib) { + unsigned error = settings->custom_zlib(out, outsize, in, insize, settings); + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + (void)expected_size; + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { + settings->ignore_adler32 = 0; + settings->ignore_nlen = 0; + settings->max_output_size = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifndef LODEPNG_NO_COMPILE_CRC +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length) { + unsigned r = 0xffffffffu; + size_t i; + for(i = 0; i < length; ++i) { + r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u); + } + return r ^ 0xffffffffu; +} +#else /* !LODEPNG_NO_COMPILE_CRC */ +unsigned lodepng_crc32(const unsigned char* data, size_t length); +#endif /* !LODEPNG_NO_COMPILE_CRC */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing PNG color channel bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, +so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + +/* TODO: make this faster */ +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { + unsigned result = 0; + size_t i; + for(i = 0 ; i < nbits; ++i) { + result <<= 1u; + result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); + } + return result; +} + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u)))); + else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) { + return lodepng_read32bitInt(&chunk[0]); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) { + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { + if(lodepng_strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) { + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) { + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + const unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next(chunk, end); + } +} + +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next_const(chunk, end); + } +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk) { + unsigned i; + size_t total_chunk_length, new_length; + unsigned char *chunk_start, *new_buffer; + + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; + if(lodepng_addofl(*outsize, total_chunk_length, &new_length)) return 77; + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outsize) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +/*Sets length and name and allocates the space for data and crc but does not +set data or crc yet. Returns the start of the chunk in chunk. The start of +the data is at chunk + 8. To finalize chunk, add the data, then use +lodepng_chunk_generate_crc */ +static unsigned lodepng_chunk_init(unsigned char** chunk, + ucvector* out, + unsigned length, const char* type) { + size_t new_length = out->size; + if(lodepng_addofl(new_length, length, &new_length)) return 77; + if(lodepng_addofl(new_length, 12, &new_length)) return 77; + if(!ucvector_resize(out, new_length)) return 83; /*alloc fail*/ + *chunk = out->data + new_length - length - 12u; + + /*1: length*/ + lodepng_set32bitInt(*chunk, length); + + /*2: chunk name (4 letters)*/ + lodepng_memcpy(*chunk + 4, type, 4); + + return 0; +} + +/* like lodepng_chunk_create but with custom allocsize */ +static unsigned lodepng_chunk_createv(ucvector* out, + unsigned length, const char* type, const unsigned char* data) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, length, type)); + + /*3: the data*/ + lodepng_memcpy(chunk + 8, data, length); + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, + unsigned length, const char* type, const unsigned char* data) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_chunk_createv(&v, length, type, data); + *out = v.data; + *outsize = v.size; + return error; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types, channels, bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. +Return value is a LodePNG error code.*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) { + switch(colortype) { + case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; + case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; + case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ + default: return 31; /* invalid color type */ + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) { + switch(colortype) { + case LCT_GREY: return 1; + case LCT_RGB: return 3; + case LCT_PALETTE: return 1; + case LCT_GREY_ALPHA: return 2; + case LCT_RGBA: return 4; + case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ + default: return 0; /*invalid color type*/ + } +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) { + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +/*allocates palette memory if needed, and initializes all colors to black*/ +static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) { + size_t i; + /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ + /*the palette must have room for up to 256 colors with 4 bytes each.*/ + if(!info->palette) info->palette = (unsigned char*)lodepng_malloc(1024); + if(!info->palette) return; /*alloc fail*/ + for(i = 0; i != 256; ++i) { + /*Initialize all unused colors with black, the value used for invalid palette indices. + This is an error according to the PNG spec, but common PNG decoders make it black instead. + That makes color conversion slightly faster due to no error handling needed.*/ + info->palette[i * 4 + 0] = 0; + info->palette[i * 4 + 1] = 0; + info->palette[i * 4 + 2] = 0; + info->palette[i * 4 + 3] = 255; + } +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) { + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { + lodepng_color_mode_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); + if(source->palette) { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); + } + return 0; +} + +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { + LodePNGColorMode result; + lodepng_color_mode_init(&result); + result.colortype = colortype; + result.bitdepth = bitdepth; + return result; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) { + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(!info->palette) /*allocate palette if empty*/ { + lodepng_color_mode_alloc_palette(info); + if(!info->palette) return 83; /*alloc fail*/ + } + if(info->palettesize >= 256) { + return 108; /*too many palette values*/ + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +/*calculate bits per pixel out of colortype and bitdepth*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info) { + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) { + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { + size_t i; + for(i = 0; i != info->palettesize; ++i) { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = (size_t)w * (size_t)h; + return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { + return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); +} + + +#ifdef LODEPNG_COMPILE_PNG + +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, +and in addition has one extra byte per line: the filter byte. So this gives a larger +result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) { + /* + 1 for the filter byte, and possibly plus padding bits per line. */ + /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ + size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; + return (size_t)h * line; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*Safely checks whether size_t overflow can be caused due to amount of pixels. +This check is overcautious rather than precise. If this check indicates no overflow, +you can safely compute in a size_t (but not an unsigned): +-(size_t)w * (size_t)h * 8 +-amount of bytes in IDAT (including filter, padding and Adam7 bytes) +-amount of bytes in raw color model +Returns 1 if overflow possible, 0 if not. +*/ +static int lodepng_pixel_overflow(unsigned w, unsigned h, + const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { + size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); + size_t numpixels, total; + size_t line; /* bytes per line in worst case */ + + if(lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; + if(lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ + + /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ + if(lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; + if(lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; + + if(lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ + if(lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ + + return 0; /* no overflow */ +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) { + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->text_num; ++i) { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->text_keys = NULL; + dest->text_strings = NULL; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* key, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + + if(new_keys) info->text_keys = new_keys; + if(new_strings) info->text_strings = new_strings; + + if(!new_keys || !new_strings) return 83; /*alloc fail*/ + + ++info->text_num; + info->text_keys[info->text_num - 1] = alloc_string(key); + info->text_strings[info->text_num - 1] = alloc_string_sized(str, size); + if(!info->text_keys[info->text_num - 1] || !info->text_strings[info->text_num - 1]) return 83; /*alloc fail*/ + + return 0; +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { + return lodepng_add_text_sized(info, key, str, lodepng_strlen(str)); +} + +void lodepng_clear_text(LodePNGInfo* info) { + LodePNGText_cleanup(info); +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) { + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->itext_num; ++i) { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->itext_keys = NULL; + dest->itext_langtags = NULL; + dest->itext_transkeys = NULL; + dest->itext_strings = NULL; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) { + LodePNGIText_cleanup(info); +} + +static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + + if(new_keys) info->itext_keys = new_keys; + if(new_langtags) info->itext_langtags = new_langtags; + if(new_transkeys) info->itext_transkeys = new_transkeys; + if(new_strings) info->itext_strings = new_strings; + + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) return 83; /*alloc fail*/ + + ++info->itext_num; + + info->itext_keys[info->itext_num - 1] = alloc_string(key); + info->itext_langtags[info->itext_num - 1] = alloc_string(langtag); + info->itext_transkeys[info->itext_num - 1] = alloc_string(transkey); + info->itext_strings[info->itext_num - 1] = alloc_string_sized(str, size); + + return 0; +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) { + return lodepng_add_itext_sized(info, key, langtag, transkey, str, lodepng_strlen(str)); +} + +/* same as set but does not delete */ +static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(profile_size == 0) return 100; /*invalid ICC profile size*/ + + info->iccp_name = alloc_string(name); + info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); + + if(!info->iccp_name || !info->iccp_profile) return 83; /*alloc fail*/ + + lodepng_memcpy(info->iccp_profile, profile, profile_size); + info->iccp_profile_size = profile_size; + + return 0; /*ok*/ +} + +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(info->iccp_name) lodepng_clear_icc(info); + info->iccp_defined = 1; + + return lodepng_assign_icc(info, name, profile, profile_size); +} + +void lodepng_clear_icc(LodePNGInfo* info) { + string_cleanup(&info->iccp_name); + lodepng_free(info->iccp_profile); + info->iccp_profile = NULL; + info->iccp_profile_size = 0; + info->iccp_defined = 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) { + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + info->gama_defined = 0; + info->chrm_defined = 0; + info->srgb_defined = 0; + info->iccp_defined = 0; + info->iccp_name = NULL; + info->iccp_profile = NULL; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) { + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + lodepng_clear_icc(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + lodepng_info_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGInfo)); + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + if(source->iccp_defined) { + CERROR_TRY_RETURN(lodepng_assign_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); + } + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8u] = in; + else out[index * bits / 8u] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree { + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) { + lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) { + int i; + for(i = 0; i != 16; ++i) { + if(tree->children[i]) { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + int bit = 0; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") +Returns error code, or 0 if ok*/ +static unsigned color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { + int bit; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + if(!tree->children[i]) return 83; /*alloc fail*/ + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; + return 0; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(mode->colortype == LCT_GREY) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) out[i] = gray; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; + else { + /*take the most significant bits of gray*/ + gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); + addColorBits(out, i, mode->bitdepth, gray); + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } else { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } else if(mode->colortype == LCT_PALETTE) { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) { + out[i * 2 + 0] = gray; + out[i * 2 + 1] = a; + } else if(mode->bitdepth == 16) { + out[i * 4 + 0] = out[i * 4 + 1] = gray; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } else { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) { + if(mode->colortype == LCT_GREY) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 2 + 0] = (gray >> 8) & 255; + out[i * 2 + 1] = gray & 255; + } else if(mode->colortype == LCT_RGB) { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 4 + 0] = (gray >> 8) & 255; + out[i * 4 + 1] = gray & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } else if(mode->colortype == LCT_RGBA) { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } else if(mode->bitdepth == 16) { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } else { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_PALETTE) { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } else { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } else { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to the common case of RGBA with 8 bit per channel. buffer must be RGBA with +enough memory.*/ +static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + unsigned num_channels = 4; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r) buffer[3] = 0; + } + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 3], 3); + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; + } + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + buffer[3] = in[i * 2 + 1]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + buffer[3] = in[i * 4 + 2]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 4); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Similar to getPixelColorsRGBA8, but with 3-channel RGB output.*/ +static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + const unsigned num_channels = 3; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 3); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 4], 3); + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_RGB) { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_GREY_ALPHA) { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } else if(mode->colortype == LCT_RGBA) { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + if(mode_in->colortype == LCT_PALETTE && !mode_in->palette) { + return 107; /* error: must provide palette if input mode is palette */ + } + + if(lodepng_color_mode_equal(mode_out, mode_in)) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + + if(mode_out->colortype == LCT_PALETTE) { + size_t palettesize = mode_out->palettesize; + const unsigned char* palette = mode_out->palette; + size_t palsize = (size_t)1u << mode_out->bitdepth; + /*if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if(palettesize == 0) { + palettesize = mode_in->palettesize; + palette = mode_in->palette; + /*if the input was also palette with same bitdepth, then the color types are also + equal, so copy literally. This to preserve the exact indices that were in the PNG + even in case there are duplicate colors in the palette.*/ + if(mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + } + if(palettesize < palsize) palsize = palettesize; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) { + const unsigned char* p = &palette[i * 4]; + error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); + if(error) break; + } + } + + if(!error) { + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { + for(i = 0; i != numpixels; ++i) { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { + getPixelColorsRGBA8(out, numpixels, in, mode_in); + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { + getPixelColorsRGB8(out, numpixels, in, mode_in); + } else { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if(error) break; + } + } + } + + if(mode_out->colortype == LCT_PALETTE) { + color_tree_cleanup(&tree); + } + + return error; +} + + +/* Converts a single rgb color without alpha from one type to another, color bits truncated to +their bitdepth. In case of single channel (gray or palette), only the r channel is used. Slow +function, do not use to process all pixels of an image. Alpha channel not supported on purpose: +this is for bKGD, supporting alpha may prevent it from finding a color in the palette, from the +specification it looks like bKGD should ignore the alpha values of the palette since it can use +any palette index but doesn't have an alpha channel. Idem with ignoring color key. */ +unsigned lodepng_convert_rgb( + unsigned* r_out, unsigned* g_out, unsigned* b_out, + unsigned r_in, unsigned g_in, unsigned b_in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { + unsigned r = 0, g = 0, b = 0; + unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ + unsigned shift = 16 - mode_out->bitdepth; + + if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { + r = g = b = r_in * mul; + } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { + r = r_in * mul; + g = g_in * mul; + b = b_in * mul; + } else if(mode_in->colortype == LCT_PALETTE) { + if(r_in >= mode_in->palettesize) return 82; + r = mode_in->palette[r_in * 4 + 0] * 257u; + g = mode_in->palette[r_in * 4 + 1] * 257u; + b = mode_in->palette[r_in * 4 + 2] * 257u; + } else { + return 31; + } + + /* now convert to output format */ + if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { + *r_out = r >> shift ; + } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { + *r_out = r >> shift ; + *g_out = g >> shift ; + *b_out = b >> shift ; + } else if(mode_out->colortype == LCT_PALETTE) { + unsigned i; + /* a 16-bit color cannot be in the palette */ + if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; + for(i = 0; i < mode_out->palettesize; i++) { + unsigned j = i * 4; + if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && + (b >> 8) == mode_out->palette[j + 2]) { + *r_out = i; + return 0; + } + } + return 82; + } else { + return 31; + } + + return 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER + +void lodepng_color_stats_init(LodePNGColorStats* stats) { + /*stats*/ + stats->colored = 0; + stats->key = 0; + stats->key_r = stats->key_g = stats->key_b = 0; + stats->alpha = 0; + stats->numcolors = 0; + stats->bits = 1; + stats->numpixels = 0; + /*settings*/ + stats->allow_palette = 1; + stats->allow_greyscale = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorStats(LodePNGColorStats* p) { + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) { + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*stats must already have been inited. */ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + /* mark things as done already if it would be impossible to have a more expensive case */ + unsigned colored_done = lodepng_is_greyscale_type(mode_in) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode_in) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode_in); + unsigned bits_done = (stats->bits == 1 && bpp == 1) ? 1 : 0; + unsigned sixteen = 0; /* whether the input image is 16 bit */ + unsigned maxnumcolors = 257; + if(bpp <= 8) maxnumcolors = LODEPNG_MIN(257, stats->numcolors + (1u << bpp)); + + stats->numpixels += numpixels; + + /*if palette not allowed, no need to compute numcolors*/ + if(!stats->allow_palette) numcolors_done = 1; + + color_tree_init(&tree); + + /*If the stats was already filled in from previous data, fill its palette in tree + and mark things as done already if we know they are the most expensive case already*/ + if(stats->alpha) alpha_done = 1; + if(stats->colored) colored_done = 1; + if(stats->bits == 16) numcolors_done = 1; + if(stats->bits >= bpp) bits_done = 1; + if(stats->numcolors >= maxnumcolors) numcolors_done = 1; + + if(!numcolors_done) { + for(i = 0; i < stats->numcolors; i++) { + const unsigned char* color = &stats->palette[i * 4]; + error = color_tree_add(&tree, color[0], color[1], color[2], color[3], i); + if(error) goto cleanup; + } + } + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode_in->bitdepth == 16 && !sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { + stats->bits = 16; + sixteen = 1; + bits_done = 1; + numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + break; + } + } + } + + if(sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 65535 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 65535 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + } + } else /* < 16-bit */ { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + + if(!bits_done && stats->bits < 8) { + /*only r is checked, < 8 bits is only relevant for grayscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > stats->bits) stats->bits = bits; + } + bits_done = (stats->bits >= bpp); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 255 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 255 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) { + if(!color_tree_has(&tree, r, g, b, a)) { + error = color_tree_add(&tree, r, g, b, a, stats->numcolors); + if(error) goto cleanup; + if(stats->numcolors < 256) { + unsigned char* p = stats->palette; + unsigned n = stats->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++stats->numcolors; + numcolors_done = stats->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + } + + /*make the stats's key always 16-bit for consistency - repeat each byte twice*/ + stats->key_r += (stats->key_r << 8); + stats->key_g += (stats->key_g << 8); + stats->key_b += (stats->key_b << 8); + } + +cleanup: + color_tree_cleanup(&tree); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*Adds a single color to the color stats. The stats must already have been inited. The color must be given as 16-bit +(with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for +all pixels of an image but only for a few additional values. */ +static unsigned lodepng_color_stats_add(LodePNGColorStats* stats, + unsigned r, unsigned g, unsigned b, unsigned a) { + unsigned error = 0; + unsigned char image[8]; + LodePNGColorMode mode; + lodepng_color_mode_init(&mode); + image[0] = r >> 8; image[1] = r; image[2] = g >> 8; image[3] = g; + image[4] = b >> 8; image[5] = b; image[6] = a >> 8; image[7] = a; + mode.bitdepth = 16; + mode.colortype = LCT_RGBA; + error = lodepng_compute_color_stats(stats, image, 1, 1, &mode); + lodepng_color_mode_cleanup(&mode); + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Computes a minimal PNG color model that can contain all colors as indicated by the stats. +The stats should be computed with lodepng_compute_color_stats. +mode_in is raw color profile of the image the stats were computed on, to copy palette order from when relevant. +Minimal PNG color model means the color type and bit depth that gives smallest amount of bits in the output image, +e.g. gray if only grayscale pixels, palette if less than 256 colors, color key if only single transparent color, ... +This is used if auto_convert is enabled (it is by default). +*/ +static unsigned auto_choose_color(LodePNGColorMode* mode_out, + const LodePNGColorMode* mode_in, + const LodePNGColorStats* stats) { + unsigned error = 0; + unsigned palettebits; + size_t i, n; + size_t numpixels = stats->numpixels; + unsigned palette_ok, gray_ok; + + unsigned alpha = stats->alpha; + unsigned key = stats->key; + unsigned bits = stats->bits; + + mode_out->key_defined = 0; + + if(key && numpixels <= 16) { + alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + key = 0; + if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + + gray_ok = !stats->colored; + if(!stats->allow_greyscale) gray_ok = 0; + if(!gray_ok && bits < 8) bits = 8; + + n = stats->numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && bits <= 8 && n != 0; /*n==0 means likely numcolors wasn't computed*/ + if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(gray_ok && !alpha && bits <= palettebits) palette_ok = 0; /*gray is less overhead*/ + if(!stats->allow_palette) palette_ok = 0; + + if(palette_ok) { + const unsigned char* p = stats->palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != stats->numcolors; ++i) { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); + lodepng_color_mode_copy(mode_out, mode_in); + } + } else /*8-bit or 16-bit per channel*/ { + mode_out->bitdepth = bits; + mode_out->colortype = alpha ? (gray_ok ? LCT_GREY_ALPHA : LCT_RGBA) + : (gray_ok ? LCT_GREY : LCT_RGB); + if(key) { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*stats always uses 16-bit, mask converts it*/ + mode_out->key_r = stats->key_r & mask; + mode_out->key_g = stats->key_g & mask; + mode_out->key_b = stats->key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predictor, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) { + short pa = LODEPNG_ABS(b - c); + short pb = LODEPNG_ABS(a - c); + short pc = LODEPNG_ABS(a + b - c - c); + /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ + if(pb < pa) { a = b; pa = pb; } + return (pc < pa) ? c : a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned width, height; + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + /* TODO: remove this. One should use a new LodePNGState for new sessions */ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(lodepng_chunk_length(in + 8) != 13) { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + width = lodepng_read32bitInt(&in[16]); + height = lodepng_read32bitInt(&in[20]); + /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ + if(w) *w = width; + if(h) *h = height; + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + /*errors returned only after the parsing so other values are still output*/ + + /*error: invalid image size*/ + if(width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); + /*error: invalid colortype or bitdepth combination*/ + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + if(state->error) return state->error; + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + if(!state->decoder.ignore_crc) { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) { + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + recon[j]; + break; + } + case 2: + if(precon) { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } else { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); + /* Unroll independent paths of this predictor. A 6x and 8x version is also possible but that adds + too much code. Whether this speeds up anything depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4, j += 4) { + unsigned char s0 = scanline[i + 0], r0 = recon[j + 0], p0 = precon[i + 0]; + unsigned char s1 = scanline[i + 1], r1 = recon[j + 1], p1 = precon[i + 1]; + unsigned char s2 = scanline[i + 2], r2 = recon[j + 2], p2 = precon[i + 2]; + unsigned char s3 = scanline[i + 3], r3 = recon[j + 3], p3 = precon[i + 3]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + recon[i + 2] = s2 + ((r2 + p2) >> 1u); + recon[i + 3] = s3 + ((r3 + p3) >> 1u); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3, j += 3) { + unsigned char s0 = scanline[i + 0], r0 = recon[j + 0], p0 = precon[i + 0]; + unsigned char s1 = scanline[i + 1], r1 = recon[j + 1], p1 = precon[i + 1]; + unsigned char s2 = scanline[i + 2], r2 = recon[j + 2], p2 = precon[i + 2]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + recon[i + 2] = s2 + ((r2 + p2) >> 1u); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2, j += 2) { + unsigned char s0 = scanline[i + 0], r0 = recon[j + 0], p0 = precon[i + 0]; + unsigned char s1 = scanline[i + 1], r1 = recon[j + 1], p1 = precon[i + 1]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + } + } + for(; i != length; ++i, ++j) recon[i] = scanline[i] + ((recon[j] + precon[i]) >> 1u); + } else { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + (recon[j] >> 1u); + } + break; + case 4: + if(precon) { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + + /* Unroll independent paths of the paeth predictor. A 6x and 8x version is also possible but that + adds too much code. Whether this speeds up anything depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4, j += 4) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + recon[i + 3] = s3 + paethPredictor(r3, p3, q3); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3, j += 3) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2, j += 2) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + } + } + + for(; i != length; ++i, ++j) { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[j])); + } + } else { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) { + recon[i] = scanline[i]; + } + for(i = bytewidth; i != length; ++i, ++j) { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[j]); + } + } + break; + default: return 36; /*error: invalid filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + for(y = 0; y < h; ++y) { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) { + size_t x; + for(x = 0; x < olinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) { + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) { + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); + } + /*we can immediately filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned pos = 0, i; + color->palettesize = chunkLength / 3u; + if(color->palettesize == 0 || color->palettesize > 256) return 38; /*error: palette too small or big*/ + lodepng_color_mode_alloc_palette(color); + if(!color->palette && color->palettesize) { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + + for(i = 0; i != color->palettesize; ++i) { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned i; + if(color->colortype == LCT_PALETTE) { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 39; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } else if(color->colortype == LCT_GREY) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } else if(color->colortype == LCT_RGB) { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(info->color.colortype == LCT_PALETTE) { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + /*error: invalid palette index, or maybe this chunk appeared before PLTE*/ + if(data[0] >= info->color.palettesize) return 103; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 44; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + /*error: this chunk must be 6 bytes for grayscale image*/ + if(chunkLength != 6) return 45; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + char *key = 0, *str = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = (unsigned)(chunkLength < string2_begin ? 0 : chunkLength - string2_begin); + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(str, data + string2_begin, length); + str[length] = 0; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + char *key = 0; + unsigned char* str = 0; + size_t size = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[string2_begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(error) break; + error = lodepng_add_text_sized(info, key, (char*)str, size); + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(langtag, data + begin, length); + langtag[length] = 0; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(transkey, data + begin, length); + transkey[length] = 0; + + /*read the actual text*/ + begin += length + 1; + + length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; + + if(compressed) { + unsigned char* str = 0; + size_t size = 0; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(!error) error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)str, size); + lodepng_free(str); + } else { + error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)(data + begin), length); + } + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} + +static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ + + info->gama_defined = 1; + info->gama_gamma = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + + return 0; /* OK */ +} + +static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ + + info->chrm_defined = 1; + info->chrm_white_x = 16777216u * data[ 0] + 65536u * data[ 1] + 256u * data[ 2] + data[ 3]; + info->chrm_white_y = 16777216u * data[ 4] + 65536u * data[ 5] + 256u * data[ 6] + data[ 7]; + info->chrm_red_x = 16777216u * data[ 8] + 65536u * data[ 9] + 256u * data[10] + data[11]; + info->chrm_red_y = 16777216u * data[12] + 65536u * data[13] + 256u * data[14] + data[15]; + info->chrm_green_x = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; + info->chrm_green_y = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; + info->chrm_blue_x = 16777216u * data[24] + 65536u * data[25] + 256u * data[26] + data[27]; + info->chrm_blue_y = 16777216u * data[28] + 65536u * data[29] + 256u * data[30] + data[31]; + + return 0; /* OK */ +} + +static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ + + info->srgb_defined = 1; + info->srgb_intent = data[0]; + + return 0; /* OK */ +} + +static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + size_t size = 0; + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + + info->iccp_defined = 1; + if(info->iccp_name) lodepng_clear_icc(info); + + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) return 75; /*no null termination, corrupt?*/ + if(length < 1 || length > 79) return 89; /*keyword too short or long*/ + + info->iccp_name = (char*)lodepng_malloc(length + 1); + if(!info->iccp_name) return 83; /*alloc fail*/ + + info->iccp_name[length] = 0; + for(i = 0; i != length; ++i) info->iccp_name[i] = (char)data[i]; + + if(data[length + 1] != 0) return 72; /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) return 75; /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_icc_size; + error = zlib_decompress(&info->iccp_profile, &size, 0, + &data[string2_begin], + length, &zlibsettings); + /*error: ICC profile larger than decoder->max_icc_size*/ + if(error && size > zlibsettings.max_output_size) error = 113; + info->iccp_profile_size = size; + if(!error && !info->iccp_profile_size) error = 100; /*invalid ICC profile size*/ + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize) { + const unsigned char* chunk = in + pos; + unsigned chunkLength; + const unsigned char* data; + unsigned unhandled = 0; + unsigned error = 0; + + if(pos + 4 > insize) return 30; + chunkLength = lodepng_chunk_length(chunk); + if(chunkLength > 2147483647) return 63; + data = lodepng_chunk_data_const(chunk); + if(data + chunkLength + 4 > in + insize) return 30; + + if(lodepng_chunk_type_equals(chunk, "PLTE")) { + error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + error = readChunk_tRNS(&state->info_png.color, data, chunkLength); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + error = readChunk_bKGD(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + error = readChunk_tEXt(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + error = readChunk_tIME(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + error = readChunk_pHYs(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + error = readChunk_gAMA(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + error = readChunk_cHRM(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + error = readChunk_sRGB(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else { + /* unhandled chunk is ok (is not an error) */ + unhandled = 1; + } + + if(!error && !unhandled && !state->decoder.ignore_crc) { + if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ + } + + return error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned char IEND = 0; + const unsigned char* chunk; + unsigned char* idat; /*the data from idat chunks, zlib compressed*/ + size_t idatsize = 0; + unsigned char* scanlines = 0; + size_t scanlines_size = 0, expected_size = 0; + size_t outsize = 0; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { + CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ + } + + /*the input filesize is a safe upper bound for the sum of idat chunks size*/ + idat = (unsigned char*)lodepng_malloc(insize); + if(!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 30); + } + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 63); + } + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + unknown = 0; + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) { + size_t newsize; + if(lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); + if(newsize > insize) CERROR_BREAK(state->error, 95); + lodepng_memcpy(idat + idatsize, data, chunkLength); + idatsize += chunkLength; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "IEND")) { + /*IEND chunk*/ + IEND = 1; + } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { + /*palette chunk (PLTE)*/ + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled + in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that + affects the alpha channel of pixels. */ + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + /*text chunk (tEXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + /*compressed text chunk (zTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + /*international text chunk (iTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + state->error = readChunk_gAMA(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + state->error = readChunk_cHRM(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + state->error = readChunk_sRGB(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + state->error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { + CERROR_BREAK(state->error, 69); + } + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); + } + + if(!state->error && state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { + state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ + } + + if(!state->error) { + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); + } else { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ + expected_size = 0; + expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); + if(*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); + if(*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); + if(*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); + } + + state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); + } + if(!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ + lodepng_free(idat); + + if(!state->error) { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!*out) state->error = 83; /*alloc fail*/ + } + if(!state->error) { + lodepng_memset(*out, 0, outsize); + state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); + } + lodepng_free(scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } else { /*color conversion needed*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*disable reading things that this function doesn't output*/ + state.decoder.read_text_chunks = 0; + state.decoder.remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + size_t buffersize; + unsigned error; + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; + settings->max_text_size = 16777216; + settings->max_icc_size = 16777216; /* 16MB is much more than enough for any reasonable ICC profile */ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + settings->ignore_critical = 0; + settings->ignore_end = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) { +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) { + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +static unsigned writeSignature(ucvector* out) { + size_t pos = out->size; + const unsigned char signature[] = {137, 80, 78, 71, 13, 10, 26, 10}; + /*8 bytes PNG signature, aka the magic bytes*/ + if(!ucvector_resize(out, out->size + 8)) return 83; /*alloc fail*/ + lodepng_memcpy(out->data + pos, signature, 8); + return 0; +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { + unsigned char *chunk, *data; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 13, "IHDR")); + data = chunk + 8; + + lodepng_set32bitInt(data + 0, w); /*width*/ + lodepng_set32bitInt(data + 4, h); /*height*/ + data[8] = (unsigned char)bitdepth; /*bit depth*/ + data[9] = (unsigned char)colortype; /*color type*/ + data[10] = 0; /*compression method*/ + data[11] = 0; /*filter method*/ + data[12] = interlace_method; /*interlace method*/ + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +/* only adds the chunk if needed (there is a key or palette with alpha) */ +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk; + size_t i, j = 8; + + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE")); + + for(i = 0; i != info->palettesize; ++i) { + /*add all channels except alpha channel*/ + chunk[j++] = info->palette[i * 4 + 0]; + chunk[j++] = info->palette[i * 4 + 1]; + chunk[j++] = info->palette[i * 4 + 2]; + } + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk = 0; + + if(info->colortype == LCT_PALETTE) { + size_t i, amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) { + if(info->palette[4 * (i - 1) + 3] != 255) break; + --amount; + } + if(amount) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, amount, "tRNS")); + /*add the alpha channel values from the palette*/ + for(i = 0; i != amount; ++i) chunk[8 + i] = info->palette[4 * i + 3]; + } + } else if(info->colortype == LCT_GREY) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + } + } else if(info->colortype == LCT_RGB) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + chunk[10] = (unsigned char)(info->key_g >> 8); + chunk[11] = (unsigned char)(info->key_g & 255); + chunk[12] = (unsigned char)(info->key_b >> 8); + chunk[13] = (unsigned char)(info->key_b & 255); + } + } + + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* zlib = 0; + size_t zlibsize = 0; + + error = zlib_compress(&zlib, &zlibsize, data, datasize, zlibsettings); + if(!error) { + error = lodepng_chunk_createv(out, zlibsize, "IDAT", zlib); + } + lodepng_free(zlib); + return error; +} + +static unsigned addChunk_IEND(ucvector* out) { + return lodepng_chunk_createv(out, 0, "IEND", 0); +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { + unsigned char* chunk = 0; + size_t keysize = lodepng_strlen(keyword), textsize = lodepng_strlen(textstring); + size_t size = keysize + 1 + textsize; + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, size, "tEXt")); + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + lodepng_memcpy(chunk + 9 + keysize, textstring, textsize); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword); + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "zTXt"); + } + if(!error) { + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword), langsize = lodepng_strlen(langtag), transsize = lodepng_strlen(transkey); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + if(compress) { + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + } + if(!error) { + size_t size = keysize + 3 + langsize + 1 + transsize + 1 + (compress ? compressedsize : textsize); + error = lodepng_chunk_init(&chunk, out, size, "iTXt"); + } + if(!error) { + size_t pos = 8; + lodepng_memcpy(chunk + pos, keyword, keysize); + pos += keysize; + chunk[pos++] = 0; /*null termination char*/ + chunk[pos++] = (compress ? 1 : 0); /*compression flag*/ + chunk[pos++] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + pos, langtag, langsize); + pos += langsize; + chunk[pos++] = 0; /*null termination char*/ + lodepng_memcpy(chunk + pos, transkey, transsize); + pos += transsize; + chunk[pos++] = 0; /*null termination char*/ + if(compress) { + lodepng_memcpy(chunk + pos, compressed, compressedsize); + } else { + lodepng_memcpy(chunk + pos, textstring, textsize); + } + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk = 0; + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + chunk[10] = (unsigned char)(info->background_g >> 8); + chunk[11] = (unsigned char)(info->background_g & 255); + chunk[12] = (unsigned char)(info->background_b >> 8); + chunk[13] = (unsigned char)(info->background_b & 255); + } else if(info->color.colortype == LCT_PALETTE) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "bKGD")); + chunk[8] = (unsigned char)(info->background_r & 255); /*palette index*/ + } + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 7, "tIME")); + chunk[8] = (unsigned char)(time->year >> 8); + chunk[9] = (unsigned char)(time->year & 255); + chunk[10] = (unsigned char)time->month; + chunk[11] = (unsigned char)time->day; + chunk[12] = (unsigned char)time->hour; + chunk[13] = (unsigned char)time->minute; + chunk[14] = (unsigned char)time->second; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 9, "pHYs")); + lodepng_set32bitInt(chunk + 8, info->phys_x); + lodepng_set32bitInt(chunk + 12, info->phys_y); + chunk[16] = info->phys_unit; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "gAMA")); + lodepng_set32bitInt(chunk + 8, info->gama_gamma); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 32, "cHRM")); + lodepng_set32bitInt(chunk + 8, info->chrm_white_x); + lodepng_set32bitInt(chunk + 12, info->chrm_white_y); + lodepng_set32bitInt(chunk + 16, info->chrm_red_x); + lodepng_set32bitInt(chunk + 20, info->chrm_red_y); + lodepng_set32bitInt(chunk + 24, info->chrm_green_x); + lodepng_set32bitInt(chunk + 28, info->chrm_green_y); + lodepng_set32bitInt(chunk + 32, info->chrm_blue_x); + lodepng_set32bitInt(chunk + 36, info->chrm_blue_y); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { + unsigned char data = info->srgb_intent; + return lodepng_chunk_createv(out, 1, "sRGB", &data); +} + +static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t keysize = lodepng_strlen(info->iccp_name); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + error = zlib_compress(&compressed, &compressedsize, + info->iccp_profile, info->iccp_profile_size, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "iCCP"); + } + if(!error) { + lodepng_memcpy(chunk + 8, info->iccp_name, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) { + size_t i; + switch(filterType) { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } else { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); + } + break; + case 4: /*Paeth*/ + if(prevline) { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*invalid filter type given*/ + } +} + +/* integer binary logarithm, max return value is 31 */ +static size_t ilog2(size_t i) { + size_t result = 0; + if(i >= 65536) { result += 16; i >>= 16; } + if(i >= 256) { result += 8; i >>= 8; } + if(i >= 16) { result += 4; i >>= 4; } + if(i >= 4) { result += 2; i >>= 2; } + if(i >= 2) { result += 1; /*i >>= 1;*/ } + return result; +} + +/* integer approximation for i * log2(i), helper function for LFS_ENTROPY */ +static size_t ilog2i(size_t i) { + size_t l; + if(i == 0) return 0; + l = ilog2(i); + /* approximate i*log2(i): l is integer logarithm, ((i - (1u << l)) << 1u) + linearly approximates the missing fractional part multiplied by i */ + return i * l + ((i - (1u << l)) << 1u); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* color, const LodePNGEncoderSettings* settings) { + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7u) / 8u, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(color); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (color->colortype == LCT_PALETTE || color->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy >= LFS_ZERO && strategy <= LFS_FOUR) { + unsigned char type = (unsigned char)strategy; + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_MINSUM) { + /*adaptive filtering*/ + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + if(type == 0) { + for(x = 0; x != linebytes; ++x) sum += (unsigned char)(attempt[type][x]); + } else { + for(x = 0; x != linebytes; ++x) { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type][x]; + sum += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum < smallest) { + bestType = type; + smallest = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_ENTROPY) { + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t bestSum = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + lodepng_memset(count, 0, 256 * sizeof(*count)); + for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + for(x = 0; x != 256; ++x) { + sum += ilog2i(count[x]); + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum > bestSum) { + bestType = type; + bestSum = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_PREDEFINED) { + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_BRUTE_FORCE) { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings; + lodepng_memcpy(&zlibsettings, &settings->zlibsettings, sizeof(LodePNGCompressSettings)); + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + if(!error) { + for(y = 0; y != h; ++y) /*try the 5 filter types*/ { + for(type = 0; type != 5; ++type) { + unsigned testsize = (unsigned)linebytes; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) { + size_t x; + for(x = 0; x < ilinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= possible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) { + *outsize = h + (h * ((w * bpp + 7u) / 8u)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7u) / 8u)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) { + addPaddingBits(padded, in, ((w * bpp + 7u) / 8u) * 8u, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } else { + /*we can immediately filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) { + if(bpp < 8) { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7u) / 8u) * 8u, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } else { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk, data + datasize); + } + return 0; +} + +static unsigned isGrayICCProfile(const unsigned char* profile, unsigned size) { + /* + It is a gray profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 + are "RGB ". We do not perform any full parsing of the ICC profile here, other + than check those 4 bytes to grayscale profile. Other than that, validity of + the profile is not checked. This is needed only because the PNG specification + requires using a non-gray color model if there is an ICC profile with "RGB " + (sadly limiting compression opportunities if the input data is grayscale RGB + data), and requires using a gray color model if it is "GRAY". + */ + if(size < 20) return 0; + return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; +} + +static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { + /* See comment in isGrayICCProfile*/ + if(size < 20) return 0; + return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) { + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + ucvector outv = ucvector_init(NULL, 0); + LodePNGInfo info; + const LodePNGInfo* info_png = &state->info_png; + + lodepng_info_init(&info); + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + /*check input values validity*/ + if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) { + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + goto cleanup; + } + if(state->encoder.zlibsettings.btype > 2) { + state->error = 61; /*error: invalid btype*/ + goto cleanup; + } + if(info_png->interlace_method > 1) { + state->error = 71; /*error: invalid interlace mode*/ + goto cleanup; + } + state->error = checkColorValidity(info_png->color.colortype, info_png->color.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + + /* color convert and compute scanline filter types */ + lodepng_info_copy(&info, &state->info_png); + if(state->encoder.auto_convert) { + LodePNGColorStats stats; + lodepng_color_stats_init(&stats); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined && + isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use palette with a GRAY ICC profile, even + if the palette has only gray colors, so disallow it.*/ + stats.allow_palette = 0; + } + if(info_png->iccp_defined && + isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use grayscale color with RGB ICC profile, so disallow gray.*/ + stats.allow_greyscale = 0; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = lodepng_compute_color_stats(&stats, image, w, h, &state->info_raw); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->background_defined) { + /*the background chunk's color must be taken into account as well*/ + unsigned r = 0, g = 0, b = 0; + LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16); + lodepng_convert_rgb(&r, &g, &b, info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color); + state->error = lodepng_color_stats_add(&stats, r, g, b, 65535); + if(state->error) goto cleanup; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = auto_choose_color(&info.color, &state->info_raw, &stats); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*also convert the background chunk*/ + if(info_png->background_defined) { + if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, + info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) { + state->error = 104; + goto cleanup; + } + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined) { + unsigned gray_icc = isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned rgb_icc = isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned gray_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; + if(!gray_icc && !rgb_icc) { + state->error = 100; /* Disallowed profile color type for PNG */ + goto cleanup; + } + if(gray_icc != gray_png) { + /*Not allowed to use RGB/RGBA/palette with GRAY ICC profile or vice versa, + or in case of auto_convert, it wasn't possible to find appropriate model*/ + state->error = state->encoder.auto_convert ? 102 : 101; + goto cleanup; + } + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { + unsigned char* converted; + size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7u) / 8u; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + if(!state->error) { + state->error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + } + lodepng_free(converted); + if(state->error) goto cleanup; + } else { + state->error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + if(state->error) goto cleanup; + } + + /* output all PNG chunks */ { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + state->error = writeSignature(&outv); + if(state->error) goto cleanup; + /*IHDR*/ + state->error = addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) goto cleanup; + } + /*color profile chunks must come before PLTE */ + if(info.iccp_defined) { + state->error = addChunk_iCCP(&outv, &info, &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + if(info.srgb_defined) { + state->error = addChunk_sRGB(&outv, &info); + if(state->error) goto cleanup; + } + if(info.gama_defined) { + state->error = addChunk_gAMA(&outv, &info); + if(state->error) goto cleanup; + } + if(info.chrm_defined) { + state->error = addChunk_cHRM(&outv, &info); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) { + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { + /*force_palette means: write suggested palette for truecolor in PLTE chunk*/ + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + /*tRNS (this will only add if when necessary) */ + state->error = addChunk_tRNS(&outv, &info.color); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) { + state->error = addChunk_bKGD(&outv, &info); + if(state->error) goto cleanup; + } + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) { + state->error = addChunk_pHYs(&outv, &info); + if(state->error) goto cleanup; + } + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) { + state->error = addChunk_tIME(&outv, &info.time); + if(state->error) goto cleanup; + } + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) { + if(lodepng_strlen(info.text_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.text_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + if(state->encoder.text_compression) { + state->error = addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } else { + state->error = addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + if(state->error) goto cleanup; + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) { + unsigned already_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) { + const char* k = info.text_keys[i]; + /* Could use strcmp, but we're not calling or reimplementing this C library function for this use only */ + if(k[0] == 'L' && k[1] == 'o' && k[2] == 'd' && k[3] == 'e' && + k[4] == 'P' && k[5] == 'N' && k[6] == 'G' && k[7] == '\0') { + already_added_id_text = 1; + break; + } + } + if(already_added_id_text == 0) { + state->error = addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + if(state->error) goto cleanup; + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) { + if(lodepng_strlen(info.itext_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.itext_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + state->error = addChunk_iTXt( + &outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + state->error = addChunk_IEND(&outv); + if(state->error) goto cleanup; + } + +cleanup: + lodepng_info_cleanup(&info); + lodepng_free(data); + + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) { + switch(code) { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + /*this error could happen if there are only 0 or 1 symbols present in the huffman code:*/ + case 16: return "invalid code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too small or too big"; /*0, or more than 256 colors*/ + case 39: return "tRNS chunk before PLTE or has more entries than palette size"; + case 40: return "tRNS chunk has wrong size for grayscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for grayscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lengths. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to grayscale conversion formula to the user.*/ + case 62: return "conversion from color to grayscale not supported"; + /*(2^31-1)*/ + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "invalid interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, invalid compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette, or index out of bounds"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "integer overflow due to too many pixels"; + case 93: return "zero width or height is invalid"; + case 94: return "header chunk must have a size of 13 bytes"; + case 95: return "integer overflow with combined idat chunk size"; + case 96: return "invalid gAMA chunk size"; + case 97: return "invalid cHRM chunk size"; + case 98: return "invalid sRGB chunk size"; + case 99: return "invalid sRGB rendering intent"; + case 100: return "invalid ICC profile color type, the PNG specification only allows RGB or GRAY"; + case 101: return "PNG specification does not allow RGB ICC profile on gray color types and vice versa"; + case 102: return "not allowed to set grayscale ICC profile with colored pixels by PNG specification"; + case 103: return "invalid palette index in bKGD chunk. Maybe it came before PLTE chunk?"; + case 104: return "invalid bKGD color while encoding (e.g. palette index out of range)"; + case 105: return "integer overflow of bitsize"; + case 106: return "PNG file must have PLTE chunk if color type is palette"; + case 107: return "color convert from palette mode requested without setting the palette data in it"; + case 108: return "tried to add more than 256 values to a palette"; + /*this limit can be configured in LodePNGDecompressSettings*/ + case 109: return "tried to decompress zlib or deflate data larger than desired max_output_size"; + case 110: return "custom zlib or inflate decompression failed"; + case 111: return "custom zlib or deflate compression failed"; + /*max text size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large text sizes.*/ + case 112: return "compressed text unreasonably large"; + /*max ICC size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large ICC profile*/ + case 113: return "ICC profile unreasonably large"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { + +#ifdef LODEPNG_COMPILE_DISK +unsigned load_file(std::vector<unsigned char>& buffer, const std::string& filename) { + long size = lodepng_filesize(filename.c_str()); + if(size < 0) return 78; + buffer.resize((size_t)size); + return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned save_file(const std::vector<unsigned char>& buffer, const std::string& filename) { + return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, 0, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in, + const LodePNGDecompressSettings& settings) { + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in, + const LodePNGCompressSettings& settings) { + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() { + lodepng_state_init(this); +} + +State::State(const State& other) { + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() { + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) { + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + const std::vector<unsigned char>& in, LodePNGColorType colortype, unsigned bitdepth) { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) { + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + State& state, + const std::vector<unsigned char>& in) { + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector<unsigned char> buffer; + /* safe output values in case error happens */ + w = h = 0; + unsigned error = load_file(buffer, filename); + if(error) return error; + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector<unsigned char>& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector<unsigned char>& out, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector<unsigned char>& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector<unsigned char>& out, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + State& state) { + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector<unsigned char> buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) error = save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ diff --git a/gbdk/gbdk-support/png2asset/lodepng.h b/gbdk/gbdk-support/png2asset/lodepng.h new file mode 100644 index 00000000..3e1da92d --- /dev/null +++ b/gbdk/gbdk-support/png2asset/lodepng.h @@ -0,0 +1,2019 @@ +/* +LodePNG version 20210627 + +Copyright (c) 2005-2021 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include <string.h> /*for size_t*/ + +extern const char* LODEPNG_VERSION_STRING; + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc. +In addition to those below, you can also define LODEPNG_NO_COMPILE_CRC to +allow implementing a custom lodepng_crc32. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +#define LODEPNG_COMPILE_ZLIB +#endif + +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +#define LODEPNG_COMPILE_PNG +#endif + +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_COMPILE_DECODER +#endif + +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +#define LODEPNG_COMPILE_ENCODER +#endif + +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +#define LODEPNG_COMPILE_DISK +#endif + +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif + +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +#define LODEPNG_COMPILE_ERROR_TEXT +#endif + +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +#define LODEPNG_COMPILE_ALLOCATORS +#endif + +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_CPP +#include <vector> +#include <string> +#endif /*LODEPNG_COMPILE_CPP*/ + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw image).*/ +typedef enum LodePNGColorType { + LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ + LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ + /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid + byte value from 0 to 255 that could be present in an invalid PNG file header. Do + not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use + the valid color type names above, or numeric values like 1 or 7 when checking for + particular disallowed color type byte values, or cast to integer to print it.*/ + LCT_MAX_OCTET_VALUE = 255 +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. + +NOTE: This overwrites existing files without warning! + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + const std::vector<unsigned char>& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector<unsigned char>& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector<unsigned char>& out, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. + +NOTE: This overwrites existing files without warning! + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings { + /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ + + /*Maximum decompressed size, beyond this the decoder may (and is encouraged to) stop decoding, + return an error, output a data size > max_output_size and all the data up to that point. This is + not hard limit nor a guarantee, but can prevent excessive memory usage. This setting is + ignored by the PNG decoder, but is used by the deflate/zlib decoder and can be used by custom ones. + Set to 0 to impose no limit (the default).*/ + size_t max_output_size; + + /*use custom zlib decoder instead of built in one (default: null). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ { + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode { + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + This field may not be allocated directly, use lodepng_color_mode_init first, + then lodepng_palette_add per color to correctly initialize it (to ensure size + of exactly 1024 bytes). + + The alpha channels must be set as well, set them to 255 for opaque images. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ + size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For grayscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/grayscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); +/* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */ +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a grayscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime { + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo { + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + Suggested background color chunk (bKGD) + + This uses the same color mode and bit depth as the PNG (except no alpha channel), + with values truncated to the bit depth in the unsigned integer. + + For grayscale and palette PNGs, the value is stored in background_r. The values + in background_g and background_b are then unused. + + So when decoding, you may get these in a different color mode than the one you requested + for the raw pixels. + + When encoding with auto_convert, you must use the color model defined in info_png.color for + these values. The encoder normally ignores info_png.color when auto_convert is on, but will + use it to interpret these values (and convert copies of them to its chosen color model). + + When encoding, avoid setting this to an expensive color, such as a non-gray value + when the image is gray, or the compression will be worse since it will be forced to + write the PNG with a more expensive color mode (when auto_convert is on). + + The decoder does not use this background color to edit the color of pixels. This is a + completely optional metadata feature. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red/gray/palette component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + Non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + All the string fields below including strings, keys, names and language tags are null terminated. + The PNG specification uses null characters for the keys, names and tags, and forbids null + characters to appear in the main text which is why we can use null termination everywhere here. + + A keyword is minimum 1 character and maximum 79 characters long (plus the + additional null terminator). It's discouraged to use a single line length + longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + + Standard text chunk keywords and strings are encoded using Latin-1. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + International text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys", and the following text encodings are used: + keys: Latin-1, langtags: ASCII, transkeys and strings: UTF-8. + keys must be 1-79 characters (plus the additional null terminator), the other + strings are any length. + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + Color profile related chunks: gAMA, cHRM, sRGB, iCPP + + LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color + profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please + use these values with a color management library. + + See the PNG, ICC and sRGB specifications for more information about the meaning of these values. + */ + + /* gAMA chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */ + unsigned gama_gamma; /* Gamma exponent times 100000 */ + + /* cHRM chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */ + unsigned chrm_white_x; /* White Point x times 100000 */ + unsigned chrm_white_y; /* White Point y times 100000 */ + unsigned chrm_red_x; /* Red x times 100000 */ + unsigned chrm_red_y; /* Red y times 100000 */ + unsigned chrm_green_x; /* Green x times 100000 */ + unsigned chrm_green_y; /* Green y times 100000 */ + unsigned chrm_blue_x; /* Blue x times 100000 */ + unsigned chrm_blue_y; /* Blue y times 100000 */ + + /* + sRGB chunk: optional. May not appear at the same time as iCCP. + If gAMA is also present gAMA must contain value 45455. + If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000. + */ + unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */ + unsigned srgb_intent; /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */ + + /* + iCCP chunk: optional. May not appear at the same time as sRGB. + + LodePNG does not parse or use the ICC profile (except its color space header field for an edge case), a + separate library to handle the ICC data (not included in LodePNG) format is needed to use it for color + management and conversions. + + For encoding, if iCCP is present, gAMA and cHRM are recommended to be added as well with values that match the ICC + profile as closely as possible, if you wish to do this you should provide the correct values for gAMA and cHRM and + enable their '_defined' flags since LodePNG will not automatically compute them from the ICC profile. + + For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray + PNG color types and a "GRAY" profile for gray PNG color types. If you disable auto_convert, you must ensure + the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is + enabled (the default), and the ICC profile is not a good match for the pixel data, this will result in an encoder + error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel + data if the pixels could be encoded as grayscale but the ICC profile is RGB. + + To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so + make sure you compute it carefully to avoid the above problems. + */ + unsigned iccp_defined; /* Whether an iCCP chunk is present (0 = not present, 1 = present). */ + char* iccp_name; /* Null terminated string with profile name, 1-79 bytes */ + /* + The ICC profile in iccp_profile_size bytes. + Don't allocate this buffer yourself. Use the init/cleanup functions + correctly and use lodepng_set_icc and lodepng_clear_icc. + */ + unsigned char* iccp_profile; + unsigned iccp_profile_size; /* The size of iccp_profile in bytes */ + + /* End of color profile related chunks */ + + + /* + unknown chunks: chunks not known by LodePNG, passed on byte for byte. + + There are 3 buffers, one for each position in the PNG where unknown chunks can appear. + Each buffer contains all unknown chunks for that position consecutively. + The 3 positions are: + 0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND. + + For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag + above in here, since the encoder will blindly follow this and could then encode an invalid PNG file + (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use + this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST), + or any non-standard PNG chunk. + + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ + +/*replaces if exists*/ +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size); +void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings { + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned ignore_critical; /*ignore unknown critical chunks*/ + unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ + /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable + errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some + strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters + in string keys, etc... */ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; + + /* maximum size for decompressed text chunks. If a text chunk's text is larger than this, an error is returned, + unless reading text chunks is disabled or this limit is set higher or disabled. Set to 0 to allow any size. + By default it is a value that prevents unreasonably large strings from hogging memory. */ + size_t max_text_size; + + /* maximum size for compressed ICC chunks. If the ICC profile is larger than this, an error will be returned. Set to + 0 to allow any size. By default this is a value that prevents ICC profiles that would be much larger than any + legitimate profile could be to hog memory. */ + size_t max_icc_size; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy { + /*every filter at zero*/ + LFS_ZERO = 0, + /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/ + LFS_ONE = 1, + LFS_TWO = 2, + LFS_THREE = 3, + LFS_FOUR = 4, + /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...), +which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorStats { + unsigned colored; /*not grayscale*/ + unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ + unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/ + size_t numpixels; + + /*user settings for computing/using the stats*/ + unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/ + unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/ +} LodePNGColorStats; + +void lodepng_color_stats_init(LodePNGColorStats* stats); + +/*Get a LodePNGColorStats of the image. The stats must already have been inited. +Returns error code (e.g. alloc fail) or 0 if ok.*/ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings { + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState { +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the IHDR chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* +Reads one metadata chunk (other than IHDR) of the PNG file and outputs what it +read in the state. Returns error code on failure. +Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const +to find the desired chunk type, and if non null use lodepng_inspect_chunk (with +chunk_pointer - start_of_file as pos). +Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...). +Ignores unsupported, unknown, non-metadata or IHDR chunks (without error). +Requirements: &in[pos] must point to start of a chunk, must use regular +lodepng_inspect first since format of most other chunks depends on IHDR, and if +there is a PLTE chunk, that one must be inspected before tRNS or bKGD. +*/ +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +The chunk pointer always points to the beginning of the chunk itself, that is +the first byte of the 4 length bytes. + +In the PNG file format, chunks have the following format: +-4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) +-4 bytes chunk type (ASCII a-z,A-Z only, see below) +-length bytes of data (may be 0 bytes if length was 0) +-4 bytes of CRC, computed on chunk name + data + +The first chunk starts at the 8th byte of the PNG file, the entire rest of the file +exists out of concatenated chunks with the above format. + +PNG standard chunk ASCII naming conventions: +-First byte: uppercase = critical, lowercase = ancillary +-Second byte: uppercase = public, lowercase = private +-Third byte: must be uppercase +-Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/* +Iterate to next chunks, allows iterating through all chunks of the PNG file. +Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call, +or the 8th byte of a PNG file which always has the first chunk), or alternatively may +point to the first byte of the PNG file (which is not a chunk but the magic header, the +function will then skip over it and return the first real chunk). +Will output pointer to the start of the next chunk, or at or beyond end of the file if there +is no more chunk after this or possibly if the chunk is corrupt. +Start this process at the 8th byte of the PNG file. +In a non-corrupt PNG file, the last chunk should have name "IEND". +*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); + +/*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng { +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState { + public: + State(); + State(const State& other); + ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + State& state, + const std::vector<unsigned char>& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector<unsigned char>& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector<unsigned char>& out, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory +*/ +unsigned load_file(std::vector<unsigned char>& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory +*/ +unsigned save_file(const std::vector<unsigned char>& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with various compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[X] support color profile chunk types (but never let them touch RGB values by default) +[ ] support all public PNG chunk types (almost done except sBIT, sPLT and hIST) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) +[ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... +[ ] error messages with line numbers (and version) +[ ] errors in state instead of as return code? +[ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +[ ] allow user to give data (void*) to custom allocator +[X] provide alternatives for C library functions not present on some platforms (memcpy, ...) +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. state settings reference + 12. changes + 13. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, + plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + cHRM: RGB chromaticities + gAMA: RGB gamma correction + iCCP: ICC color profile + sRGB: rendering intent + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not (yet) supported but treated as unknown chunks by LodePNG: + sBIT + hIST + sPLT + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to grayscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG <version>" to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, grayscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to grayscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: grayscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: grayscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +Non supported color conversions: +-color to grayscale when non-gray pixels are present: no error is thrown, but +the result will look ugly because only the red channel is taken (it assumes all +three channels are the same in this case so ignores green and blue). The reason +no error is given is to allow converting from three-channel grayscale images to +one-channel even if there are numerical imprecisions. +-anything to palette when the palette does not have an exact match for a from-color +in it: in this case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any gray or gray+alpha, to gray or gray+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + +It is not recommended to use the numerical values to programmatically make +different decisions based on error types as the numbers are not guaranteed to +stay backwards compatible. They are for human consumption only. Programmatically +only 0 or non-0 matter. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outsize. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distinction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards compliant. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +NOTE: these examples do not support wide-character filenames, you can use an +external method to handle such files and encode or decode in-memory + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include <iostream> + +int main(int argc, char *argv[]) { + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector<unsigned char> image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) { + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + +11. state settings reference +---------------------------- + +A quick reference of some settings to set on the LodePNGState + +For decoding: + +state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums +state.decoder.zlibsettings.custom_...: use custom inflate function +state.decoder.ignore_crc: ignore CRC checksums +state.decoder.ignore_critical: ignore unknown critical chunks +state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors +state.decoder.color_convert: convert internal PNG color to chosen one +state.decoder.read_text_chunks: whether to read in text metadata chunks +state.decoder.remember_unknown_chunks: whether to read in unknown chunks +state.info_raw.colortype: desired color type for decoded image +state.info_raw.bitdepth: desired bit depth for decoded image +state.info_raw....: more color settings, see struct LodePNGColorMode +state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo + +For encoding: + +state.encoder.zlibsettings.btype: disable compression by setting it to 0 +state.encoder.zlibsettings.use_lz77: use LZ77 in compression +state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize +state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match +state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching +state.encoder.zlibsettings.lazymatching: try one more LZ77 matching +state.encoder.zlibsettings.custom_...: use custom deflate function +state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png +state.encoder.filter_palette_zero: PNG filter strategy for palette +state.encoder.filter_strategy: PNG filter strategy to encode with +state.encoder.force_palette: add palette even if not encoding to one +state.encoder.add_id: add LodePNG identifier and version as a text chunk +state.encoder.text_compression: use compressed text chunks for metadata +state.info_raw.colortype: color type of raw input image you provide +state.info_raw.bitdepth: bit depth of raw input image you provide +state.info_raw: more color settings, see struct LodePNGColorMode +state.info_png.color.colortype: desired color type if auto_convert is false +state.info_png.color.bitdepth: desired bit depth if auto_convert is false +state.info_png.color....: more color settings, see struct LodePNGColorMode +state.info_png....: more PNG related settings, see struct LodePNGInfo + + +12. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +Not all changes are listed here, the commit history in github lists more: +https://github.com/lvandeve/lodepng + +*) 27 jun 2021: added warnings that file reading/writing functions don't support + wide-character filenames (support for this is not planned, opening files is + not the core part of PNG decoding/decoding and is platform dependent). +*) 17 okt 2020: prevent decoding too large text/icc chunks by default. +*) 06 mar 2020: simplified some of the dynamic memory allocations. +*) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct + overflow checks. +*) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. +*) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette + if gray ICC profile) and non-ICC LodePNGColorProfile renamed to + LodePNGColorStats. +*) 30 dec 2018: code style changes only: removed newlines before opening braces. +*) 10 sep 2018: added way to inspect metadata chunks without full decoding. +*) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use + palette index in case of palette. +*) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This + change is backwards compatible unless you relied on unknown_chunks for those. +*) 11 jun 2018: less restrictive check for pixel size integer overflow +*) 14 jan 2018: allow optionally ignoring a few more recoverable errors +*) 17 sep 2017: fix memory leak for some encoder input error cases +*) 27 nov 2016: grey+alpha auto color model detection bugfix +*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). +*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within + the limits of pure C90). +*) 08 dec 2015: Made load_file function return error if file can't be opened. +*) 24 okt 2015: Bugfix with decoding to palette output. +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 24 aug 2014: Moved to github +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrunk the implementation code. Made new samples. +*) 6 nov 2011: (!) By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011: (!) changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also various fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +13. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2021 Lode Vandevenne +*/ diff --git a/gbdk/gbdk-support/png2asset/png2asset.cpp b/gbdk/gbdk-support/png2asset/png2asset.cpp new file mode 100644 index 00000000..3016c065 --- /dev/null +++ b/gbdk/gbdk-support/png2asset/png2asset.cpp @@ -0,0 +1,1339 @@ +#include <vector> +#include <string> +#include <algorithm> +#include <cstring> +#include <set> +#include <stdio.h> +#include <fstream> +#include "lodepng.h" + +using namespace std; + +int decodePNG(vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true); +void loadFile(vector<unsigned char>& buffer, const std::string& filename); + +#define BIT(VALUE, INDEX) (1 & ((VALUE) >> (INDEX))) + +bool export_as_map = false; +bool use_map_attributes = false; +size_t pal_size; +#define TILE_W 8 +int tile_h; +int bpp = 2; +unsigned int tile_origin = 0; // Default to no tile index offset + +struct Tile +{ + vector< unsigned char > data; + unsigned char pal; + + Tile(size_t size = 0) : data(size), pal(0) {} + bool operator==(const Tile& t) const + { + return data == t.data && pal == t.pal; + } + + const Tile& operator=(const Tile& t) + { + data = t.data; + pal = t.pal; + return *this; + } + + enum PackMode { + GB, + SGB, + SMS, + }; + + vector< unsigned char > GetPackedData(PackMode pack_mode) { + vector< unsigned char > ret(tile_h * bpp, 0); + if(pack_mode == GB) { + for(int j = 0; j < tile_h; ++j) { + for(int i = 0; i < 8; ++ i) { + unsigned char col = data[8 * j + i]; + ret[j * 2 ] |= BIT(col, 0) << (7 - i); + ret[j * 2 + 1] |= BIT(col, 1) << (7 - i); + } + } + } + else if(pack_mode == SGB) + { + for(int j = 0; j < tile_h; ++j) { + for(int i = 0; i < 8; ++ i) { + unsigned char col = data[8 * j + i]; + ret[j * 2 ] |= BIT(col, 0) << (7 - i); + ret[j * 2 + 1] |= BIT(col, 1) << (7 - i); + ret[(tile_h + j) * 2 ] |= BIT(col, 2) << (7 - i); + ret[(tile_h + j) * 2 + 1] |= BIT(col, 3) << (7 - i); + } + } + } + else if(pack_mode == SMS) + { + for(int j = 0; j < tile_h; ++j) { + for(int i = 0; i < 8; ++ i) { + unsigned char col = data[8 * j + i]; + ret[j * 4 ] |= BIT(col, 0) << (7 - i); + ret[j * 4 + 1] |= BIT(col, 1) << (7 - i); + ret[j * 4 + 2] |= BIT(col, 2) << (7 - i); + ret[j * 4 + 3] |= BIT(col, 3) << (7 - i); + } + } + } + return ret; + } +}; + +struct PNGImage +{ + vector< unsigned char > data; //data in indexed format + unsigned int w; + unsigned int h; + + size_t palettesize; //number of palette colors + unsigned char* palette; //palette colors in RGBA (1 color == 4 bytes) + + unsigned char GetGBColor(int x, int y) + { + return data[w * y + x] % pal_size; + } + + bool ExtractGBTile(int x, int y, int tile_h, Tile& tile) + { + tile.pal = (export_as_map && !use_map_attributes) ? data[w * y + x] >> 2 : 0; //Set the palette to 0 when pals are not stored in tiles to allow tiles to be equal even when their palettes are different + + bool all_zero = true; + for(int j = 0; j < tile_h; ++ j) + { + for(int i = 0; i < 8; ++i) + { + unsigned char color_idx = GetGBColor(x + i, y + j); + tile.data[j * 8 + i] = color_idx; + all_zero = all_zero && (color_idx == 0); + } + } + return !all_zero; + } +}; + +struct MTTile +{ + char offset_x; + char offset_y; + unsigned char offset_idx; + unsigned char props; + + MTTile(char offset_x, char offset_y, unsigned char offset_idx, unsigned char props) : offset_x(offset_x), offset_y(offset_y), offset_idx(offset_idx), props(props) {} + MTTile() : offset_x(0), offset_y(0), offset_idx(0), props(0) {} +}; +string source_tileset; +unsigned int extra_tile_count = 0; +unsigned int source_palette_count = 0; +unsigned int source_tileset_size = 0; +bool includeTileData = true; +bool includedMapOrMetaspriteData = true; +PNGImage source_tileset_image; +bool use_source_tileset = false; +bool keep_duplicate_tiles = false; + +typedef vector< MTTile > MetaSprite; +vector< Tile > tiles; +vector< MetaSprite > sprites; +vector< unsigned char > map; +vector< unsigned char > map_attributes; +PNGImage image; +int props_default = 0x00; // Default Sprite props has no attributes enabled +bool use_structs = false; +bool flip_tiles = true; +Tile::PackMode pack_mode = Tile::GB; + +Tile FlipH(const Tile& tile) +{ + Tile ret; + for(int j = (int)tile.data.size() - 8; j >= 0; j -= 8) + { + for(int i = 0; i < 8; ++ i) + { + ret.data.push_back(tile.data[j + i]); + } + } + ret.pal = tile.pal; + return ret; +} + +Tile FlipV(const Tile& tile) +{ + Tile ret; + for(int j = 0; j < (int)tile.data.size(); j += 8) + { + for(int i = 7; i >= 0; -- i) + { + ret.data.push_back(tile.data[j + i]); + } + } + ret.pal = tile.pal; + return ret; +} + +bool FindTile(const Tile& t, size_t& idx, unsigned char& props) +{ + vector< Tile >::iterator it; + it = find(tiles.begin(), tiles.end(), t); + if(it != tiles.end()) + { + idx = (size_t)(it - tiles.begin()); + props = props_default; + return true; + } + + if(flip_tiles) + { + Tile tile = FlipV(t); + it = find(tiles.begin(), tiles.end(), tile); + if(it != tiles.end()) + { + idx = (size_t)(it - tiles.begin()); + props = props_default | (1 << 5); + return true; + } + + tile = FlipH(tile); + it = find(tiles.begin(), tiles.end(), tile); + if(it != tiles.end()) + { + idx = (size_t)(it - tiles.begin()); + props = props_default | (1 << 5) | (1 << 6); + return true; + } + + tile = FlipV(tile); + it = find(tiles.begin(), tiles.end(), tile); + if(it != tiles.end()) + { + idx = (size_t)(it - tiles.begin()); + props = props_default | (1 << 6); + return true; + } + } + + return false; +} + +void GetMetaSprite(int _x, int _y, int _w, int _h, int pivot_x, int pivot_y) +{ + int last_x = _x + pivot_x; + int last_y = _y + pivot_y; + + sprites.push_back(MetaSprite()); + MetaSprite& mt_sprite = sprites.back(); + for(int y = _y; y < _y + _h && y < (int)image.h; y += tile_h) + { + for(int x = _x; x < _x + _w && x < (int)image.w; x += 8) + { + Tile tile(tile_h * 8); + if (image.ExtractGBTile(x, y, tile_h, tile)) + { + size_t idx; + unsigned char props; + unsigned char pal_idx = image.data[y * image.w + x] >> 2; //We can pick the palette from the first pixel of this tile + + if(keep_duplicate_tiles) + { + tiles.push_back(tile); + idx = tiles.size() - 1; + props = props_default; + } + else + { + if(!FindTile(tile, idx, props)) + { + if (use_source_tileset) { + printf("found a tile not in the source tileset at %d,%d. The target tileset has %d extra tiles.\n", x, y,extra_tile_count+1); + extra_tile_count++; + includeTileData = true; + } + tiles.push_back(tile); + idx = tiles.size() - 1; + props = props_default; + } + } + + props |= pal_idx; + + if(tile_h == 16) + idx *= 2; + + mt_sprite.push_back(MTTile(x - last_x, y - last_y, (unsigned char)idx, props)); + + last_x = x; + last_y = y; + } + } + } +} + +void GetMap() +{ + for(int y = 0; y < (int)image.h; y += 8) + { + for(int x = 0; x < (int)image.w; x += 8) + { + Tile tile(8 * 8); + image.ExtractGBTile(x, y, 8, tile); + + size_t idx; + unsigned char props; + + if(keep_duplicate_tiles) + { + tiles.push_back(tile); + idx = tiles.size() - 1; + props = props_default; + } + else + { + if(!FindTile(tile, idx, props)) + { + if (use_source_tileset) { + printf("found a tile not in the source tileset at %d,%d. The target tileset has %d extra tiles.\n", x, y, extra_tile_count + 1); + extra_tile_count++; + includeTileData = true; + } + tiles.push_back(tile); + idx = tiles.size() - 1; + props = props_default; + + if(tiles.size() > 256 && pack_mode != Tile::SMS) + printf("Warning: found more than 256 tiles on x:%d,y:%d\n", x, y); + + if(((tiles.size() + tile_origin) > 256) && (pack_mode != Tile::SMS)) + printf("Warning: tile count (%d) + tile origin (%d) exceeds 256 at x:%d,y:%d\n", (unsigned int)tiles.size(), tile_origin, x, y); + } + } + + map.push_back((unsigned char)idx + tile_origin); + + if(use_map_attributes) + { + unsigned char pal_idx = image.data[y * image.w + x] >> bpp; //We can pick the palette from the first pixel of this tile + if(pack_mode == Tile::SGB) + { + props = props << 1; //Mirror flags in SGB are on bit 7 + props |= (pal_idx + 4) << 2; //Pals are in bits 2,3,4 and need to go from 4 to 7 + map.push_back(props); //Also they are stored within the map tiles + } + else if(pack_mode == Tile::SMS) + { + props = props >> 4; + if(idx > 255) + props |= 1; + map.push_back(props); + } + else + { + props |= pal_idx; + map_attributes.push_back(props); + } + } + + } + } +} +//Functor to compare entries in SetPal +struct CmpIntColor { + bool operator() (unsigned int const& c1, unsigned int const& c2) const + { + unsigned char* c1_ptr = (unsigned char*)&c1; + unsigned char* c2_ptr = (unsigned char*)&c2; + + //Compare alpha first, transparent color is considered smaller + if(c1_ptr[0] != c2_ptr[0]) + { + return c1_ptr[0] < c2_ptr[0]; + } + else + { + unsigned int lum_1 = (unsigned int)(c1_ptr[3] * 0.299f + c1_ptr[2] * 0.587f + c1_ptr[1] * 0.114f); + unsigned int lum_2 = (unsigned int)(c2_ptr[3] * 0.299f + c2_ptr[2] * 0.587f + c2_ptr[1] * 0.114f); + return lum_1 > lum_2; + } + } +}; + +//This set will keep colors in the palette ordered based on their grayscale values to ensure they look good on DMG +//This assumes the palette used in DMG will be 00 01 10 11 +typedef set< unsigned int, CmpIntColor > SetPal; + +SetPal GetPaletteColors(const PNGImage& image, int x, int y, int w, int h) +{ + SetPal ret; + for(int j = y; j < (y + h); ++ j) + { + for(int i = x; i < (x + w); ++ i) + { + const unsigned char* color = &image.data[(j * image.w + i) * 4]; + int color_int = (color[0] << 24) | (color[1] << 16) | (color[2] << 8) | color[3]; + ret.insert(color_int); + } + } + + for(SetPal::iterator it = ret.begin(); it != ret.end(); ++it) + { + if(it != ret.begin() && ((0xFF & *it) != 0xFF)) //ret.begin() should be the only one transparent + printf("Warning: found more than one transparent color in tile at x:%d, y:%d of size w:%d, h:%d\n", x, y, w, h); + } + + return ret; +} + +unsigned int PaletteCountApplyMaxLimit(unsigned int max_palettes, unsigned int cur_palette_size) +{ + if (cur_palette_size > max_palettes) + { + printf("Warning: %d palettes found, truncating to %d (-max_palettes)\n", (unsigned int)cur_palette_size, (unsigned int)max_palettes); + return max_palettes; + } + else + return cur_palette_size; +} + +bool GetSourceTileset(bool keep_palette_order, unsigned int max_palettes, vector< SetPal >& palettes) { + + lodepng::State sourceTilesetState; + vector<unsigned char> buffer2; + + lodepng::load_file(buffer2, source_tileset); + + + if (keep_palette_order) { + //Calling with keep_palette_order means + //-The image is png8 + //-Each 4 colors define a gbc palette, the first color is the transparent one + //-Each rectangle with dimension(8, tile_h) in the image has colors from one of those palettes only + sourceTilesetState.info_raw.colortype = LCT_PALETTE; + sourceTilesetState.info_raw.bitdepth = 8; + sourceTilesetState.decoder.color_convert = false; + + unsigned error = lodepng::decode(source_tileset_image.data, source_tileset_image.w, source_tileset_image.h, sourceTilesetState, buffer2); + if (error) + { + printf("decoder error %s\n", lodepng_error_text(error)); + return false; + } + + + if (sourceTilesetState.info_raw.colortype != LCT_PALETTE) + { + printf("error: keep_palette_order only works with png8"); + return false; + } + + unsigned int palette_count = PaletteCountApplyMaxLimit(max_palettes, sourceTilesetState.info_raw.palettesize / pal_size); + source_tileset_image.palettesize = palette_count * pal_size; + source_tileset_image.palette = sourceTilesetState.info_raw.palette; + } + else { + + PNGImage image32; + unsigned error = lodepng::decode(image32.data, image32.w, image32.h, sourceTilesetState, buffer2); //decode as 32 bit + if (error) + { + printf("decoder error %s\n", lodepng_error_text(error)); + return false; + } + + + int* palettes_per_tile = new int[(image32.w / 8) * (image32.h / tile_h)]; + + for (unsigned int y = 0; y < image32.h; y += tile_h) + { + for (unsigned int x = 0; x < image32.w; x += 8) + { + //Get palette colors on (x, y, 8, tile_h) + SetPal pal = GetPaletteColors(image32, x, y, 8, tile_h); + if (pal.size() > pal_size) + { + printf("Error: more than %d colors found in tile at x:%d, y:%d of size w:%d, h:%d\n", (unsigned int)pal_size, x, y, 8, tile_h); + return false; + } + + //Check if it matches any palettes or create a new one + size_t i; + for (i = 0; i < palettes.size(); ++i) + { + //Try to merge this palette with any of the palettes (checking if they are equal is not enough since the palettes can have less than 4 colors) + SetPal merged(palettes[i]); + merged.insert(pal.begin(), pal.end()); + if (merged.size() <= pal_size) + { + if (palettes[i].size() <= pal_size) + palettes[i] = merged; //Increase colors with this palette (it has less than 4 colors) + break; //Found palette + } + } + + if (i == palettes.size()) + { + //Palette not found, add a new one + palettes.push_back(pal); + } + + palettes_per_tile[(y / tile_h) * (image32.w / 8) + (x / 8)] = i; + } + }; + + //Create the indexed image + source_tileset_image.data.clear(); + source_tileset_image.w = image32.w; + source_tileset_image.h = image32.h; + + unsigned int palette_count = PaletteCountApplyMaxLimit(max_palettes, palettes.size()); + + source_tileset_image.palettesize = palette_count * pal_size; + source_tileset_image.palette = new unsigned char[palette_count * pal_size * 4]; //pal_size colors * 4 bytes each + source_palette_count = source_tileset_image.palettesize; + + for (size_t p = 0; p < palette_count; ++p) + { + int* color_ptr = (int*)&source_tileset_image.palette[p * pal_size * 4]; + + //TODO: if palettes[p].size() != pal_size we should probably try to fill the gaps based on grayscale values + + for (SetPal::iterator it = palettes[p].begin(); it != palettes[p].end(); ++it, color_ptr++) + { + unsigned char* c = (unsigned char*)&(*it); + *color_ptr = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]; + } + } + + for (size_t y = 0; y < image32.h; ++y) + { + for (size_t x = 0; x < image32.w; ++x) + { + unsigned char* c32ptr = &image32.data[(image32.w * y + x) * 4]; + int color32 = (c32ptr[0] << 24) | (c32ptr[1] << 16) | (c32ptr[2] << 8) | c32ptr[3]; + unsigned char palette = palettes_per_tile[(y / tile_h) * (image32.w / 8) + (x / 8)]; + unsigned char index = std::distance(palettes[palette].begin(), palettes[palette].find(color32)); + source_tileset_image.data.push_back((palette << bpp) + index); + } + } + } + + // We'll change the image variable + // So we don't have to change any of the existing code + PNGImage temp = image; + image = source_tileset_image; + use_source_tileset = false; + GetMap(); + + // Our source tileset shouldn't build the map arrays up + // Clear anything from the previous 'GetMap' call + map.clear(); + map_attributes.clear(); + use_source_tileset = true; + + // Change the image variable back + image = temp; + + source_tileset_size = tiles.size(); + + printf("Got %d tiles from the source tileset.\n", (unsigned int)tiles.size()); + printf("Got %d palettes from the source tileset.\n", (unsigned int)source_tileset_image.palettesize/4); + + return true; + +} + + +void Export(const PNGImage& image, const char* path) +{ + lodepng::State state; + state.info_png.color.colortype = LCT_PALETTE; + state.info_png.color.bitdepth = 8; + state.info_raw.colortype = LCT_PALETTE; + state.info_raw.bitdepth = 8; + state.encoder.auto_convert = 0; //we specify ourselves exactly what output PNG color mode we want + +#define ADD_PALETTE(R, G, B, A) lodepng_palette_add(&state.info_png.color, R, G, B, A); lodepng_palette_add(&state.info_raw, R, G, B, A) + for(size_t p = 0; p < image.palettesize; ++ p) + { + unsigned char* c = &image.palette[p * 4]; + ADD_PALETTE(c[0], c[1], c[2], c[3]); + } + + std::vector<unsigned char> buffer; + lodepng::encode(buffer, image.data, image.w, image.h, state); + lodepng::save_file(buffer, path); +} + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + printf("usage: png2asset <file>.png [options]\n"); + printf("-c ouput file (default: <png file>.c)\n"); + printf("-sw <width> metasprites width size (default: png width)\n"); + printf("-sh <height> metasprites height size (default: png height)\n"); + printf("-sp <props> change default for sprite OAM property bytes (in hex) (default: 0x00)\n"); + printf("-px <x coord> metasprites pivot x coordinate (default: metasprites width / 2)\n"); + printf("-py <y coord> metasprites pivot y coordinate (default: metasprites height / 2)\n"); + printf("-pw <width> metasprites collision rect widht (default: metasprites width)\n"); + printf("-ph <height> metasprites collision rect height (default: metasprites height)\n"); + printf("-spr8x8 use SPRITES_8x8 (default: SPRITES_8x16)\n"); + printf("-spr8x16 use SPRITES_8x16 (default: SPRITES_8x16)\n"); + printf("-b <bank> bank (default 0)\n"); + printf("-keep_palette_order use png palette\n"); + printf("-noflip disable tile flip\n"); + printf("-map Export as map (tileset + bg)\n"); + printf("-use_map_attributes Use CGB BG Map attributes (default: palettes are stored for each tile in a separate array)\n"); + printf("-use_structs Group the exported info into structs (default: false) (used by ZGB Game Engine)\n"); + printf("-bpp bits per pixel: 2, 4 (default: 2)\n"); + printf("-max_palettes max number of palettes allowed (default: 8)\n"); + printf(" (note: max colors = max_palettes x num colors per palette)\n"); + printf("-pack_mode gb, sgb or sms (default: gb)\n"); + printf("-tile_origin tile index offset for maps (default: 0)\n"); + + printf("-tiles_only export tile data only\n"); + printf("-maps_only export map tilemap only\n"); + printf("-metasprites_only export metasprite descriptors only\n"); + printf("-source_tileset use source tileset (image with common tiles)\n"); + printf("-keep_duplicate_tiles do not remove duplicate tiles (default: not enabled)\n"); + + printf("-bin export to binary format\n"); + printf("-transposed export transposed (column-by-column instead of row-by-row)\n"); + return 0; + } + + //default params + int sprite_w = 0; + int sprite_h = 0; + int pivot_x = 0xFFFFFF; + int pivot_y = 0xFFFFFF; + int pivot_w = 0xFFFFFF; + int pivot_h = 0xFFFFFF; + tile_h = 16; + string output_filename = argv[1]; + output_filename = output_filename.substr(0, output_filename.size() - 4) + ".c"; + int bank = 0; + bool keep_palette_order = false; + bool output_binary = false; + bool output_transposed = false; + size_t max_palettes = 8; + + //Parse argv + for(int i = 2; i < argc; ++i) + { + if(!strcmp(argv[i], "-sw")) + { + sprite_w = atoi(argv[++ i]); + } + else if(!strcmp(argv[i], "-sh")) + { + sprite_h = atoi(argv[++ i]); + } + else if(!strcmp(argv[i], "-sp")) + { + props_default = strtol(argv[++i], NULL, 16); + } + if(!strcmp(argv[i], "-px")) + { + pivot_x = atoi(argv[++ i]); + } + else if(!strcmp(argv[i], "-py")) + { + pivot_y = atoi(argv[++ i]); + } + else if(!strcmp(argv[i], "-pw")) + { + pivot_w = atoi(argv[++ i]); + } + else if(!strcmp(argv[i], "-ph")) + { + pivot_h = atoi(argv[++ i]); + } + else if(!strcmp(argv[i], "-spr8x8")) + { + tile_h = 8; + } + else if(!strcmp(argv[i],"-spr8x16")) + { + tile_h = 16; + } + else if(!strcmp(argv[i], "-c")) + { + output_filename = argv[++ i]; + } + else if(!strcmp(argv[i], "-b")) + { + bank = atoi(argv[++ i]); + } + else if(!strcmp(argv[i], "-keep_palette_order")) + { + keep_palette_order = true; + } + else if(!strcmp(argv[i], "-noflip")) + { + flip_tiles = false; + } + else if(!strcmp(argv[i], "-map")) + { + export_as_map = true; + } + else if(!strcmp(argv[i], "-use_map_attributes")) + { + use_map_attributes = true; + } + else if(!strcmp(argv[i], "-use_structs")) + { + use_structs = true; + } + else if(!strcmp(argv[i], "-bpp")) + { + bpp = atoi(argv[++ i]); + } + else if(!strcmp(argv[i], "-max_palettes")) + { + max_palettes = atoi(argv[++ i]); + if (max_palettes == 0) + { + printf("-max_palettes must be larger than zero\n"); + return 1; + } + } + else if(!strcmp(argv[i], "-pack_mode")) + { + std::string pack_mode_str = argv[++ i]; + if (pack_mode_str == "gb") pack_mode = Tile::GB; + else if(pack_mode_str == "sgb") pack_mode = Tile::SGB; + else if(pack_mode_str == "sms") pack_mode = Tile::SMS; + else + { + printf("-pack_mode must be one of gb, sgb or sms\n"); + return 1; + } + } + else if(!strcmp(argv[i], "-tile_origin")) + { + tile_origin = atoi(argv[++ i]); + } + else if (!strcmp(argv[i], "-maps_only") || !strcmp(argv[i], "-metasprites_only")) + { + includeTileData = false; + } + else if (!strcmp(argv[i], "-tiles_only")) + { + includedMapOrMetaspriteData = false; + } + else if (!strcmp(argv[i], "-keep_duplicate_tiles")) + { + keep_duplicate_tiles = true; + } + else if (!strcmp(argv[i], "-source_tileset")) + { + use_source_tileset = true; + includeTileData = false; + source_tileset = argv[++i]; + } + else if (!strcmp(argv[i], "-bin")) + { + output_binary = true; + } + else if (!strcmp(argv[i], "-transposed")) + { + output_transposed = true; + } + } + + pal_size = 1 << bpp; + + if(export_as_map) + tile_h = 8; //Force tiles_h to 8 on maps + + int slash_pos = (int)output_filename.find_last_of('/'); + if(slash_pos == -1) + slash_pos = (int)output_filename.find_last_of('\\'); + int dot_pos = (int)output_filename.find_first_of('.', slash_pos == -1 ? 0 : slash_pos); + + string output_filename_h = output_filename.substr(0, dot_pos) + ".h"; + string output_filename_bin = output_filename.substr(0, dot_pos) + "_map.bin"; + string output_filename_attributes_bin = output_filename.substr(0, dot_pos) + "_map_attributes.bin"; + string output_filename_tiles_bin = output_filename.substr(0, dot_pos) + "_tiles.bin"; + string data_name = output_filename.substr(slash_pos + 1, dot_pos - 1 - slash_pos); + replace(data_name.begin(), data_name.end(), '-', '_'); + + // This was moved from outside the upcoming else statement when not using keep_palette_order + // So the 'GetSourceTileset' function can pre-populate it from the source tileset + vector< SetPal > palettes; + + if (use_source_tileset) { + + if (!GetSourceTileset(keep_palette_order, max_palettes, palettes)) { + return 1; + } + } + + + //load and decode png + vector<unsigned char> buffer; + lodepng::load_file(buffer, argv[1]); + lodepng::State state; + if(keep_palette_order) + { + //Calling with keep_palette_order means + //-The image is png8 + //-For CGB: Each 4 colors define a gbc palette, the first color is the transparent one + //-Each rectangle with dimension(8, tile_h) in the image has colors from one of those palettes only + state.info_raw.colortype = LCT_PALETTE; + state.info_raw.bitdepth = 8; + state.decoder.color_convert = false; + unsigned error = lodepng::decode(image.data, image.w, image.h, state, buffer); + if(error) + { + printf("decoder error %s\n", lodepng_error_text(error)); + return 1; + } + + if(state.info_raw.colortype != LCT_PALETTE) + { + printf("error: keep_palette_order only works with png8"); + return 1; + } + + unsigned int palette_count = PaletteCountApplyMaxLimit(max_palettes, state.info_raw.palettesize / pal_size); + image.palettesize = palette_count * pal_size; + image.palette = state.info_raw.palette; + + if (use_source_tileset) { + + // Make sure these two values match when keeping palette order + if (image.palettesize != source_tileset_image.palettesize) { + + printf("error: The number of color palette's for your source tileset (%d) and target image (%d) do not match.", (unsigned int)source_tileset_image.palettesize, (unsigned int)image.palettesize); + return 1; + } + + size_t size = max(image.palettesize, source_tileset_image.palettesize); + + // Make sure these two values match when keeping palette order + if (memcmp(image.palette, source_tileset_image.palette, size) != 0) { + + printf("error: The palette's for your source tileset and target image do not match."); + return 1; + } + } + } + else + { + PNGImage image32; + unsigned error = lodepng::decode(image32.data, image32.w, image32.h, state, buffer); //decode as 32 bit + if(error) + { + printf("decoder error %s\n", lodepng_error_text(error)); + return 1; + } + + // Validate image dimensions + if( ((image32.w % TILE_W) != 0) || ((image32.h % tile_h) != 0) ) + { + printf("Error: Image size %d x %d isn't an even multiple of tile size %d x %d\n", image32.w, image32.h, TILE_W, tile_h); + return 1; + } + + int* palettes_per_tile = new int[(image32.w / 8) * (image32.h /tile_h)]; + for(unsigned int y = 0; y < image32.h; y += tile_h) + { + for(unsigned int x = 0; x < image32.w; x += 8) + { + //Get palette colors on (x, y, 8, tile_h) + SetPal pal = GetPaletteColors(image32, x, y, 8, tile_h); + if(pal.size() > pal_size) + { + printf("Error: more than %d colors found in tile at x:%d, y:%d of size w:%d, h:%d\n", (unsigned int)pal_size, x, y, 8, tile_h); + return 1; + } + + //Check if it matches any palettes or create a new one + size_t i; + for(i = 0; i < palettes.size(); ++i) + { + //Try to merge this palette wit any of the palettes (checking if they are equal is not enough since the palettes can have less than 4 colors) + SetPal merged(palettes[i]); + merged.insert(pal.begin(), pal.end()); + if(merged.size() <= pal_size) + { + if(palettes[i].size() <= pal_size) + palettes[i] = merged; //Increase colors with this palette (it has less than 4 colors) + break; //Found palette + } + } + + if(i == palettes.size()) + { + //Palette not found, add a new one + palettes.push_back(pal); + } + + palettes_per_tile[(y / tile_h) * (image32.w / 8) + (x / 8)] = i; + } + } + + //Create the indexed image + image.data.clear(); + image.w = image32.w; + image.h = image32.h; + + unsigned int palette_count = PaletteCountApplyMaxLimit(max_palettes, palettes.size()); + + image.palettesize = palette_count * pal_size; + image.palette = new unsigned char[palette_count * pal_size * 4]; //pal_size colors * 4 bytes each + + // If we are using a sourcetileset and have more palettes than it defines + if (use_source_tileset && image.palettesize > source_palette_count) { + printf("Found %d extra palette(s) for target tilemap.\n", (unsigned int)(image.palettesize - source_palette_count) / 4); + } + for(size_t p = 0; p < palette_count; ++p) + { + int *color_ptr = (int*)&image.palette[p * pal_size * 4]; + + //TODO: if palettes[p].size() != pal_size we should probably try to fill the gaps based on grayscale values + + for(SetPal::iterator it = palettes[p].begin(); it != palettes[p].end(); ++ it, color_ptr ++) + { + unsigned char* c = (unsigned char*)&(*it); + *color_ptr = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]; + } + } + + for(size_t y = 0; y < image32.h; ++ y) + { + for(size_t x = 0; x < image32.w; ++x) + { + unsigned char* c32ptr = &image32.data[(image32.w * y + x) * 4]; + int color32 = (c32ptr[0] << 24) | (c32ptr[1] << 16) | (c32ptr[2] << 8) | c32ptr[3]; + unsigned char palette = palettes_per_tile[(y / tile_h) * (image32.w / 8) + (x / 8)]; + unsigned char index = std::distance(palettes[palette].begin(), palettes[palette].find(color32)); + image.data.push_back((palette << bpp) + index); + } + } + + //Test: output png to see how it looks + //Export(image, "temp.png"); + } + + if(sprite_w == 0) sprite_w = (int)image.w; + if(sprite_h == 0) sprite_h = (int)image.h; + if(pivot_x == 0xFFFFFF) pivot_x = sprite_w / 2; + if(pivot_y == 0xFFFFFF) pivot_y = sprite_h / 2; + if(pivot_w == 0xFFFFFF) pivot_w = sprite_w; + if(pivot_h == 0xFFFFFF) pivot_h = sprite_h; + + if(export_as_map) + { + //Extract map + GetMap(); + } + else + { + //Extract metasprites + for(int y = 0; y < (int)image.h; y += sprite_h) + { + for(int x = 0; x < (int)image.w; x += sprite_w) + { + GetMetaSprite(x, y, sprite_w, sprite_h, pivot_x, pivot_y); + } + } + } + + //Output .h FILE + FILE* file; + if(!output_binary||!export_as_map){ + file=fopen(output_filename_h.c_str(), "w"); + if (!file) { + printf("Error writing file"); + return 1; + } + + fprintf(file, "//AUTOGENERATED FILE FROM png2asset\n"); + fprintf(file, "#ifndef METASPRITE_%s_H\n", data_name.c_str()); + fprintf(file, "#define METASPRITE_%s_H\n", data_name.c_str()); + fprintf(file, "\n"); + fprintf(file, "#include <stdint.h>\n"); + fprintf(file, "#include <gbdk/platform.h>\n"); + fprintf(file, "#include <gbdk/metasprites.h>\n"); + fprintf(file, "\n"); + if(use_structs) + { + if(export_as_map) + { + fprintf(file, "#include \"TilesInfo.h\"\n"); + fprintf(file, "#include \"MapInfo.h\"\n"); + fprintf(file, "\n"); + fprintf(file, "extern const struct TilesInfo %s_tiles_info;\n", data_name.c_str()); + fprintf(file, "extern const struct MapInfo %s;\n", data_name.c_str()); + } + else + { + fprintf(file, "#include \"MetaSpriteInfo.h\"\n"); + fprintf(file, "\n"); + fprintf(file, "extern const struct MetaSpriteInfo %s;\n", data_name.c_str()); + } + } + else + { + fprintf(file, "#define %s_TILE_ORIGIN %d\n", data_name.c_str(), tile_origin); + fprintf(file, "#define %s_TILE_H %d\n", data_name.c_str(), tile_h); + fprintf(file, "#define %s_WIDTH %d\n", data_name.c_str(), sprite_w); + fprintf(file, "#define %s_HEIGHT %d\n", data_name.c_str(), sprite_h); + fprintf(file, "#define %s_TILE_COUNT %d\n", data_name.c_str(), ((unsigned int)tiles.size() - source_tileset_size) * (tile_h >> 3)); + fprintf(file, "#define %s_PALETTE_COUNT %d\n", data_name.c_str(), (unsigned int)image.palettesize / 4); + + if (includedMapOrMetaspriteData) { + + if(export_as_map) + { + fprintf(file, "#define %s_MAP_ATTRIBUTES ", data_name.c_str()); + if(use_map_attributes && map_attributes.size()) + fprintf(file, "%s_map_attributes\n", data_name.c_str()); + else + fprintf(file, "0\n"); + + fprintf(file, "#define %s_TILE_PALS ", data_name.c_str()); + if(use_map_attributes) + fprintf(file, "0\n"); + else + fprintf(file, "%s_tile_pals\n", data_name.c_str()); + } + else + { + fprintf(file, "#define %s_PIVOT_X %d\n", data_name.c_str(), pivot_x); + fprintf(file, "#define %s_PIVOT_Y %d\n", data_name.c_str(), pivot_y); + fprintf(file, "#define %s_PIVOT_W %d\n", data_name.c_str(), pivot_w); + fprintf(file, "#define %s_PIVOT_H %d\n", data_name.c_str(), pivot_h); + } + } + fprintf(file, "\n"); + fprintf(file, "BANKREF_EXTERN(%s)\n", data_name.c_str()); + fprintf(file, "\n"); + + // If we are not using a source tileset, or if we have extra palettes defined + if (image.palettesize - source_palette_count > 0 || !use_source_tileset) { + fprintf(file, "extern const palette_color_t %s_palettes[%d];\n", data_name.c_str(), (unsigned int)image.palettesize - source_palette_count); + } + if (includeTileData) { + fprintf(file, "extern const uint8_t %s_tiles[%d];\n", data_name.c_str(), (unsigned int)((tiles.size() - source_tileset_size) * (8 * tile_h * bpp / 8))); + } + + fprintf(file, "\n"); + if (includedMapOrMetaspriteData) { + if(export_as_map) + { + fprintf(file, "extern const unsigned char %s_map[%d];\n", data_name.c_str(), (unsigned int)map.size()); + + if(use_map_attributes) { + if(map_attributes.size()) { + fprintf(file, "extern const unsigned char %s_map_attributes[%d];\n", data_name.c_str(), (unsigned int)map_attributes.size()); + } + } + else if (includeTileData) + fprintf(file, "extern const unsigned char* %s_tile_pals[%d];\n", data_name.c_str(), (unsigned int)tiles.size()); + } + else + { + fprintf(file, "extern const metasprite_t* const %s_metasprites[%d];\n", data_name.c_str(), (unsigned int)sprites.size()); + } + } + } + fprintf(file, "\n"); + fprintf(file, "#endif"); + + + fclose(file); + + //Output .c FILE + file = fopen(output_filename.c_str(), "w"); + if(!file) { + printf("Error writing file"); + return 1; + } + + if (bank) fprintf(file, "#pragma bank %d\n\n", bank); + + fprintf(file, "//AUTOGENERATED FILE FROM png2asset\n\n"); + + fprintf(file, "#include <stdint.h>\n"); + fprintf(file, "#include <gbdk/platform.h>\n"); + fprintf(file, "#include <gbdk/metasprites.h>\n"); + fprintf(file, "\n"); + + fprintf(file, "BANKREF(%s)\n\n", data_name.c_str()); + + // Are we not using a source tileset, or do we have extra colors + if (image.palettesize - source_palette_count > 0||!use_source_tileset) { + + // Subtract however many palettes we had in the source tileset + fprintf(file, "const palette_color_t %s_palettes[%d] = {\n", data_name.c_str(), (unsigned int)image.palettesize - source_palette_count); + + // Offset by however many palettes we had in the source ileset + for (size_t i = source_palette_count/4; i < image.palettesize / 4; ++i) + { + if(i != 0) + fprintf(file, ",\n"); + fprintf(file, "\t"); + + unsigned char* pal_ptr = &image.palette[i * 16]; + for(int c = 0; c < 4; ++ c, pal_ptr += 4) + { + fprintf(file, "RGB8(%d, %d, %d)", pal_ptr[0], pal_ptr[1], pal_ptr[2]); + if(c != 3) + fprintf(file, ", "); + } + } + fprintf(file, "\n};\n"); + } + + if (includeTileData) { + fprintf(file, "\n"); + fprintf(file, "const uint8_t %s_tiles[%d] = {\n", data_name.c_str(), (unsigned int)((tiles.size()-source_tileset_size) * 8 * tile_h * bpp / 8)); + for (vector< Tile >::iterator it = tiles.begin()+ source_tileset_size; it != tiles.end(); ++it) + { + fprintf(file, "\t"); + + vector< unsigned char > packed_data = (*it).GetPackedData(pack_mode); + for(vector< unsigned char >::iterator it2 = packed_data.begin(); it2 != packed_data.end(); ++it2) + { + fprintf(file, "0x%02x", (*it2)); + if((it + 1) != tiles.end() || (it2 + 1) != packed_data.end()) + fprintf(file, ","); + } + + if(it != tiles.end()) + fprintf(file, "\n"); + } + fprintf(file, "};\n\n"); + } + + if(includedMapOrMetaspriteData) { + + if(!export_as_map) + { + for(vector< MetaSprite >::iterator it = sprites.begin(); it != sprites.end(); ++ it) + { + fprintf(file, "const metasprite_t %s_metasprite%d[] = {\n", data_name.c_str(), (int)(it - sprites.begin())); + fprintf(file, "\t"); + for(MetaSprite::iterator it2 = (*it).begin(); it2 != (*it).end(); ++ it2) + { + fprintf(file, "METASPR_ITEM(%d, %d, %d, %d), ", (*it2).offset_y, (*it2).offset_x, (*it2).offset_idx, (*it2).props); + } + fprintf(file, "METASPR_TERM\n"); + fprintf(file, "};\n\n"); + } + + fprintf(file, "const metasprite_t* const %s_metasprites[%d] = {\n\t", data_name.c_str(), (unsigned int)sprites.size()); + for(vector< MetaSprite >::iterator it = sprites.begin(); it != sprites.end(); ++ it) + { + fprintf(file, "%s_metasprite%d", data_name.c_str(), (int)(it - sprites.begin())); + if(it + 1 != sprites.end()) + fprintf(file, ", "); + } + fprintf(file, "\n};\n"); + + if(use_structs) + { + fprintf(file, "\n"); + fprintf(file, "#include \"MetaSpriteInfo.h\"\n"); + fprintf(file, "const struct MetaSpriteInfo %s = {\n", data_name.c_str()); + fprintf(file, "\t%d, //width\n", pivot_w); + fprintf(file, "\t%d, //height\n", pivot_h); + fprintf(file, "\t%d, //num tiles\n", (unsigned int)tiles.size() * (tile_h >> 3)); + fprintf(file, "\t%s_tiles, //tiles\n", data_name.c_str()); + fprintf(file, "\t%d, //num palettes\n", (unsigned int)(image.palettesize / pal_size)); + fprintf(file, "\t%s_palettes, //CGB palette\n", data_name.c_str()); + fprintf(file, "\t%d, //num sprites\n", (unsigned int)sprites.size()); + fprintf(file, "\t%s_metasprites, //metasprites\n", data_name.c_str()); + fprintf(file, "};\n"); + } + } + else + { + if (includeTileData) { + //Export tiles pals (if any) + if(!use_map_attributes) + { + fprintf(file, "\n"); + fprintf(file, "const uint8_t %s_tile_pals[%d] = {\n\t", data_name.c_str(), (unsigned int)tiles.size()- source_tileset_size); + for(vector< Tile >::iterator it = tiles.begin()+ source_tileset_size; it != tiles.end(); ++ it) + { + if(it != tiles.begin()) + fprintf(file, ", "); + fprintf(file, "%d", it->pal); + } + fprintf(file, "\n};\n"); + } + + if(use_structs) + { + //Export Tiles Info + fprintf(file, "\n"); + fprintf(file, "#include \"TilesInfo.h\"\n"); + fprintf(file, "BANKREF(%s_tiles_info)\n", data_name.c_str()); + fprintf(file, "const struct TilesInfo %s_tiles_info = {\n", data_name.c_str()); + fprintf(file, "\t%d, //num tiles\n", (unsigned int)tiles.size() * (tile_h >> 3)); + fprintf(file, "\t%s_tiles, //tiles\n", data_name.c_str()); + fprintf(file, "\t%d, //num palettes\n", (unsigned int)(image.palettesize / pal_size)); + fprintf(file, "\t%s_palettes, //palettes\n", data_name.c_str()); + if(!use_map_attributes) + fprintf(file, "\t%s_tile_pals, //tile palettes\n", data_name.c_str()); + else + fprintf(file, "\t0 //tile palettes\n"); + fprintf(file, "};\n"); + } + } + + //Export map + fprintf(file, "\n"); + fprintf(file, "const unsigned char %s_map[%d] = {\n", data_name.c_str(), (unsigned int)map.size()); + size_t line_size = map.size() / (image.h / 8); + if (output_transposed) { + + for(size_t i = 0; i < line_size; ++i) + { + fprintf(file, "\t"); + for (size_t j = 0; j < image.h / 8; ++j) + { + fprintf(file, "0x%02x,", map[j * line_size + i]); + } + fprintf(file, "\n"); + } + } + else { + + for (size_t j = 0; j < image.h / 8; ++j) + { + fprintf(file, "\t"); + for (size_t i = 0; i < line_size; ++i) + { + fprintf(file, "0x%02x,", map[j * line_size + i]); + } + fprintf(file, "\n"); + } + } + fprintf(file, "};\n"); + + + //Export map attributes (if any) + if(use_map_attributes && map_attributes.size()) + { + fprintf(file, "\n"); + fprintf(file, "const unsigned char %s_map_attributes[%d] = {\n", data_name.c_str(), (unsigned int)map_attributes.size()); + if (output_transposed) { + for (size_t i = 0; i < line_size; ++i) + { + fprintf(file, "\t"); + for (size_t j = 0; j < image.h / 8; ++j) + { + fprintf(file, "0x%02x,", map_attributes[j * line_size + i]); + } + fprintf(file, "\n"); + } + } + else { + for (size_t j = 0; j < image.h / 8; ++j) + { + fprintf(file, "\t"); + for (size_t i = 0; i < line_size; ++i) + { + fprintf(file, "0x%02x,", map_attributes[j * line_size + i]); + } + fprintf(file, "\n"); + } + } + + fprintf(file, "};\n"); + } + + if(use_structs) + { + //Export Map Info + fprintf(file, "\n"); + fprintf(file, "#include \"MapInfo.h\"\n"); + fprintf(file, "BANKREF_EXTERN(%s_tiles_info)\n", data_name.c_str()); + fprintf(file, "const struct MapInfo %s = {\n", data_name.c_str()); + fprintf(file, "\t%s_map, //map\n", data_name.c_str()); + fprintf(file, "\t%d, //with\n", image.w >> 3); + fprintf(file, "\t%d, //height\n", image.h >> 3); + if (use_map_attributes && map_attributes.size()) + fprintf(file, "\t%s_map_attributes, //map attributes\n", data_name.c_str()); + else + fprintf(file, "\t%s, //map attributes\n", "0"); + fprintf(file, "\tBANK(%s_tiles_info), //tiles bank\n", data_name.c_str()); + fprintf(file, "\t&%s_tiles_info, //tiles info\n", data_name.c_str()); + fprintf(file, "};\n"); + } + } + } + + fclose(file); + } + + // If we are exporting as a map, and binary output is desired + else if (export_as_map) { + + std::ofstream mapBinaryFile, mapAttributesBinaryfile,tilesBinaryFile; + mapBinaryFile.open(output_filename_bin, std::ios_base::binary); + tilesBinaryFile.open(output_filename_tiles_bin, std::ios_base::binary); + + for (vector< Tile >::iterator it = tiles.begin() + source_tileset_size; it != tiles.end(); ++it) + { + + vector< unsigned char > packed_data = (*it).GetPackedData(pack_mode); + for (vector< unsigned char >::iterator it2 = packed_data.begin(); it2 != packed_data.end(); ++it2) + { + + const char chars[] = { (const char)(*it2) }; + tilesBinaryFile.write(chars, 1); + } + + } + + + // Open our file for writing attributes if speciied + if (use_map_attributes)mapAttributesBinaryfile.open(output_filename_attributes_bin, std::ios_base::binary); + + int columns = image.w >> 3; + int rows = image.h >> 3; + + // If we want the values to be column-by-column + if (output_transposed) { + + // Swap the column/row for loops + for (int column = 0; column < columns; column++) { + for (int row = 0; row < rows; ++row) { + + int tile = column + row * columns; + + const char mapChars[] = { (const char)map[tile] }; + + // Write map items column-by-column + mapBinaryFile.write(mapChars, 1); + if(use_map_attributes) { + const char mapAttributeChars[] = { (const char)map_attributes[tile] }; + mapAttributesBinaryfile.write(mapAttributeChars, 1); + } + } + } + } + else { + + // Write the arrays as-is, row-by-row + mapBinaryFile.write((const char*)(&map[0]), rows * columns); + if (use_map_attributes)mapAttributesBinaryfile.write((const char*)(&map_attributes[0]), rows * columns); + } + + // Finalzie the files + mapBinaryFile.close(); + tilesBinaryFile.close(); + if (use_map_attributes)mapAttributesBinaryfile.close(); + + } +} diff --git a/gbdk/gbdk-support/png2asset/png2asset.sln b/gbdk/gbdk-support/png2asset/png2asset.sln new file mode 100644 index 00000000..c10e3616 --- /dev/null +++ b/gbdk/gbdk-support/png2asset/png2asset.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "png2asset", "png2asset.vcxproj", "{6BA57070-30F6-442A-9DDA-32876359CB99}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6BA57070-30F6-442A-9DDA-32876359CB99}.Debug|x64.ActiveCfg = Debug|x64 + {6BA57070-30F6-442A-9DDA-32876359CB99}.Debug|x64.Build.0 = Debug|x64 + {6BA57070-30F6-442A-9DDA-32876359CB99}.Debug|x86.ActiveCfg = Debug|Win32 + {6BA57070-30F6-442A-9DDA-32876359CB99}.Debug|x86.Build.0 = Debug|Win32 + {6BA57070-30F6-442A-9DDA-32876359CB99}.Release|x64.ActiveCfg = Release|x64 + {6BA57070-30F6-442A-9DDA-32876359CB99}.Release|x64.Build.0 = Release|x64 + {6BA57070-30F6-442A-9DDA-32876359CB99}.Release|x86.ActiveCfg = Release|Win32 + {6BA57070-30F6-442A-9DDA-32876359CB99}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/gbdk/gbdk-support/png2asset/png2asset.vcxproj b/gbdk/gbdk-support/png2asset/png2asset.vcxproj new file mode 100644 index 00000000..e7fbf6f2 --- /dev/null +++ b/gbdk/gbdk-support/png2asset/png2asset.vcxproj @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{6BA57070-30F6-442A-9DDA-32876359CB99}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>png2asset</RootNamespace> + <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + <PostBuildEvent> + <Command>copy $(TargetPath) $(ZGB_PATH)\..\env\gbdk\bin +copy $(TargetPath) C:\Users\Zalo\Desktop\gb\gbdk-2020\build\gbdk\bin</Command> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <PrecompiledHeader> + </PrecompiledHeader> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + <PostBuildEvent> + <Command>copy $(TargetPath) $(ZGB_PATH)\..\env\gbdk\bin</Command> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <PrecompiledHeader> + </PrecompiledHeader> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + <PostBuildEvent> + <Command>copy $(TargetPath) $(ZGB_PATH)\..\env\gbdk\bin</Command> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="lodepng.cpp" /> + <ClCompile Include="png2asset.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="lodepng.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/gbdk/gbdk-support/png2asset/png2asset.vcxproj.filters b/gbdk/gbdk-support/png2asset/png2asset.vcxproj.filters new file mode 100644 index 00000000..3884e1af --- /dev/null +++ b/gbdk/gbdk-support/png2asset/png2asset.vcxproj.filters @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="png2asset.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="lodepng.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="lodepng.h"> + <Filter>Source Files</Filter> + </ClInclude> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/gbdk/gbdk-support/png2asset/readme.md b/gbdk/gbdk-support/png2asset/readme.md new file mode 100644 index 00000000..a1bb1f41 --- /dev/null +++ b/gbdk/gbdk-support/png2asset/readme.md @@ -0,0 +1,53 @@ +# png2asset +A tool that converts png to maps or meta sprites in C for gbdk 2020 + +### Working with png2asset + - The origin (pivot) for the metasprite is not required to be in the upper left-hand corner as with regular hardware sprites. See `-px` and `-py`. + + - The conversion process supports using both SPRITES_8x8 (`-spr8x8`) and SPRITES_8x16 mode (`-spr8x16`). If 8x16 mode is used then the height of the metasprite must be a multiple of 16. + +#### Terminology +The following abbreviations are used in this section: +* Original Game Boy and Game Boy Pocket style hardware: `DMG` +* Game Boy Color: `CGB` + +#### Conversion Process +png2asset accepts any png as input, although that does not mean any image will be valid. The program will follow the next steps: + - The image will be subdivided into tiles of 8x8 or 8x16 + - For each tile a palette will be generated + - If there are more than 4 colors in the palette it will throw an error + - The palette will be sorted from darkest to lightest. If there is a transparent color that will be the first one (this will create a palette that will also work with `DMG` devices) + - If there are more than 8 palettes the program will throw an error + +With all this, the program will generate a new indexed image (with palette), where each 4 colors define a palette and all colors within a tile can only have colors from one of these palettes + +It is also posible to pass an indexed 8-bit png with the palette properly sorted out, using `-keep_palette_order` + - Palettes will be extracted from the image palette in groups of 4 colors. + - Each tile can only have colors from one of these palettes per tile + - The maximum number of colors is 32 + +Using this image a tileset will be created + - Duplicated tiles will be removed + - Tiles will be matched without mirror, using vertical mirror, horizontal mirror or both (use `-noflip` to turn off matching mirrored tiles) + - The palette won't be taken into account for matching, only the pixel color order, meaning there will be a match between tiles using different palettes but looking identical on grayscale + +#### Maps +Passing `-map` the png can be converted to a map that can be used in both the background and the window. In this case, png2asset will generate: + - The palettes + - The tileset + - The map + - The color info + - By default, an array of palette index for each tile. This is not the way the hardware works but it takes less space and will create maps compatibles with both `DMG` and `CGB` devices. + - Passing `-use_map_attributes` will create an array of map attributes. It will also add mirroring info for each tile and because of that maps created with this won't be compatible with. + - Use `-noflip` to make background maps which are compatible with `DMG` devices. + +#### Meta sprites +By default the png will be converted to metasprites. The image will be subdivided into meta sprites of `-sw` x `-sh`. In this case png2asset will generate: + - The metasprites, containing an array of: + - tile index + - y offset + - x offset + - flags, containing the mirror info, the palettes for both DMG and GBC and the sprite priority + - The metasprites array + + |
