Files
gbdk-2020/gbdk-support/png2asset/process_arguments.cpp
bbbbbr e4cee3cfb2 png2asset: -use_metafile option to load args from <inputfile>.meta #802
- File not found is only a warning, not an error
- metafile args are processed after args and appended to logged output
- Split main arg processing function into a couple smaller ones
2025-12-01 15:25:39 -08:00

472 lines
17 KiB
C++

#include <vector>
#include <string>
#include <algorithm>
#include <cstring>
#include <set>
#include <stdio.h>
#include <fstream>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <sstream>
#include "lodepng.h"
#include "mttile.h"
#include "export.h"
#include "map_attributes.h"
#include "palettes.h"
#include "cmp_int_color.h"
#include "image_utils.h"
#include "image_data.h"
#include "maps.h"
#include "metasprites.h"
#include "metasprites_functions.h"
#include "process_arguments.h"
#include "png_image.h"
#include "tiles.h"
#define ARG_SKIP_NONE 0
#define ARG_AT_INPUT_FILENAME 1
#define ARG_AFTER_INPUT_FILENAME 2
using namespace std;
static string str_remove_path(string str_in);
static void logArguments(int startIndex, int argc, const char* argv[], PNG2AssetArguments* args);
static void initArguments(PNG2AssetArguments* args);
static void showHelp(void);
static int processArguments(int startIndex, int argc, const char* argv[], PNG2AssetArguments* args);
static int handleMetaFileArgs(PNG2AssetArguments* args);
// Strip any leading path and slashes
static string str_remove_path(string str_in) {
size_t slash_pos = str_in.find_last_of('/');
if (slash_pos != str_in.npos)
str_in = str_in.substr(slash_pos, str_in.length() - slash_pos);
slash_pos = str_in.find_last_of('\\');
if (slash_pos != str_in.npos)
str_in = str_in.substr(slash_pos, str_in.length() - slash_pos);
return str_in;
}
static void initArguments(PNG2AssetArguments* args) {
//default values for some params
args->spriteSize.width = 0;
args->spriteSize.height = 0;
args->map_attributes_size.width = 0;
args->map_attributes_size.height = 0;
args->map_attributes_packed_size.width = 0;
args->map_attributes_packed_size.height = 0;
args->pivot.x = 0xFFFFFF;
args->pivot.y = 0xFFFFFF;
args->pivot.width = 0xFFFFFF;
args->pivot.height = 0xFFFFFF;
args->max_palettes = 8;
args->keep_palette_order = false;
args->repair_indexed_pal = false;
args->output_binary = false;
args->output_transposed = false;
args->export_as_map = false;
args->use_map_attributes = false;
args->use_2x2_map_attributes = false;
args->pack_map_attributes = false;
args->convert_rgb_to_nes = false;
args->includeTileData = true;
args->includedMapOrMetaspriteData = true;
args->keep_duplicate_tiles = false;
args->keep_empty_sprite_tiles = false;
args->include_palettes = true;
args->use_structs = false;
args->flip_tiles = true;
// args->errorCode;
args->bank = BANK_NUM_UNSET;
args->area_specified = false;
args->sprite_mode = SPR_8x16;
args->bpp = 2;
args->props_default = 0;
args->tile_origin = 0; // Default to no tile index offset
args->source_total_color_count = 0;
args->source_tileset_size = 0;
args->has_source_tilesets = false;
args->has_entity_tileset = false;
args->processing_mode = MODE_MAIN_IMAGE;
args->pack_mode = Tile::GB;
args->map_entry_size_bytes = 1;
args->args_for_logging_to_output = "";
args->relative_paths = false;
args->use_metafile = false;
}
void showHelp(void) {
printf("usage: png2asset <file>.png [options]\n");
printf("-o <filename> ouput file (if not used then default is <png file>.c)\n");
printf("-c <filename> deprecated, same as -o\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 width (default: metasprites width)\n");
printf("-ph <height> metasprites collision rect height (default: metasprites height)\n");
printf("-spr8x8 for 8x8 hardware sprites (use SPRITES_8x8)\n");
printf("-spr8x16 for 8x16 hardware sprites (use SPRITES_8x16) (the default)\n");
printf("-spr16x16msx MSX only: for 16x16 hardware sprites (use SPRITES_16x16)\n");
printf("-sprite_no_optimize keep empty sprite tiles, do not remove duplicate tiles\n");
printf("-b <banknum> Bank number (default: fixed bank)\n");
printf("-area <area name> Area name. Alters \"pragma bank ...\" output to constseg style. (Ex: -area LIT)\n");
printf("-keep_palette_order use png palette\n");
printf("-repair_indexed_pal try to repair indexed tile palettes (implies \"-keep_palette_order\")\n");
printf("-noflip disable tile flip\n");
printf("-map Export as map (tileset + bg) instead of default metasprite output\n");
printf("-use_map_attributes Use CGB BG Map attributes\n");
printf("-use_nes_attributes Use NES BG Map attributes\n");
printf("-use_nes_colors Convert RGB color values to NES PPU colors\n");
printf("-use_structs Group the exported info into structs (default: false) (used by ZGB Game Engine)\n");
printf("-bpp bits per pixel: 1, 2, 4 (default: 2. using 1 auto-enables \"-pack_mode 1bpp\")\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, nes, sgb, sms, 1bpp (default: gb. using 1bpp auto-enables \"-bpp 1\")\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("-entity_tileset (maps only) mark matching tiles counting from 255 down, entity patterns not exported\n");
printf("-keep_duplicate_tiles do not remove duplicate tiles (default: not enabled)\n");
printf("-no_palettes do not export palette data\n");
printf("-bin export to binary format (requires -map)\n");
printf("-transposed export transposed (column-by-column instead of row-by-row)\n");
printf("-rel_paths paths to tilesets are relative to the input file path\n");
printf("-use_metafile Read extra options from file <inputfile>.meta (file missing not an error)\n");
}
static void logArguments(int startIndex, int argc, const char* argv[], PNG2AssetArguments* args) {
// Save all args for logging into output files
for (int i = startIndex; i < argc; ++i) {
args->args_for_logging_to_output.append(" ").append( str_remove_path((string)argv[i]) );
}
}
static int processArguments(int startIndex, int argc, const char* argv[], PNG2AssetArguments* args) {
//Parse argv
for (int i = startIndex; i < argc; ++i)
{
if(!strcmp(argv[i], "-sw"))
{
args->spriteSize.width = atoi(argv[++i]);
}
else if(!strcmp(argv[i], "-sh"))
{
args->spriteSize.height = atoi(argv[++i]);
}
else if(!strcmp(argv[i], "-sp"))
{
args->props_default = strtol(argv[++i], NULL, 16);
}
else if(!strcmp(argv[i], "-px"))
{
args->pivot.x = atoi(argv[++i]);
}
else if(!strcmp(argv[i], "-py"))
{
args->pivot.y = atoi(argv[++i]);
}
else if(!strcmp(argv[i], "-pw"))
{
args->pivot.width = atoi(argv[++i]);
}
else if(!strcmp(argv[i], "-ph"))
{
args->pivot.height = atoi(argv[++i]);
}
else if(!strcmp(argv[i], "-spr8x8"))
{
args->sprite_mode = SPR_8x8;
}
else if(!strcmp(argv[i], "-spr8x16"))
{
args->sprite_mode = SPR_8x16;
}
else if(!strcmp(argv[i], "-spr16x16msx"))
{
args->sprite_mode = SPR_16x16_MSX;
}
else if(!strcmp(argv[i], "-c") || !strcmp(argv[i], "-o"))
{
if ((i + 1) >= argc) {
printf("Error: -c or -o requires a filename, none specified\n");
return EXIT_FAILURE;
} else if (argv[i+1][0] == '-') {
printf("Error: next argument after -o looks like an option instead of a filename (\"%s\")\n", argv[i + 1]);
return EXIT_FAILURE;
}
args->output_filename = argv[++i];
}
else if(!strcmp(argv[i], "-b"))
{
args->bank = atoi(argv[++i]);
}
else if(!strcmp(argv[i], "-area"))
{
args->area_name = argv[++i];
args->area_specified = true;
}
else if(!strcmp(argv[i], "-keep_palette_order"))
{
args->keep_palette_order = true;
}
else if(!strcmp(argv[i], "-repair_indexed_pal"))
{
args->repair_indexed_pal = true;
args->keep_palette_order = true; // -repair_indexed_pal requires -keep_palette_order, so force it on
}
else if(!strcmp(argv[i], "-noflip"))
{
args->flip_tiles = false;
}
else if(!strcmp(argv[i], "-map"))
{
args->export_as_map = true;
}
else if(!strcmp(argv[i], "-use_map_attributes"))
{
args->use_map_attributes = true;
}
else if(!strcmp(argv[i], "-use_nes_attributes"))
{
args->use_map_attributes = true;
args->use_2x2_map_attributes = true;
args->pack_map_attributes = true;
}
else if(!strcmp(argv[i], "-use_nes_colors"))
{
args->convert_rgb_to_nes = true;
}
else if(!strcmp(argv[i], "-use_structs"))
{
args->use_structs = true;
}
else if(!strcmp(argv[i], "-bpp"))
{
args->bpp = atoi(argv[++i]);
// If set to "-bpp 1" auto-enable "-pack_mode 1bpp"
if (atoi(argv[i]) == 1) args->pack_mode = Tile::BPP1;
}
else if(!strcmp(argv[i], "-max_palettes"))
{
args->max_palettes = atoi(argv[++i]);
if(args->max_palettes == 0)
{
printf("-max_palettes must be larger than zero\n");
return EXIT_FAILURE;
}
}
else if(!strcmp(argv[i], "-pack_mode"))
{
std::string pack_mode_str = argv[++i];
if(pack_mode_str == "gb") args->pack_mode = Tile::GB;
else if(pack_mode_str == "nes") args->pack_mode = Tile::NES;
else if(pack_mode_str == "sgb") {
args->pack_mode = Tile::SGB;
// SGB attributes are packed in map data, so 2 bytes per map tile
args->map_entry_size_bytes = 2;
}
else if(pack_mode_str == "sms") {
args->pack_mode = Tile::SMS;
// SMS attributes are packed in map data, so 2 bytes per map tile
args->map_entry_size_bytes = 2;
}
else if(pack_mode_str == "1bpp") {
args->pack_mode = Tile::BPP1;
// If set to "-pack_mode 1bpp" auto-enable "-bpp 1"
args->bpp = 1;
}
else
{
printf("-pack_mode must be one of gb, nes, sgb, sms, 1bpp\n");
return EXIT_FAILURE;
}
}
else if(!strcmp(argv[i], "-tile_origin"))
{
args->tile_origin = atoi(argv[++i]);
}
else if(!strcmp(argv[i], "-maps_only") || !strcmp(argv[i], "-metasprites_only"))
{
args->includeTileData = false;
}
else if(!strcmp(argv[i], "-tiles_only"))
{
args->includedMapOrMetaspriteData = false;
}
else if(!strcmp(argv[i], "-keep_duplicate_tiles"))
{
args->keep_duplicate_tiles = true;
}
else if(!strcmp(argv[i], "-sprite_no_optimize"))
{
args->keep_duplicate_tiles = true;
args->keep_empty_sprite_tiles = true;
}
else if(!strcmp(argv[i], "-no_palettes"))
{
args->include_palettes = false;
}
else if(!strcmp(argv[i], "-source_tileset"))
{
// Warning / TODO:FIXME
// includeTileData gets overridden back to TRUE if any tiles not present in the source tileset are found.
// This makes understanding it's full export behavior much harder. Would be better to remove it and try to simplify logic.
args->includeTileData = false;
args->source_tilesets.push_back(argv[++i]);
}
else if(!strcmp(argv[i], "-entity_tileset"))
{
args->entity_tileset_filename = argv[++i];
args->has_entity_tileset = true;
}
else if(!strcmp(argv[i], "-bin"))
{
args->output_binary = true;
}
else if(!strcmp(argv[i], "-transposed"))
{
args->output_transposed = true;
}
else if(!strcmp(argv[i], "-rel_paths")) {
args->relative_paths = true;
}
else if(!strcmp(argv[i], "-use_metafile")) {
args->use_metafile = true;
}
else {
printf("Warning: Argument \"%s\" not recognized\n", argv[i]);
}
}
return EXIT_SUCCESS;
}
// Read in and process a set of arguments from a file named <inputfile>.meta
static int handleMetaFileArgs(PNG2AssetArguments* args) {
string fname = args->input_filename + ".meta";
ifstream metaFile(fname);
if ( metaFile )
{
static vector<string> argStrings;
static std::vector<char const*> metafile_argv; // Static for program scope, const to ensure c_str() pointers remain valid
// Read file contents
stringstream metaFileBuffer;
metaFileBuffer << metaFile.rdbuf();
metaFile.close();
// Split strings on spaces/newlines
string argEntry;
argStrings.clear();
while (metaFileBuffer >> argEntry) {
argStrings.push_back(argEntry);
}
// Build argv style array
int metafile_argc = static_cast<int>(argStrings.size());
metafile_argv.clear();
metafile_argv.reserve(metafile_argc + 1); // +1 for null terminator entry (optional with our usage)
for (const auto& s : argStrings) {
metafile_argv.push_back(s.c_str());
}
metafile_argv.push_back(nullptr);
// Append args to logged ones and then process them
logArguments(ARG_SKIP_NONE, metafile_argc, metafile_argv.data(), args);
if (processArguments(ARG_SKIP_NONE, metafile_argc, metafile_argv.data(), args) == EXIT_FAILURE)
return EXIT_FAILURE;
} else {
printf("Warning: -use_metafile specified but no meta file found at: %s\n", fname.c_str());
}
return EXIT_SUCCESS;
}
int processPNG2AssetArguments(int argc, char* argv[], PNG2AssetArguments* args) {
initArguments(args);
if (argc < 2) {
showHelp();
return EXIT_SUCCESS;
}
//default params
args->input_filename = argv[ARG_AT_INPUT_FILENAME];
args->output_filename = argv[ARG_AT_INPUT_FILENAME];
args->output_filename = args->output_filename.substr(0, args->output_filename.size() - 4) + ".c";
logArguments(ARG_AT_INPUT_FILENAME, argc, (const char **)argv, args);
if (processArguments(ARG_AFTER_INPUT_FILENAME, argc, (const char **)argv, args) == EXIT_FAILURE)
return EXIT_FAILURE;
if (args->use_metafile) {
if (handleMetaFileArgs(args) == EXIT_FAILURE)
return EXIT_FAILURE;
}
int slash_pos = (int)args->output_filename.find_last_of('/');
if(slash_pos == -1)
slash_pos = (int)args->output_filename.find_last_of('\\');
int dot_pos = (int)args->output_filename.find_first_of('.', slash_pos == -1 ? 0 : slash_pos);
args->output_filename_h = args->output_filename.substr(0, dot_pos) + ".h";
args->output_filename_bin = args->output_filename.substr(0, dot_pos) + "_map.bin";
args->output_filename_attributes_bin = args->output_filename.substr(0, dot_pos) + "_map_attributes.bin";
args->output_filename_tiles_bin = args->output_filename.substr(0, dot_pos) + "_tiles.bin";
args->output_filename_palettes_bin = args->output_filename.substr(0, dot_pos) + "_palettes.bin";
args->data_name = args->output_filename.substr(slash_pos + 1, dot_pos - 1 - slash_pos);
replace(args->data_name.begin(), args->data_name.end(), '-', '_');
if ((args->area_specified == true) && (args->bank == BANK_NUM_UNSET)) {
printf("Error: \"-area\" specified but bank number is missing. A bank number is required, use \"-b\"\n");
return EXIT_FAILURE;
}
if((args->output_binary) && (args->export_as_map == false)) {
printf("Error: \"-bin\" export mode only works when \"-map\" is enabled\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}