diff options
author | yenatch <yenatch@gmail.com> | 2017-07-07 23:23:21 -0400 |
---|---|---|
committer | yenatch <yenatch@gmail.com> | 2017-07-07 23:23:21 -0400 |
commit | 7af1ac9dedd5fcef8eabfe39a10d72f17418a6a4 (patch) | |
tree | 8ec5a0ee353976ff9b3131cdde783e9618c03bf2 | |
parent | ef17a2820d16b9f488875d2d0c07dbc0e1dd0c04 (diff) |
Use scan_includes and gfx tools from pokecrystal
-rwxr-xr-x | Makefile | 42 | ||||
-rw-r--r-- | tools/Makefile | 17 | ||||
-rw-r--r-- | tools/common.h | 37 | ||||
-rw-r--r-- | tools/gfx.c | 291 | ||||
-rw-r--r-- | tools/scan_includes.c | 130 |
5 files changed, 495 insertions, 22 deletions
@@ -1,31 +1,26 @@ -.PHONY: all compare clean tidy +.PHONY: all tools compare clean tidy .SUFFIXES: -.SUFFIXES: .asm .o .gbc .png +.SUFFIXES: .asm .o .gbc .1bpp .2bpp .png .pcm .wav .SECONDEXPANSION: +.PRECIOUS: +.SECONDARY: -# Build Pokemon Pinball. ROM := pokepinball.gbc OBJS := main.o wram.o sram.o -# If your default python is 3, you may want to change this to python27. PYTHON := python PRET := pokemon-reverse-engineering-tools/pokemontools MD5 := md5sum -c --quiet -$(foreach obj, $(OBJS), \ - $(eval $(obj:.o=)_dep := $(shell $(PYTHON) $(PRET)/scan_includes.py $(obj:.o=.asm))) \ -) - -# Link objects together to build a rom. all: $(ROM) compare -# Assemble source files into objects. -# Use rgbasm -h to use halts without nops. -$(OBJS): $$*.asm $$($$*_dep) - @$(PYTHON) $(PRET)/gfx.py 2bpp $(2bppq) - @$(PYTHON) $(PRET)/gfx.py 1bpp $(1bppq) - @$(PYTHON) $(PRET)/pcm.py pcm $(pcmq) +ifeq (,$(filter $(MAKECMDGOALS),tools)) +Makefile: tools +endif + +%.o: dep = $(shell tools/scan_includes $(@D)/$*.asm) +%.o: %.asm $$(dep) rgbasm -h -o $@ $< $(ROM): $(OBJS) contents/contents.link @@ -36,21 +31,24 @@ $(ROM): $(OBJS) contents/contents.link compare: $(ROM) @$(MD5) rom.md5 -# Remove files generated by the build process. +tools: + @$(MAKE) -C tools + tidy: rm -f $(ROM) $(OBJS) $(ROM:.gbc=.sym) $(ROM:.gbc=.map) clean: tidy find . \( -iname '*.1bpp' -o -iname '*.2bpp' -o -iname '*.pcm' \) -exec rm {} + +%.interleave.2bpp: %.interleave.png + rgbgfx -o $@ $< + tools/gfx --interleave --png $< -o $@ $@ + %.2bpp: %.png - $(eval 2bppq += $<) - @rm -f $@ + rgbgfx -o $@ $< %.1bpp: %.png - $(eval 1bppq += $<) - @rm -f $@ + rgbgfx -d1 -o $@ $< %.pcm: %.wav - $(eval pcmq += $<) - @rm -f $@ + $(PYTHON) $(PRET)/pcm.py pcm $< diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..c3c3d35 --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,17 @@ +.PHONY: all clean + +CC := gcc +CFLAGS := -std=c99 + +tools := \ + gfx \ + scan_includes + +all: $(tools) + @: + +clean: + rm -f $(tools) + +%: %.c + $(CC) $(CFLAGS) -o $@ $< diff --git a/tools/common.h b/tools/common.h new file mode 100644 index 0000000..bc877cc --- /dev/null +++ b/tools/common.h @@ -0,0 +1,37 @@ +#ifndef GUARD_COMMON_H +#define GUARD_COMMON_H + +int __getopt_long_i__; +#define getopt_long(c, v, s, l) getopt_long(c, v, s, l, &__getopt_long_i__) + +FILE *fopen_verbose(char *filename, char *mode) { + FILE *f = fopen(filename, mode); + if (!f) { + fprintf(stderr, "Could not open file: \"%s\"\n", filename); + } + return f; +} + +uint8_t *read_u8(char *filename, int *size) { + FILE *f = fopen_verbose(filename, "rb"); + if (!f) { + exit(1); + } + fseek(f, 0, SEEK_END); + *size = ftell(f); + rewind(f); + uint8_t *data = malloc(*size); + fread(data, 1, *size, f); + fclose(f); + return data; +} + +void write_u8(char *filename, uint8_t *data, int size) { + FILE *f = fopen_verbose(filename, "wb"); + if (f) { + fwrite(data, 1, size, f); + fclose(f); + } +} + +#endif // GUARD_COMMON_H diff --git a/tools/gfx.c b/tools/gfx.c new file mode 100644 index 0000000..3e5624e --- /dev/null +++ b/tools/gfx.c @@ -0,0 +1,291 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <getopt.h> +#include <string.h> +#include <stdint.h> + +#include "common.h" + +static void usage(void) { + fprintf(stderr, "Usage: gfx [--trim-whitespace] [--remove-whitespace] [--interleave] [--remove-duplicates [--keep-whitespace]] [--remove-xflip] [--remove-yflip] [--png filename] [-d depth] [-h] [-o outfile] infile\n"); +} + +static void error(char *message) { + fputs(message, stderr); + fputs("\n", stderr); +} + +struct Options { + int trim_whitespace; + int remove_whitespace; + int help; + char *outfile; + int depth; + int interleave; + int remove_duplicates; + int keep_whitespace; + int remove_xflip; + int remove_yflip; + char *png_file; +}; + +struct Options Options = { + .depth = 2, +}; + +void get_args(int argc, char *argv[]) { + struct option long_options[] = { + {"remove-whitespace", no_argument, &Options.remove_whitespace, 1}, + {"trim-whitespace", no_argument, &Options.trim_whitespace, 1}, + {"interleave", no_argument, &Options.interleave, 1}, + {"remove-duplicates", no_argument, &Options.remove_duplicates, 1}, + {"keep-whitespace", no_argument, &Options.keep_whitespace, 1}, + {"remove-xflip", no_argument, &Options.remove_xflip, 1}, + {"remove-yflip", no_argument, &Options.remove_yflip, 1}, + {"png", required_argument, 0, 'p'}, + {"depth", required_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {0} + }; + for (int opt = 0; opt != -1;) { + switch (opt = getopt_long(argc, argv, "ho:d:p:", long_options)) { + case 'h': + Options.help = true; + break; + case 'o': + Options.outfile = optarg; + break; + case 'd': + Options.depth = strtoul(optarg, NULL, 0); + break; + case 'p': + Options.png_file = optarg; + break; + case 0: + case -1: + break; + default: + usage(); + exit(1); + break; + } + } +} + +struct Graphic { + int size; + uint8_t *data; +}; + +bool is_whitespace(uint8_t *tile, int tile_size) { + uint8_t WHITESPACE = 0; + for (int i = 0; i < tile_size; i++) { + if (tile[i] != WHITESPACE) { + return false; + } + } + return true; +} + +void trim_whitespace(struct Graphic *graphic) { + int tile_size = Options.depth * 8; + for (int i = graphic->size - tile_size; i > 0; i -= tile_size) { + if (is_whitespace(&graphic->data[i], tile_size)) { + graphic->size = i; + } else { + break; + } + } +} + +void remove_whitespace(struct Graphic *graphic) { + int tile_size = Options.depth * 8; + if (Options.interleave) tile_size *= 2; + int i = 0; + for (int j = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) { + while (is_whitespace(&graphic->data[j], tile_size)) { + j += tile_size; + } + if (j >= graphic->size) { + break; + } + if (j > i) { + memcpy(&graphic->data[i], &graphic->data[j], tile_size); + } + } + graphic->size = i; +} + +bool tile_exists(uint8_t *tile, uint8_t *tiles, int tile_size, int num_tiles) { + for (int i = 0; i < num_tiles; i++) { + bool match = true; + for (int j = 0; j < tile_size; j++) { + if (tile[j] != tiles[i * tile_size + j]) { + match = false; + } + } + if (match) { + return true; + } + } + return false; +} + +void remove_duplicates(struct Graphic *graphic) { + int tile_size = Options.depth * 8; + if (Options.interleave) tile_size *= 2; + int num_tiles = 0; + for (int i = 0, j = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) { + while (tile_exists(&graphic->data[j], graphic->data, tile_size, num_tiles)) { + if (Options.keep_whitespace && is_whitespace(&graphic->data[j], tile_size)) { + break; + } + j += tile_size; + } + if (j >= graphic->size) { + break; + } + if (j > i) { + memcpy(&graphic->data[i], &graphic->data[j], tile_size); + } + num_tiles++; + } + graphic->size = num_tiles * tile_size; +} + +bool flip_exists(uint8_t *tile, uint8_t *tiles, int tile_size, int num_tiles, bool xflip, bool yflip) { + uint8_t *flip = calloc(tile_size, 1); + int half_size = tile_size / 2; + for (int i = 0; i < tile_size; i++) { + int byte = i; + if (yflip) { + byte = tile_size - 1 - (i ^ 1); + if (Options.interleave && i < half_size) { + byte = half_size - 1 - (i ^ 1); + } + } + if (xflip) { + for (int bit = 0; bit < 8; bit++) { + flip[byte] |= ((tile[i] >> bit) & 1) << (7 - bit); + } + } else { + flip[byte] = tile[i]; + } + } + if (tile_exists(flip, tiles, tile_size, num_tiles)) { + return true; + } + return false; +} + +void remove_flip(struct Graphic *graphic, bool xflip, bool yflip) { + int tile_size = Options.depth * 8; + if (Options.interleave) tile_size *= 2; + int num_tiles = 0; + for (int i = 0, j = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) { + while (flip_exists(&graphic->data[j], graphic->data, tile_size, num_tiles, xflip, yflip)) { + if (Options.keep_whitespace && is_whitespace(&graphic->data[j], tile_size)) { + break; + } + j += tile_size; + } + if (j >= graphic->size) { + break; + } + if (j > i) { + memcpy(&graphic->data[i], &graphic->data[j], tile_size); + } + num_tiles++; + } + graphic->size = num_tiles * tile_size; +} + +void interleave(struct Graphic *graphic, int width) { + int tile_size = Options.depth * 8; + int width_tiles = width / 8; + int num_tiles = graphic->size / tile_size; + uint8_t *interleaved = malloc(graphic->size); + for (int i = 0; i < num_tiles; i++) { + int tile = i * 2; + int row = i / width_tiles; + tile -= width_tiles * row; + if (row % 2) { + tile -= width_tiles; + tile += 1; + } + memcpy(&interleaved[tile * tile_size], &graphic->data[i * tile_size], tile_size); + } + graphic->size = num_tiles * tile_size; + memcpy(graphic->data, interleaved, graphic->size); + free(interleaved); +} + +int png_get_width(char *filename) { + FILE *f = fopen_verbose(filename, "rb"); + if (!f) { + exit(1); + } + + const int OFFSET_WIDTH = 16; + uint8_t bytes[4]; + fseek(f, OFFSET_WIDTH, SEEK_SET); + fread(bytes, 1, 4, f); + fclose(f); + + int width = 0; + for (int i = 0; i < 4; i++) { + width |= bytes[i] << (8 * (3 - i)); + } + return width; +} + + +int main(int argc, char *argv[]) { + get_args(argc, argv); + argc -= optind; + argv += optind; + if (Options.help) { + usage(); + return 0; + } + if (argc < 1) { + usage(); + exit(1); + } + char *infile = argv[0]; + struct Graphic graphic; + graphic.data = read_u8(infile, &graphic.size); + if (Options.trim_whitespace) { + trim_whitespace(&graphic); + } + if (Options.interleave) { + if (!Options.png_file) { + error("interleave: need --png to infer dimensions"); + usage(); + exit(1); + } + int width = png_get_width(Options.png_file); + interleave(&graphic, width); + } + if (Options.remove_duplicates) { + remove_duplicates(&graphic); + } + if (Options.remove_xflip) { + remove_flip(&graphic, true, false); + } + if (Options.remove_yflip) { + remove_flip(&graphic, false, true); + } + if (Options.remove_xflip && Options.remove_yflip) { + remove_flip(&graphic, true, true); + } + if (Options.remove_whitespace) { + remove_whitespace(&graphic); + } + if (Options.outfile) { + write_u8(Options.outfile, graphic.data, graphic.size); + } + free(graphic.data); + return 0; +} diff --git a/tools/scan_includes.c b/tools/scan_includes.c new file mode 100644 index 0000000..b6fcca0 --- /dev/null +++ b/tools/scan_includes.c @@ -0,0 +1,130 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <getopt.h> + +void usage(void) { + printf("Usage: scan_includes [-h] [-s] filename\n" + "-h, --help\n" + " Print usage and exit\n" + "-s, --strict\n" + " Fail if a file cannot be read\n"); +} + +struct Options { + bool help; + bool strict; +}; + +struct Options Options = {0}; + + +void scan_file(char* filename) { + FILE* f; + long size; + char* orig; + char* buffer; + char* include; + int length; + + f = fopen(filename, "r"); + if (!f) { + if (Options.strict) { + fprintf(stderr, "Could not open file: '%s'\n", filename); + exit(1); + } else { + return; + } + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + rewind(f); + + buffer = malloc(size + 1); + orig = buffer; + fread(buffer, 1, size, f); + buffer[size] = '\0'; + fclose(f); + + for (; buffer && (buffer - orig < size); buffer++) { + if (buffer[0] == ';') { + buffer = strchr(buffer, '\n'); + if (!buffer) { + fprintf(stderr, "%s: no newline at end of file\n", filename); + break; + } + continue; + } + bool is_include = false; + bool is_incbin = false; + if ((strncmp(buffer, "INCBIN", 6) == 0) || (strncmp(buffer, "incbin", 6) == 0)) { + is_incbin = true; + } else if ((strncmp(buffer, "INCLUDE", 7) == 0) || (strncmp(buffer, "include", 7) == 0)) { + is_include = true; + } + if (is_incbin || is_include) { + buffer = strchr(buffer, '"') + 1; + if (!buffer) { + break; + } + length = strcspn(buffer, "\""); + include = malloc(length + 1); + strncpy(include, buffer, length); + include[length] = '\0'; + printf("%s ", include); + if (is_include) { + scan_file(include); + } + free(include); + } + } + + free(orig); +} + +void get_args(int argc, char *argv[]) { + while (1) { + struct option long_options[] = { + {"strict", no_argument, 0, 's'}, + {"help", no_argument, 0, 'h'}, + {0} + }; + int i = 0; + int opt = getopt_long(argc, argv, "sh", long_options, &i); + + if (opt == -1) { + break; + } + + switch (opt) { + case 's': + Options.strict = true; + break; + case 'h': + Options.help = true; + break; + default: + usage(); + exit(1); + break; + } + } +} + +int main(int argc, char* argv[]) { + get_args(argc, argv); + argc -= optind; + argv += optind; + if (Options.help) { + usage(); + return 0; + } + if (argc < 1) { + usage(); + exit(1); + } + scan_file(argv[0]); + return 0; +} |