shithub: pokecrystal

Download patch

ref: a18f83b911afa94f4f3522204cd404c3e92df58e
parent: c8f06f45d59bf1008e98497481629edc77993300
author: Rangi <remy.oukaour+rangi42@gmail.com>
date: Thu Sep 2 13:15:50 EDT 2021

Rewrite tools/pokemon_animation_graphics.c, and keep `void usage` and `void parse_args` at the top of tools' files

--- a/tools/pokemon_animation_graphics.c
+++ b/tools/pokemon_animation_graphics.c
@@ -1,41 +1,52 @@
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdbool.h>
-#include <getopt.h>
-#include <stdint.h>
+#include "common.h"
 
-static void usage(void) {
-	printf("Usage: pokemon_animation_graphics [-o outfile] [-t mapfile] 2bpp_file dimensions_file\n");
-	exit(1);
-}
-
 struct Options {
-	int girafarig;
+	const char *out_filename;
+	const char *map_filename;
+	bool girafarig;
 };
 
-struct Options Options = {0};
+void usage() {
+	fputs("Usage: pokemon_animation_graphics [-h|--help] [-o|--output front.animated.2bpp] [-t|--tilemap front.animated.tilemap] [--girafarig] front.2bpp front.dimensions\n", stderr);
+}
 
+void parse_args(int argc, char *argv[], struct Options *options) {
+	struct option long_options[] = {
+		{"output", required_argument, 0, 'o'},
+		{"tilemap", required_argument, 0, 't'},
+		{"girafarig", no_argument, 0, 'g'},
+		{"help", no_argument, 0, 'h'},
+		{0}
+	};
+	for (int opt; (opt = getopt_long(argc, argv, "o:t:h", long_options)) != -1;) {
+		switch (opt) {
+		case 'o':
+			options->out_filename = optarg;
+			break;
+		case 't':
+			options->map_filename = optarg;
+			break;
+		case 'g':
+			options->girafarig = true;
+			break;
+		case 'h':
+			usage();
+			exit(0);
+			break;
+		default:
+			usage();
+			exit(1);
+		}
+	}
+}
 
-struct Tilemap {
-	uint8_t* data;
-	int size;
-};
+#define TILE_SIZE 16
 
-struct Graphic {
-	uint8_t* data;
-	int size;
-};
-
-void transpose_tiles(uint8_t* tiles, int width, int size, int tile_size) {
-	int i;
-	int j;
-	uint8_t* new_tiles;
-	new_tiles = malloc(size);
-	for (i = 0; i < size; i++) {
-		j = i / tile_size * width * tile_size;
-		j = j % size + tile_size * (j / size) + i % tile_size;
+void transpose_tiles(uint8_t *tiles, int width, int size) {
+	uint8_t *new_tiles = malloc_verbose(size);
+	for (int i = 0; i < size; i++) {
+		int j = i / TILE_SIZE * width * TILE_SIZE;
+		j = (j / size) * TILE_SIZE + j % size + i % TILE_SIZE;
 		new_tiles[j] = tiles[i];
 	}
 	memcpy(tiles, new_tiles, size);
@@ -42,27 +53,14 @@
 	free(new_tiles);
 }
 
-bool compare_tile(uint8_t *tile, uint8_t *other) {
-	int j;
-	for (j = 0; j < 16; j++) {
-		if (tile[j] != other[j]) {
-			return false;
-		}
-	}
-	return true;
-}
-
-int get_tile_index(uint8_t* tile, uint8_t* tiles, int num_tiles, int preferred_tile_id) {
+int get_tile_index(const uint8_t *tile, const uint8_t *tiles, int num_tiles, int preferred_tile_id) {
 	if (preferred_tile_id >= 0 && preferred_tile_id < num_tiles) {
-		uint8_t *other = &tiles[preferred_tile_id * 16];
-		if (compare_tile(tile, other)) {
+		if (!memcmp(tile, &tiles[preferred_tile_id * TILE_SIZE], TILE_SIZE)) {
 			return preferred_tile_id;
 		}
 	}
-	int i;
-	for (i = 0; i < num_tiles; i++) {
-		uint8_t *other = &tiles[i * 16];
-		if (compare_tile(tile, other)) {
+	for (int i = 0; i < num_tiles; i++) {
+		if (!memcmp(tile, &tiles[i * TILE_SIZE], TILE_SIZE)) {
 			return i;
 		}
 	}
@@ -69,181 +67,122 @@
 	return -1;
 }
 
-FILE *fopen_verbose(char *filename, char *mode) {
-	FILE *f = fopen(filename, mode);
-	if (!f) {
-		fprintf(stderr, "Could not open file: \"%s\"\n", filename);
+uint8_t read_dimensions(const char *filename) {
+	long filesize;
+	uint8_t *bytes = read_u8(filename, &filesize);
+	if (filesize != 1) {
+		error_exit("%s: invalid dimensions file\n", filename);
 	}
-	return f;
+	uint8_t dimensions = bytes[0];
+	free(bytes);
+	return dimensions;
 }
 
-void create_tilemap(struct Tilemap* tilemap, struct Graphic* graphic, char* graphics_filename, int width, int height) {
-	long graphics_size;
-	uint8_t* graphics;
-	FILE* f;
-	int i;
-	int tile;
+uint8_t *read_tiles(const char *filename, int width, int height, long *tiles_size) {
+	int frame_size = width * height * TILE_SIZE;
 
-	f = fopen_verbose(graphics_filename, "rb");
-	if (!f) {
-		exit(1);
+	uint8_t *tiles = read_u8(filename, tiles_size);
+	if (!*tiles_size) {
+		error_exit("%s: empty file\n", filename);
+	} else if (*tiles_size % TILE_SIZE) {
+		error_exit("%s: not divisible into 8x8-px 2bpp tiles\n", filename);
+	} else if (*tiles_size % frame_size) {
+		error_exit("%s: not divisible into %dx%d-tile frames\n", filename, width, height);
 	}
-	fseek(f, 0, SEEK_END);
-	graphics_size = ftell(f);
-	if (!graphics_size) {
-		fprintf(stderr, "empty file %s\n", graphics_filename);
-		exit(1);
+
+	int num_frames = *tiles_size / frame_size;
+	for (int i = 0; i < num_frames; i++) {
+		transpose_tiles(&tiles[i * frame_size], width, frame_size);
 	}
-	rewind(f);
-	graphics = malloc(graphics_size);
-	if (!graphics) {
-		fprintf(stderr, "malloc failure\n");
-		exit(1);
+
+	return tiles;
+}
+
+void write_graphics(const char *filename, const uint8_t *tiles, long tiles_size, int num_tiles_per_frame, bool girafarig) {
+	int max_size = tiles_size;
+	int max_num_tiles = max_size / TILE_SIZE;
+	if (girafarig) {
+		// Ensure space for a duplicate of tile 0 at the end
+		max_size += TILE_SIZE;
 	}
-	if (graphics_size != (long)fread(graphics, 1, graphics_size, f)) {
-		fprintf(stderr, "failed to read file %s\n", graphics_filename);
-		exit(1);
+	uint8_t *data = malloc_verbose(max_size);
+
+	int num_tiles = 0;
+#define DATA_APPEND_TILES(tile, length) do { \
+	memcpy(&data[num_tiles * TILE_SIZE], &tiles[(tile) * TILE_SIZE], (length) * TILE_SIZE); \
+	num_tiles += (length); \
+} while (0)
+	// Copy the first frame directly
+	DATA_APPEND_TILES(0, num_tiles_per_frame);
+	// Skip redundant tiles in the animated frames
+	for (int i = num_tiles_per_frame; i < max_num_tiles; i++) {
+		int index = get_tile_index(&tiles[i * TILE_SIZE], data, num_tiles, i % num_tiles_per_frame);
+		if (index == -1) {
+			DATA_APPEND_TILES(i, 1);
+		}
 	}
-	fclose(f);
+	if (girafarig) {
+		// Add a duplicate of tile 0 to the end
+		DATA_APPEND_TILES(0, 1);
+	}
+#undef DATA_APPEND_TILES
 
-	int num_tiles_per_frame = width * height;
-	int tile_size = 16;
-	int num_frames = graphics_size / (tile_size * num_tiles_per_frame);
-	int frame_size = num_tiles_per_frame * tile_size;
+	write_u8(filename, data, num_tiles * TILE_SIZE);
+	free(data);
+}
 
-	// transpose each frame
-	for (i = 0; i < num_frames; i++) {
-		transpose_tiles(graphics + i * frame_size, width, frame_size, tile_size);
-	}
+void write_tilemap(const char *filename, const uint8_t *tiles, long tiles_size, int num_tiles_per_frame, bool girafarig) {
+	int size = tiles_size / TILE_SIZE;
+	uint8_t *data = malloc_verbose(size);
 
-	// first frame is naively populated with redundant tiles,
-	// so fill it unconditionally and start from the second frame
-	int num_tiles = width * height;
-	int tilemap_size = graphics_size / tile_size;
-	tilemap->data = malloc(tilemap_size * 2);
-	for (i = 0; i < num_tiles; i++) {
-		tilemap->data[tilemap->size] = i;
-		tilemap->size++;
+	int num_tiles = num_tiles_per_frame;
+	// Copy the first frame directly
+	for (int i = 0; i < num_tiles_per_frame; i++) {
+		data[i] = i;
 	}
-	for (i = num_tiles; i < tilemap_size; i++) {
-		int preferred = i % num_tiles_per_frame;
-		int index = get_tile_index(graphics + i * tile_size, graphics, i, preferred);
-		if (Options.girafarig && index == 0) {
+	// Skip redundant tiles in the animated frames
+	for (int i = num_tiles_per_frame; i < size; i++) {
+		int index = get_tile_index(&tiles[i * TILE_SIZE], tiles, i, i % num_tiles_per_frame);
+		int tile;
+		if (girafarig && index == 0) {
 			tile = num_tiles;
 		} else if (index == -1) {
 			tile = num_tiles++;
 		} else {
-			tile = tilemap->data[index];
+			tile = data[index];
 		}
-		tilemap->data[tilemap->size] = tile;
-		tilemap->size++;
+		data[i] = tile;
 	}
 
-	int graphic_size = tilemap->size * 16;
-	if (Options.girafarig) {
-		// This is probably not needed, but just in case...
-		graphic_size += 16;
-	}
-
-	graphic->data = malloc(graphic_size);
-	graphic->size = 16 * width * height;
-	memcpy(graphic->data, graphics, graphic->size);
-	for (i = width * height; i < tilemap->size; i++) {
-		tile = get_tile_index(graphics + 16 * i, graphic->data, graphic->size / 16, i % num_tiles_per_frame);
-		if (tile == -1) {
-			memcpy(graphic->data + graphic->size, graphics + 16 * i, 16);
-			graphic->size += 16;
-		}
-	}
-	if (Options.girafarig) {
-		// Add a duplicate of tile 0 to the end.
-		memcpy(graphic->data + graphic->size, graphics, 16);
-		graphic->size += 16;
-	}
-
-	free(graphics);
+	write_u8(filename, data, size);
+	free(data);
 }
 
-int main(int argc, char* argv[]) {
-	char* dimensions_filename;
-	char* graphics_filename;
-	char* outfile = NULL;
-	char* mapfile = NULL;
-	FILE* f;
-	uint8_t bytes[1];
-	int width;
-	int height;
-	struct Graphic graphic = {0};
-	struct Tilemap tilemap = {0};
+int main(int argc, char *argv[]) {
+	struct Options options = {0};
+	parse_args(argc, argv, &options);
 
-	while (1) {
-		struct option long_options[] = {
-			{"girafarig", no_argument, &Options.girafarig, 1},
-			{"tilemap", required_argument, 0, 't'},
-			{"output", required_argument, 0, 'o'},
-			{0}
-		};
-		int long_option_index = 0;
-		int opt = getopt_long(argc, argv, "o:t:", long_options, &long_option_index);
-		if (opt == -1) {
-			break;
-		}
-		switch (opt) {
-		case 0:
-			break;
-		case 'o':
-			outfile = optarg;
-			break;
-		case 't':
-			mapfile = optarg;
-			break;
-		default:
-			usage();
-			break;
-		}
-	}
 	argc -= optind;
 	argv += optind;
-
 	if (argc < 2) {
 		usage();
-	}
-
-	graphics_filename = argv[0];
-	dimensions_filename = argv[1];
-
-	f = fopen_verbose(dimensions_filename, "rb");
-	if (!f) {
 		exit(1);
 	}
-	if (1 != fread(bytes, 1, 1, f)) {
-		fprintf(stderr, "failed to read file %s\n", dimensions_filename);
-		exit(1);
-	}
-	fclose(f);
-	width = bytes[0] & 0xf;
-	height = bytes[0] >> 4;
 
-	create_tilemap(&tilemap, &graphic, graphics_filename, width, height);
+	uint8_t dimensions = read_dimensions(argv[1]);
+	int width = dimensions & 0xF;
+	int height = dimensions >> 4;
 
-	if (outfile) {
-		f = fopen_verbose(outfile, "wb");
-		if (f) {
-			fwrite(graphic.data, 1, graphic.size, f);
-			fclose(f);
-		}
-	}
+	long tiles_size;
+	uint8_t *tiles = read_tiles(argv[0], width, height, &tiles_size);
 
-	if (mapfile) {
-		f = fopen_verbose(mapfile, "wb");
-		if (f) {
-			fwrite(tilemap.data, 1, tilemap.size, f);
-			fclose(f);
-		}
+	if (options.out_filename) {
+		write_graphics(options.out_filename, tiles, tiles_size, width * height, options.girafarig);
 	}
+	if (options.map_filename) {
+		write_tilemap(options.map_filename, tiles, tiles_size, width * height, options.girafarig);
+	}
 
-	free(graphic.data);
-	free(tilemap.data);
-
+	free(tiles);
 	return 0;
 }
--- a/tools/scan_includes.c
+++ b/tools/scan_includes.c
@@ -1,9 +1,31 @@
 #include "common.h"
 
-void usage(void) {
+void usage() {
 	fputs("Usage: scan_includes [-h|--help] [-s|--strict] filename.asm\n", stderr);
 }
 
+void parse_args(int argc, char *argv[], bool *strict) {
+	struct option long_options[] = {
+		{"strict", no_argument, 0, 's'},
+		{"help", no_argument, 0, 'h'},
+		{0}
+	};
+	for (int opt; (opt = getopt_long(argc, argv, "sh", long_options)) != -1;) {
+		switch (opt) {
+		case 's':
+			*strict = true;
+			break;
+		case 'h':
+			usage();
+			exit(0);
+			break;
+		default:
+			usage();
+			exit(1);
+		}
+	}
+}
+
 void scan_file(const char *filename, bool strict) {
 	errno = 0;
 	FILE *f = fopen(filename, "rb");
@@ -64,28 +86,6 @@
 	}
 
 	free(contents);
-}
-
-void parse_args(int argc, char *argv[], bool *strict) {
-	struct option long_options[] = {
-		{"strict", no_argument, 0, 's'},
-		{"help", no_argument, 0, 'h'},
-		{0}
-	};
-	for (int opt; (opt = getopt_long(argc, argv, "sh", long_options)) != -1;) {
-		switch (opt) {
-		case 's':
-			*strict = true;
-			break;
-		case 'h':
-			usage();
-			exit(0);
-			break;
-		default:
-			usage();
-			exit(1);
-		}
-	}
 }
 
 int main(int argc, char *argv[]) {
--- a/tools/stadium.c
+++ b/tools/stadium.c
@@ -1,5 +1,36 @@
 #include "common.h"
 
+enum Base { BASE_NONE, BASE_US, BASE_EU, BASE_DEBUG };
+
+void usage() {
+	fputs("Usage: stadium [-h|--help] [-b|--base us|eu|dbg] pokecrystal.gbc\n", stderr);
+}
+
+void parse_args(int argc, char *argv[], enum Base *base) {
+	struct option long_options[] = {
+		{"base", required_argument, 0, 'b'},
+		{"help", no_argument, 0, 'h'},
+		{0}
+	};
+	for (int opt; (opt = getopt_long(argc, argv, "b:h", long_options)) != -1;) {
+		switch (opt) {
+		case 'b':
+			*base = !strcmp(optarg, "us") ? BASE_US :
+				!strcmp(optarg, "eu") ? BASE_EU :
+				!strcmp(optarg, "dbg") ? BASE_DEBUG :
+				BASE_NONE;
+			break;
+		case 'h':
+			usage();
+			exit(0);
+			break;
+		default:
+			usage();
+			exit(1);
+		}
+	}
+}
+
 // The Game Boy cartridge header stores a global checksum at 0x014E-0x014F
 #define GLOBALOFF 0x014E
 // "base" data; Crystal-only
@@ -17,8 +48,6 @@
 // The CRC initial value for Crystal base data
 #define CRC_INIT_BASE 0xACDE
 
-typedef enum Base { BASE_NONE, BASE_US, BASE_EU, BASE_DEBUG } Base;
-
 // Base data format: "base", 1, version byte, CRC (big-endian),
 // 16 bytes = a 128-bit mask of which banks Stadium can skip comparing
 
@@ -36,41 +65,12 @@
 
 uint8_t n64ps3[N64PS3SIZE] = {'N', '6', '4', 'P', 'S', '3'};
 
-static void usage(void) {
-	fputs("Usage: stadium [-h|--help] [-b|--base us|eu|dbg] romfile\n", stderr);
-}
-
-void parse_args(int argc, char *argv[], Base *b) {
-	struct option long_options[] = {
-		{"base", required_argument, 0, 'b'},
-		{"help", no_argument, 0, 'h'},
-		{0}
-	};
-	for (int opt; (opt = getopt_long(argc, argv, "hb:", long_options)) != -1;) {
-		switch (opt) {
-		case 'h':
-			usage();
-			exit(0);
-			break;
-		case 'b':
-			*b = !strcmp(optarg, "us") ? BASE_US :
-				!strcmp(optarg, "eu") ? BASE_EU :
-				!strcmp(optarg, "dbg") ? BASE_DEBUG :
-				BASE_NONE;
-			break;
-		default:
-			usage();
-			exit(1);
-		}
-	}
-}
-
 #define SET_U16BE(file, off, v) do { \
 	file[(off) + 0] = (uint8_t)(((v) & 0xFF00) >> 8); \
 	file[(off) + 1] = (uint8_t)(((v) & 0x00FF) >> 0); \
 } while (0)
 
-void calculate_checksums(uint8_t *file, int filesize, Base base) {
+void calculate_checksums(uint8_t *file, int filesize, enum Base base) {
 	int NUMCHECKS = filesize / CHECKSIZE;
 	int DATASIZE = HEADERSIZE + NUMCHECKS * 2; // 2 bytes per checksum
 	int ORIGIN = filesize - DATASIZE; // Stadium data goes at the end of the file
@@ -138,7 +138,7 @@
 }
 
 int main(int argc, char *argv[]) {
-	Base base = BASE_NONE;
+	enum Base base = BASE_NONE;
 	parse_args(argc, argv, &base);
 
 	argc -= optind;