First Commit

Upload literally everything from the pokecrystal16 expand-move-ID branch
This commit is contained in:
Zeta_Null 2023-09-10 12:35:35 -04:00
commit 2f8a41f833
4618 changed files with 480386 additions and 0 deletions

10
tools/.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
bpp2png
gfx
lzcomp
make_patch
palette
png_dimensions
pokemon_animation
pokemon_animation_graphics
scan_includes
stadium

38
tools/Makefile Normal file
View file

@ -0,0 +1,38 @@
.PHONY: all clean
CC := gcc
CFLAGS := -O3 -flto -std=c11 -Wall -Wextra -pedantic
tools := \
bpp2png \
lzcomp \
gfx \
make_patch \
png_dimensions \
pokemon_animation \
pokemon_animation_graphics \
scan_includes \
stadium
all: $(tools)
@:
clean:
$(RM) $(tools)
gfx: common.h
png_dimensions: common.h
pokemon_animation: common.h
pokemon_animation_graphics: common.h
scan_includes: common.h
stadium: common.h
bpp2png: bpp2png.c lodepng/lodepng.c common.h lodepng/lodepng.h
$(CC) $(CFLAGS) -o $@ bpp2png.c lodepng/lodepng.c
lzcomp: CFLAGS += -Wno-strict-overflow -Wno-sign-compare
lzcomp: $(wildcard lz/*.c) $(wildcard lz/*.h)
$(CC) $(CFLAGS) -o $@ lz/*.c
%: %.c
$(CC) $(CFLAGS) -o $@ $<

240
tools/bpp2png.c Normal file
View file

@ -0,0 +1,240 @@
#define PROGRAM_NAME "bpp2png"
#define USAGE_OPTS "[-h|--help] [-w width] [-d depth] [-p in.gbcpal] [-t] in.2bpp|in.1bpp out.png"
#include "common.h"
#include "lodepng/lodepng.h"
struct Options {
unsigned int width;
unsigned int depth;
const char *palette;
bool transpose;
};
typedef uint8_t Palette[4][3];
void parse_args(int argc, char *argv[], struct Options *options) {
struct option long_options[] = {
{"width", required_argument, 0, 'w'},
{"depth", required_argument, 0, 'd'},
{"palette", required_argument, 0, 'p'},
{"transpose", no_argument, 0, 't'},
{"help", no_argument, 0, 'h'},
{0}
};
for (int opt; (opt = getopt_long(argc, argv, "w:d:p:th", long_options)) != -1;) {
switch (opt) {
case 'w':
options->width = (unsigned int)strtoul(optarg, NULL, 0);
if (options->width % 8) {
error_exit("Width not divisible by 8 px: %u\n", options->width);
}
break;
case 'd':
options->depth = (unsigned int)strtoul(optarg, NULL, 0);
if (options->depth != 1 && options->depth != 2) {
error_exit("Depth is not 1 or 2: %u\n", options->depth);
}
break;
case 'p':
options->palette = optarg;
break;
case 't':
options->transpose = true;
break;
case 'h':
usage_exit(0);
break;
default:
usage_exit(1);
}
}
}
bool is_1bpp(const char *filename) {
const char *end = strrchr(filename, '.');
return end && strlen(end) == 5 && end[1] == '1' && (end[2] == 'b' || end[2] == 'B')
&& (end[3] == 'p' || end[3] == 'P') && (end[4] == 'p' || end[4] == 'P');
}
uint8_t *extend_1bpp_to_2bpp(uint8_t *bpp_data, long *size) {
*size *= 2;
bpp_data = xrealloc(bpp_data, *size);
for (long i = *size - 2; i >= 0; i -= 2) {
bpp_data[i] = bpp_data[i + 1] = bpp_data[i >> 1];
}
return bpp_data;
}
void mingle_2bpp_planes(uint8_t *bpp_data, long size) {
for (long i = 0; i < size; i += 2) {
// Interleave aka "mingle" bits
// <https://graphics.stanford.edu/~seander/bithacks.html#Interleave64bitOps>
#define EXPAND_PLANE(b) (((((b) * 0x0101010101010101ULL & 0x8040201008040201ULL) * 0x0102040810204081ULL) >> 48) & 0xAAAA)
uint16_t r = (EXPAND_PLANE(bpp_data[i]) >> 1) | EXPAND_PLANE(bpp_data[i + 1]);
bpp_data[i] = r >> 8;
bpp_data[i + 1] = r & 0xff;
}
}
unsigned int calculate_size(long bytes, unsigned int *width) {
long pixels = bytes * 4;
if (pixels % (8 * 8)) {
return 0;
}
if (!*width) {
// If no width is specified, try to guess an appropriate one
#define GUESS_SIZE(w, h) pixels == (w) * (h) * 8 * 8 ? (w) * 8
*width = GUESS_SIZE(5, 5) // mon pic
: GUESS_SIZE(6, 6) // mon front/back pic
: GUESS_SIZE(7, 7) // mon/trainer pic
: GUESS_SIZE(2, 4) // mon icon
: GUESS_SIZE(2, 12) // walking sprite
: GUESS_SIZE(2, 6) // standing sprite
: GUESS_SIZE(2, 2) // still sprite
: GUESS_SIZE(4, 4) // big sprite
: pixels > 16 * 8 * 8 ? 16 * 8 // maximum width
: (unsigned int)(pixels / 8);
}
unsigned int height = (unsigned int)((pixels + *width * 8 - 1) / (*width * 8) * 8);
if (*width == 0 || height == 0) {
error_exit("Not divisible into 8x8-px tiles: %ux%u\n", *width, height);
}
return height;
}
uint8_t *pad_to_rectangle(uint8_t *bpp_data, long filesize, unsigned int width, unsigned int height) {
unsigned int size = width * height / 4;
if (size > filesize) {
bpp_data = xrealloc(bpp_data, size);
// Fill padding with white
memset(bpp_data + filesize, 0, size - filesize);
}
return bpp_data;
}
uint8_t *transpose_tiles(uint8_t *bpp_data, unsigned int width, unsigned int height) {
unsigned int size = width * height / 4;
uint8_t *transposed = xmalloc(size);
unsigned int cols = width / 8;
for (unsigned int i = 0; i < size; i++) {
unsigned int j = (i / 0x10) * cols * 0x10;
j = (j % size) + 0x10 * (j / size) + (i % 0x10);
transposed[j] = bpp_data[i];
}
free(bpp_data);
return transposed;
}
uint8_t *rearrange_tiles_to_scanlines(uint8_t *bpp_data, unsigned int width, unsigned int height) {
unsigned int size = width * height / 4;
uint8_t *rearranged = xmalloc(size);
unsigned int cols = width / 8;
unsigned int j = 0;
for (unsigned int line = 0; line < height; line++) {
unsigned int row = line / 8;
for (unsigned int col = 0; col < cols; col++) {
unsigned int i = ((row * cols + col) * 8 + line % 8) * 2;
rearranged[j] = bpp_data[i];
rearranged[j + 1] = bpp_data[i + 1];
j += 2;
}
}
free(bpp_data);
return rearranged;
}
void read_gbcpal(Palette palette, const char *filename) {
long filesize;
uint8_t *pal_data = read_u8(filename, &filesize);
if (filesize != 4 * 2) {
error_exit("Invalid .gbcpal file: \"%s\"\n", filename);
}
for (int i = 0; i < 4; i++) {
uint8_t b1 = pal_data[i * 2], b2 = pal_data[i * 2 + 1];
#define RGB5_TO_RGB8(x) (uint8_t)(((x) << 3) | ((x) >> 2))
palette[i][0] = RGB5_TO_RGB8(b1 & 0x1f); // red
palette[i][1] = RGB5_TO_RGB8(((b1 & 0xe0) >> 5) | ((b2 & 0x03) << 3)); // green
palette[i][2] = RGB5_TO_RGB8((b2 & 0x7c) >> 2); // blue
}
free(pal_data);
}
unsigned int write_png(const char *filename, uint8_t *bpp_data, unsigned int width, unsigned int height, Palette palette) {
LodePNGState state;
lodepng_state_init(&state);
state.info_raw.bitdepth = state.info_png.color.bitdepth = 2;
if (palette) {
state.info_raw.colortype = state.info_png.color.colortype = LCT_PALETTE;
for (int i = 0; i < 4; i++) {
uint8_t *color = palette[i];
lodepng_palette_add(&state.info_raw, color[0], color[1], color[2], 0xff);
lodepng_palette_add(&state.info_png.color, color[0], color[1], color[2], 0xff);
}
} else {
state.info_raw.colortype = state.info_png.color.colortype = LCT_GREY;
// 2-bit PNG white/light/dark/gray indexes are the inverse of 2bpp
for (unsigned int i = 0; i < width * height / 4; i++) {
bpp_data[i] = ~bpp_data[i];
}
}
unsigned char *buffer;
size_t buffer_size;
lodepng_encode(&buffer, &buffer_size, bpp_data, width, height, &state);
unsigned int error = state.error;
lodepng_state_cleanup(&state);
if (!error) {
error = lodepng_save_file(buffer, buffer_size, filename);
}
free(buffer);
return error;
}
int main(int argc, char *argv[]) {
struct Options options = {0};
parse_args(argc, argv, &options);
argc -= optind;
argv += optind;
if (argc != 2) {
usage_exit(1);
}
Palette palette = {0};
if (options.palette) {
read_gbcpal(palette, options.palette);
}
const char *bpp_filename = argv[0];
long filesize;
uint8_t *bpp_data = read_u8(bpp_filename, &filesize);
if (!options.depth) {
options.depth = is_1bpp(bpp_filename) ? 1 : 2;
}
if (!filesize || filesize % (8 * options.depth)) {
error_exit("Invalid .%dbpp file: \"%s\"\n", options.depth, bpp_filename);
}
if (options.depth == 1) {
bpp_data = extend_1bpp_to_2bpp(bpp_data, &filesize);
}
mingle_2bpp_planes(bpp_data, filesize);
unsigned int height = calculate_size(filesize, &options.width);
bpp_data = pad_to_rectangle(bpp_data, filesize, options.width, height);
if (options.transpose) {
bpp_data = transpose_tiles(bpp_data, options.width, height);
}
bpp_data = rearrange_tiles_to_scanlines(bpp_data, options.width, height);
const char *png_filename = argv[1];
unsigned int error = write_png(png_filename, bpp_data, options.width, height, options.palette ? palette : NULL);
if (error) {
error_exit("Could not write to file \"%s\": %s\n", png_filename, lodepng_error_text(error));
}
free(bpp_data);
return 0;
}

151
tools/common.h Normal file
View file

@ -0,0 +1,151 @@
#ifndef GUARD_COMMON_H
#define GUARD_COMMON_H
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdnoreturn.h>
#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#ifndef PROGRAM_NAME
#error Define PROGRAM_NAME before including common.h!
#endif
#ifndef USAGE_OPTS
#error Define USAGE_OPTS before including common.h!
#endif
#define COUNTOF(...) (sizeof(__VA_ARGS__) / sizeof(*(__VA_ARGS__)))
#define error_exit(...) exit((fprintf(stderr, PROGRAM_NAME ": " __VA_ARGS__), 1))
noreturn void usage_exit(int status) {
fprintf(stderr, "Usage: " PROGRAM_NAME " " USAGE_OPTS "\n");
exit(status);
}
int getopt_long_index;
#define getopt_long(argc, argv, optstring, longopts) getopt_long(argc, argv, optstring, longopts, &getopt_long_index)
void *xmalloc(size_t size) {
errno = 0;
void *m = malloc(size);
if (!m) {
error_exit("Could not allocate %zu bytes: %s\n", size, strerror(errno));
}
return m;
}
void *xcalloc(size_t size) {
errno = 0;
void *m = calloc(size, 1);
if (!m) {
error_exit("Could not allocate %zu bytes: %s\n", size, strerror(errno));
}
return m;
}
void *xrealloc(void *m, size_t size) {
errno = 0;
m = realloc(m, size);
if (!m) {
error_exit("Could not allocate %zu bytes: %s\n", size, strerror(errno));
}
return m;
}
FILE *xfopen(const char *filename, char rw) {
char mode[3] = {rw, 'b', '\0'};
errno = 0;
FILE *f = fopen(filename, mode);
if (!f) {
error_exit("Could not open file \"%s\": %s\n", filename, strerror(errno));
}
return f;
}
void xfread(uint8_t *data, size_t size, const char *filename, FILE *f) {
errno = 0;
if (fread(data, 1, size, f) != size) {
fclose(f);
error_exit("Could not read from file \"%s\": %s\n", filename, strerror(errno));
}
}
void xfwrite(const uint8_t *data, size_t size, const char *filename, FILE *f) {
errno = 0;
if (fwrite(data, 1, size, f) != size) {
fclose(f);
error_exit("Could not write to file \"%s\": %s\n", filename, strerror(errno));
}
}
long xfsize(const char *filename, FILE *f) {
long size = -1;
errno = 0;
if (!fseek(f, 0, SEEK_END)) {
size = ftell(f);
if (size != -1) {
rewind(f);
}
}
if (size == -1) {
error_exit("Could not measure file \"%s\": %s\n", filename, strerror(errno));
}
return size;
}
uint8_t *read_u8(const char *filename, long *size) {
FILE *f = xfopen(filename, 'r');
*size = xfsize(filename, f);
uint8_t *data = xmalloc(*size);
xfread(data, *size, filename, f);
fclose(f);
return data;
}
void write_u8(const char *filename, uint8_t *data, size_t size) {
FILE *f = xfopen(filename, 'w');
xfwrite(data, size, filename, f);
fclose(f);
}
uint32_t read_png_width(const char *filename) {
FILE *f = xfopen(filename, 'r');
uint8_t header[16] = {0};
xfread(header, sizeof(header), filename, f);
static uint8_t expected_header[16] = {
0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n', // signature
0, 0, 0, 13, // IHDR chunk length
'I', 'H', 'D', 'R', // IHDR chunk type
};
if (memcmp(header, expected_header, sizeof(header))) {
fclose(f);
error_exit("Not a valid PNG file: \"%s\"\n", filename);
}
uint8_t bytes[4] = {0};
xfread(bytes, sizeof(bytes), filename, f);
fclose(f);
return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
}
void read_dimensions(const char *filename, int *width) {
long filesize;
uint8_t *bytes = read_u8(filename, &filesize);
if (filesize != 1) {
error_exit("%s: invalid dimensions file\n", filename);
}
uint8_t dimensions = bytes[0];
free(bytes);
*width = dimensions & 0xF;
int height = dimensions >> 4;
if (*width != height || (*width != 5 && *width != 6 && *width != 7)) {
error_exit("%s: invalid dimensions: %dx%d tiles\n", filename, *width, height);
}
}
#endif // GUARD_COMMON_H

56
tools/consts.py Normal file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Usage: python consts.py constants/some_constants.asm
View numeric values of `const`ants.
"""
import sys
import re
const_value = 0
const_inc = 1
def asm_int(s):
base = {'$': 16, '&': 8, '%': 2}.get(s[0], 10)
return int(s if base == 10 else s[1:], base)
def print_const(s, v):
print(f'{s} == {v} == ${v:x}')
def parse_for_constants(line):
global const_value, const_inc
if not (m := re.match(r'^\s+([A-Za-z_][A-Za-z0-9_@#]*)(?:\s+([^;\\n]+))?', line)):
return
macro, rest = m.groups()
args = [arg.strip() for arg in rest.split(',')] if rest else []
if args and not args[-1]:
args = args[:-1]
if macro == 'const_def':
const_value = asm_int(args[0]) if len(args) >= 1 else 0
const_inc = asm_int(args[1]) if len(args) >= 2 else 1
elif macro == 'const':
print_const(args[0], const_value)
const_value += const_inc
elif macro == 'shift_const':
print_const(args[0], 1 << const_value)
print_const(args[0] + '_F', const_value)
const_value += const_inc
elif macro == 'const_skip':
const_value += const_inc * (asm_int(args[0]) if len(args) >= 1 else 1)
elif macro == 'const_next':
const_value = asm_int(args[0])
def main():
if len(sys.argv) < 2:
print(f'Usage: {sys.argv[0]} constants/some_constants.asm', file=sys.stderr)
sys.exit(1)
for filename in sys.argv[1:]:
with open(filename, 'r', encoding='utf-8') as file:
for line in file:
parse_for_constants(line)
if __name__ == '__main__':
main()

78
tools/free_space.awk Normal file
View file

@ -0,0 +1,78 @@
#!/usr/bin/gawk -f
# Usage: tools/free_space.awk [BANK=<bank_spec>] pokecrystal.map
# The BANK argument allows printing free space in one, all, or none of the ROM's banks.
# Valid arguments are numbers (in decimal "42" or hexadecimal "0x2a"), "all" or "none".
# If not specified, defaults to "none".
# The `BANK` argument MUST be before the map file name, otherwise it has no effect!
# Yes: tools/free_space.awk BANK=all pokecrystal.map
# No: tools/free_space.awk pokecrystal.map BANK=42
# Copyright (c) 2020, Eldred Habert.
# SPDX-License-Identifier: MIT
BEGIN {
nb_banks = 0
free = 0
rom_bank = 0 # Safety net for malformed files
# Default settings
# Variables assigned via the command-line (except through `-v`) are *after* `BEGIN`
BANK="none"
}
# Only accept ROM banks, ignore everything else
toupper($0) ~ /^[ \t]*ROM[0X][ \t]+BANK[ \t]+#/ {
nb_banks++
rom_bank = 1
split($0, fields, /[ \t]/)
bank_num = strtonum(substr(fields[3], 2))
}
function register_bank(amount) {
free += amount
rom_bank = 0 # Reject upcoming banks by default
if (BANK ~ /all/ || BANK == bank_num) {
printf "Bank %3d: %5d/16384 (%.2f%%)\n", bank_num, amount, amount * 100 / 16384
}
}
function register_bank_str(str) {
if (str ~ /\$[0-9A-F]+/) {
register_bank(strtonum("0x" substr(str, 2)))
} else {
printf "Malformed number? \"%s\" does not start with '$'\n", str
}
}
rom_bank && toupper($0) ~ /^[ \t]*EMPTY$/ {
# Empty bank
register_bank(16384)
}
rom_bank && toupper($0) ~ /^[ \t]*SLACK:[ \t]/ {
# Old (rgbds <=0.6.0) end-of-bank free space
register_bank_str($2)
}
rom_bank && toupper($0) ~ /^[ \t]*TOTAL EMPTY:[ \t]/ {
# New (rgbds >=0.6.1) total free space
register_bank_str($3)
}
END {
# Determine number of banks, by rounding to upper power of 2
total_banks = 2 # Smallest size is 2 banks
while (total_banks < nb_banks) {
total_banks *= 2
}
# RGBLINK omits "trailing" ROM banks, so fake them
bank_num = nb_banks
while (bank_num < total_banks) {
register_bank(16384)
bank_num++
}
total = total_banks * 16384
printf "Free space: %5d/%5d (%.2f%%)\n", free, total, free * 100 / total
}

300
tools/gfx.c Normal file
View file

@ -0,0 +1,300 @@
#define PROGRAM_NAME "gfx"
#define USAGE_OPTS "[-h|--help] [--trim-whitespace] [--remove-whitespace] [--interleave] [--remove-duplicates [--keep-whitespace]] [--remove-xflip] [--remove-yflip] [--preserve indexes] [-d|--depth depth] [-p|--png filename.png] [-o|--out outfile] infile"
#include "common.h"
struct Options {
bool trim_whitespace;
bool remove_whitespace;
bool interleave;
bool remove_duplicates;
bool keep_whitespace;
bool remove_xflip;
bool remove_yflip;
int *preserved;
int num_preserved;
int depth;
char *png_file;
char *outfile;
};
struct Options options = {.depth = 2};
void parse_args(int argc, char *argv[]) {
struct option long_options[] = {
{"remove-whitespace", no_argument, 0, 'R'},
{"trim-whitespace", no_argument, 0, 'T'},
{"interleave", no_argument, 0, 'I'},
{"remove-duplicates", no_argument, 0, 'D'},
{"keep-whitespace", no_argument, 0, 'W'},
{"remove-xflip", no_argument, 0, 'X'},
{"remove-yflip", no_argument, 0, 'Y'},
{"preserve", required_argument, 0, 'r'},
{"png", required_argument, 0, 'p'},
{"depth", required_argument, 0, 'd'},
{"out", required_argument, 0, 'o'},
{"help", no_argument, 0, 'h'},
{0}
};
for (int opt; (opt = getopt_long(argc, argv, "d:o:p:h", long_options)) != -1;) {
switch (opt) {
case 'R':
options.remove_whitespace = true;
break;
case 'T':
options.trim_whitespace = true;
break;
case 'I':
options.interleave = true;
break;
case 'D':
options.remove_duplicates = true;
break;
case 'W':
options.keep_whitespace = true;
break;
case 'X':
options.remove_xflip = true;
break;
case 'Y':
options.remove_yflip = true;
break;
case 'r':
for (char *token = strtok(optarg, ","); token; token = strtok(NULL, ",")) {
options.preserved = xrealloc(options.preserved, ++options.num_preserved * sizeof(*options.preserved));
options.preserved[options.num_preserved-1] = strtoul(token, NULL, 0);
}
break;
case 'd':
options.depth = strtoul(optarg, NULL, 0);
break;
case 'p':
options.png_file = optarg;
break;
case 'o':
options.outfile = optarg;
break;
case 'h':
usage_exit(0);
break;
default:
usage_exit(1);
}
}
}
struct Graphic {
uint8_t *data;
long size;
};
bool is_preserved(int index) {
for (int i = 0; i < options.num_preserved; i++) {
if (options.preserved[i] == index) {
return true;
}
}
return false;
}
void shift_preserved(int removed_index) {
for (int i = 0; i < options.num_preserved; i++) {
if (options.preserved[i] >= removed_index) {
options.preserved[i]--;
}
}
}
bool is_whitespace(const uint8_t *tile, int tile_size) {
for (int i = 0; i < tile_size; i++) {
if (tile[i] != 0) {
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) && !is_preserved(i / tile_size)) {
graphic->size = i;
} else {
break;
}
}
}
int get_tile_size(void) {
return options.depth * (options.interleave ? 16 : 8);
}
void remove_whitespace(struct Graphic *graphic) {
int tile_size = get_tile_size();
graphic->size &= ~(tile_size - 1);
int i = 0;
for (int j = 0, d = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) {
for (; j < graphic->size && is_whitespace(&graphic->data[j], tile_size) && !is_preserved(j / tile_size - d); j += tile_size, d++) {
shift_preserved(j / tile_size - d);
}
if (j >= graphic->size) {
break;
} else if (j > i) {
memcpy(&graphic->data[i], &graphic->data[j], tile_size);
}
}
graphic->size = i;
}
bool tile_exists(const uint8_t *tile, const 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;
break;
}
}
if (match) {
return true;
}
}
return false;
}
void remove_duplicates(struct Graphic *graphic) {
int tile_size = get_tile_size();
graphic->size &= ~(tile_size - 1);
int num_tiles = 0;
for (int i = 0, j = 0, d = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) {
for (; j < graphic->size && tile_exists(&graphic->data[j], graphic->data, tile_size, num_tiles); j += tile_size, d++) {
if ((options.keep_whitespace && is_whitespace(&graphic->data[j], tile_size)) || is_preserved(j / tile_size - d)) {
break;
}
shift_preserved(j / tile_size - d);
}
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;
}
// for (int i = 0; i < 256; i++)
// for (int bit = 0; bit < 8; bit++) {
// flipped[i] |= ((i >> bit) & 1) << (7 - bit);
const uint8_t flipped[256] = {
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
};
bool flip_exists(const uint8_t *tile, const uint8_t *tiles, int tile_size, int num_tiles, bool xflip, bool yflip) {
uint8_t flip[tile_size]; // VLA
memset(flip, 0, tile_size);
int half_size = tile_size / 2;
for (int i = 0; i < tile_size; i++) {
int j = yflip ? (options.interleave && i < half_size ? half_size : tile_size) - 1 - (i ^ 1) : i;
flip[j] = xflip ? flipped[tile[i]] : tile[i];
}
return tile_exists(flip, tiles, tile_size, num_tiles);
}
void remove_flip(struct Graphic *graphic, bool xflip, bool yflip) {
int tile_size = get_tile_size();
graphic->size &= ~(tile_size - 1);
int num_tiles = 0;
for (int i = 0, j = 0, d = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) {
for (; j < graphic->size && flip_exists(&graphic->data[j], graphic->data, tile_size, num_tiles, xflip, yflip); j += tile_size, d++) {
if ((options.keep_whitespace && is_whitespace(&graphic->data[j], tile_size)) || is_preserved(j / tile_size - d)) {
break;
}
shift_preserved(j / tile_size - d);
}
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 = xmalloc(graphic->size);
for (int i = 0; i < num_tiles; i++) {
int row = i / width_tiles;
int tile = i * 2 - (row % 2 ? width_tiles * (row + 1) - 1 : width_tiles * row);
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 main(int argc, char *argv[]) {
parse_args(argc, argv);
argc -= optind;
argv += optind;
if (argc < 1) {
usage_exit(1);
}
struct Graphic graphic;
graphic.data = read_u8(argv[0], &graphic.size);
if (options.trim_whitespace) {
trim_whitespace(&graphic);
}
if (options.interleave) {
if (!options.png_file) {
error_exit("--interleave needs --png to infer dimensions\n");
}
int width = read_png_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;
}

6497
tools/lodepng/lodepng.c Normal file

File diff suppressed because it is too large Load diff

2019
tools/lodepng/lodepng.h Normal file

File diff suppressed because it is too large Load diff

35
tools/lz/global.c Normal file
View file

@ -0,0 +1,35 @@
#include "proto.h"
const struct compressor compressors[] = {
// NOTE: the "flags" field for each compressor will be set to the chosen/current method number minus the base
// number for that particular compressor. That means that each compressor will use a zero-based flags value.
{.methods = 72, .name = "singlepass", .function = &try_compress_single_pass}, // 0-71
{.methods = 2, .name = "null", .function = &store_uncompressed}, // 72-73
{.methods = 6, .name = "repetitions", .function = &try_compress_repetitions}, // 74-79
{.methods = 16, .name = "multipass", .function = &try_compress_multi_pass}, // 80-95
{0} // end of the list
};
const unsigned char bit_flipping_table[] = {
// For each byte, the table contains that same byte with its bits flipped around (for instance,
// 0x58 (01011000 binary) becomes 0x1a (00011010 binary)). This is faster than flipping bits
// manually at runtime.
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
};
char option_name_buffer[] = "-?"; // used to extract the name of a short option (separated from its argument)

54
tools/lz/main.c Normal file
View file

@ -0,0 +1,54 @@
#include "proto.h"
int main (int argc, char ** argv) {
struct options options = get_options(argc, argv);
unsigned short size;
unsigned char * file_buffer = read_file_into_buffer(options.input, &size);
struct command * commands;
if (options.mode & 2) {
unsigned short original_size = size, remainder;
commands = get_commands_from_file(file_buffer, &size, &remainder);
if (!commands) error_exit(1, "invalid command stream");
if (options.mode == 2) {
unsigned char * uncompressed = get_uncompressed_data(commands, file_buffer, &size);
if (!uncompressed) error_exit(1, "output data is too large");
write_raw_data_to_file(options.output, uncompressed, size);
free(uncompressed);
} else
write_commands_and_padding_to_textfile(options.output, commands, size, file_buffer, original_size - remainder, remainder);
} else {
commands = compress(file_buffer, &size, options.method);
(options.mode ? write_commands_to_textfile : write_commands_to_file)(options.output, commands, size, file_buffer, options.alignment);
}
free(file_buffer);
free(commands);
return 0;
}
struct command * compress (const unsigned char * data, unsigned short * size, unsigned method) {
unsigned char * bitflipped = malloc(*size);
unsigned current;
for (current = 0; current < *size; current ++) bitflipped[current] = bit_flipping_table[data[current]];
const struct compressor * compressor = compressors;
struct command * result;
if (method < COMPRESSION_METHODS) {
while (method >= compressor -> methods) method -= (compressor ++) -> methods;
result = compressor -> function(data, bitflipped, size, method);
} else {
struct command * compressed_sequences[COMPRESSION_METHODS];
unsigned short lengths[COMPRESSION_METHODS];
unsigned flags = 0;
for (current = 0; current < COMPRESSION_METHODS; current ++) {
lengths[current] = *size;
if (flags == compressor -> methods) {
flags = 0;
compressor ++;
}
compressed_sequences[current] = compressor -> function(data, bitflipped, lengths + current, flags ++);
}
result = select_optimal_sequence(compressed_sequences, lengths, size);
for (current = 0; current < COMPRESSION_METHODS; current ++) free(compressed_sequences[current]);
}
free(bitflipped);
return result;
}

102
tools/lz/merging.c Normal file
View file

@ -0,0 +1,102 @@
#include "proto.h"
struct command * select_optimal_sequence (struct command ** sequences, const unsigned short * lengths, unsigned short * final_length) {
struct command * compressor_sequences[NUM_COMPRESSORS * 2];
unsigned short compressor_lengths[NUM_COMPRESSORS * 2];
struct command * inverted_sequences[COMPRESSION_METHODS];
unsigned short inverted_lengths[COMPRESSION_METHODS];
unsigned p, current, method = 0;
for (current = 0; current < NUM_COMPRESSORS; current ++) {
compressor_sequences[current] = select_command_sequence(sequences + method, lengths + method, compressors[current].methods, compressor_lengths + current);
compressor_sequences[current + NUM_COMPRESSORS] = select_command_sequence(sequences + method, lengths + method, -(int) compressors[current].methods,
compressor_lengths + (current + NUM_COMPRESSORS));
for (p = 0; p < compressors[current].methods; p ++) {
inverted_sequences[method + compressors[current].methods - 1 - p] = sequences[method + p];
inverted_lengths[method + compressors[current].methods - 1 - p] = lengths[method + p];
}
method += compressors[current].methods;
}
unsigned short final_lengths[8];
struct command * final_sequences[8] = {
select_command_sequence(compressor_sequences, compressor_lengths, NUM_COMPRESSORS, final_lengths),
select_command_sequence(compressor_sequences, compressor_lengths, -NUM_COMPRESSORS, final_lengths + 1),
select_command_sequence(compressor_sequences + NUM_COMPRESSORS, compressor_lengths + NUM_COMPRESSORS, NUM_COMPRESSORS, final_lengths + 2),
select_command_sequence(compressor_sequences + NUM_COMPRESSORS, compressor_lengths + NUM_COMPRESSORS, -NUM_COMPRESSORS, final_lengths + 3),
select_command_sequence(sequences, lengths, COMPRESSION_METHODS, final_lengths + 4),
select_command_sequence(sequences, lengths, -COMPRESSION_METHODS, final_lengths + 5),
select_command_sequence(inverted_sequences, inverted_lengths, COMPRESSION_METHODS, final_lengths + 6),
select_command_sequence(inverted_sequences, inverted_lengths, -COMPRESSION_METHODS, final_lengths + 7)
};
for (current = 0; current < (2 * NUM_COMPRESSORS); current ++) free(compressor_sequences[current]);
struct command * result = select_command_sequence(final_sequences, final_lengths, 8, final_length);
for (current = 0; current < 8; current ++) free(final_sequences[current]);
return result;
}
struct command * select_command_sequence (struct command ** sequences, const unsigned short * lengths, int count, unsigned short * final_length) {
// negative count indicates iterating backwards
unsigned short min_sequence = 0, min_length = compressed_length(*sequences, *lengths);
unsigned short seq, len;
int backwards = 0;
if (count < 0) {
backwards = 1;
count = -count;
}
for (seq = 1; seq < count; seq ++) {
len = compressed_length(sequences[seq], lengths[seq]);
if (len < min_length) {
min_sequence = seq;
min_length = len;
}
}
*final_length = lengths[min_sequence];
struct command * current = malloc(*final_length * sizeof(struct command));
memcpy(current, sequences[min_sequence], *final_length * sizeof(struct command));
struct command * new;
for (seq = 1; seq < count; seq ++) {
if (backwards) seq = count - seq;
new = merge_command_sequences(current, *final_length, sequences[(seq + min_sequence) % count], lengths[(seq + min_sequence) % count], final_length);
if (backwards) seq = count - seq; // restore the value for the loop
free(current);
current = new;
}
return current;
}
struct command * merge_command_sequences (const struct command * current, unsigned short current_length, const struct command * new, unsigned short new_length,
unsigned short * result_length) {
struct command * result = malloc(sizeof(struct command) * (current_length + new_length));
struct command * current_command = result;
const struct command * saved_current;
const struct command * saved_new;
unsigned short current_pos, new_pos;
while (current_length) {
if (current -> count == new -> count) {
*(current_command ++) = pick_best_command(2, *(current ++), *(new ++));
current_length --;
continue;
}
saved_current = current;
saved_new = new;
current_pos = (current ++) -> count;
new_pos = (new ++) -> count;
current_length --;
while (current_pos != new_pos)
if (current_pos < new_pos) {
current_pos += (current ++) -> count;
current_length --;
} else
new_pos += (new ++) -> count;
current_pos = compressed_length(saved_current, current - saved_current);
new_pos = compressed_length(saved_new, new - saved_new);
if (new_pos < current_pos) {
memcpy(current_command, saved_new, sizeof(struct command) * (new - saved_new));
current_command += new - saved_new;
} else {
memcpy(current_command, saved_current, sizeof(struct command) * (current - saved_current));
current_command += current - saved_current;
}
}
*result_length = current_command - result;
return result;
}

112
tools/lz/mpcomp.c Normal file
View file

@ -0,0 +1,112 @@
#include "proto.h"
/*
Multi-pass compressor: performs an initial pass generating a single command for each byte position in the data and
refines the command stream further in subsequent passes.
Methods defined: 16
Flags values: the flags are a bitfield; each bit triggers some alternate behavior if set:
1: always emit a literal command (0) for the first byte of the file
2: when reducing a two-byte repetition (2) command in the overlap elimination pass, don't force it to contain a
whole number of repetitions (i.e., an even count)
4: don't emit copy commands (4, 5, 6) with a count of 3
8: don't emit single-byte repetition (1) commands
*/
struct command * try_compress_multi_pass (const unsigned char * data, const unsigned char * flipped, unsigned short * size, unsigned flags) {
struct command * result = calloc(*size, sizeof(struct command));
unsigned char * reversed = malloc(*size);
short * sources = malloc(*size * sizeof(short));
unsigned short pos, next, current = 0;
for (pos = 0; pos < *size; pos ++) {
reversed[pos] = data[*size - 1 - pos];
sources[pos] = -1;
}
for (pos = (flags & 1); pos < *size; pos += (result[pos].count >= MULTIPASS_SKIP_THRESHOLD) ? result[pos].count : 1) {
result[pos] = pick_command_for_pass(data, flipped, reversed, sources, *size, pos, flags);
if ((result[pos].command >= 4) || (result[pos].count < MULTIPASS_SKIP_THRESHOLD)) sources[current ++] = pos;
}
free(reversed);
free(sources);
for (pos = 0; pos < *size; pos ++) {
for (current = 1; current < result[pos].count; current ++) if (result[pos + current].count > result[pos].count) {
result[pos].count = current;
if ((result[pos].command == 2) && (current & 1) && !(flags & 2)) result[pos].count --;
}
if (result[pos].count <= command_size(result[pos])) result[pos] = (struct command) {.command = 0, .count = 0};
}
for (pos = 0; pos < *size; pos ++)
if (!result[pos].command) {
for (current = 1; (current < MAX_COMMAND_COUNT) && ((pos + current) < *size); current ++) if (result[pos + current].command) break;
result[pos] = (struct command) {.command = 0, .count = current, .value = pos};
} else if (result[pos].count > MAX_COMMAND_COUNT) {
result[pos + MAX_COMMAND_COUNT] = result[pos];
result[pos + MAX_COMMAND_COUNT].count -= MAX_COMMAND_COUNT;
if ((result[pos + MAX_COMMAND_COUNT].command >= 4) && (result[pos + MAX_COMMAND_COUNT].value >= 0))
result[pos + MAX_COMMAND_COUNT].value += (result[pos].command == 6) ? -MAX_COMMAND_COUNT : MAX_COMMAND_COUNT;
result[pos].count = MAX_COMMAND_COUNT;
}
for (next = pos = 0; pos < *size; pos ++)
if (pos == next)
next += result[pos].count;
else
result[pos].command = 7;
repack(&result, size);
return result;
}
struct command pick_command_for_pass (const unsigned char * data, const unsigned char * flipped, const unsigned char * reversed, const short * sources,
unsigned short length, unsigned short position, unsigned flags) {
struct command result = pick_repetition_for_pass(data, length, position, flags);
if (result.count >= MULTIPASS_SKIP_THRESHOLD) return result;
unsigned char p;
for (p = 0; p < 3; p ++) {
struct command temp = pick_copy_for_pass(data, p[(const unsigned char * []) {data, flipped, reversed}], sources, p + 4, length, position, flags);
if (temp.command == 7) continue;
if (temp.count > result.count) result = temp;
}
if ((result.command >= 4) && (result.value >= (position - LOOKBACK_LIMIT))) result.value -= position;
return result;
}
struct command pick_repetition_for_pass (const unsigned char * data, unsigned short length, unsigned short position, unsigned flags) {
unsigned short p;
if (data[position]) {
if ((position + 1) >= length) return (struct command) {.command = 1, .count = 1, .value = data[position]};
struct command result;
if (!(flags & 8) && (data[position] == data[position + 1]))
result = (struct command) {.command = 1, .value = data[position]};
else
result = (struct command) {.command = 2, .value = data[position] | (data[position + 1] << 8)};
for (p = 1; ((position + p) < length) && (p < LOOKAHEAD_LIMIT); p ++) if (data[position + p] != data[position + (p & 1)]) break;
result.count = p;
return result;
} else {
for (p = position + 1; (p < length) && (p < (position + LOOKAHEAD_LIMIT)); p ++) if (data[p]) break;
return (struct command) {.command = 3, .count = p - position};
}
}
struct command pick_copy_for_pass (const unsigned char * data, const unsigned char * reference, const short * sources, unsigned char command_type,
unsigned short length, unsigned short position, unsigned flags) {
struct command result = {.command = 7, .count = (flags & 4) ? 4 : 3};
if (length < 3) return result;
unsigned refpos, count;
const unsigned char * current;
unsigned char buffer[6] = {0};
memcpy(buffer, reference + length - 3, 3);
while (*sources >= 0) {
refpos = *(sources ++);
if (command_type == 6) refpos = length - 1 - refpos;
if (refpos >= (length - 3))
current = buffer + refpos - (length - 3);
else
current = reference + refpos;
if (memcmp(data + position, current, ((position + 4) > length) ? length - position : 4)) continue;
for (count = 4; (count < (length - position)) && (count < (length - refpos)); count ++) if (data[position + count] != current[count]) break;
if (count > (length - refpos)) count = length - refpos;
if (count > (length - position)) count = length - position;
if (result.count > count) continue;
result = (struct command) {.command = command_type, .count = count, .value = sources[-1]};
}
return result;
}

20
tools/lz/nullcomp.c Normal file
View file

@ -0,0 +1,20 @@
#include "proto.h"
/*
Null compressor: stores data uncompressed, using literal (0) commands only.
Methods defined: 2
Flags values: 0 = split a trailing 33-to-64-byte block at the end into two short blocks; 1 = don't
*/
struct command * store_uncompressed (__attribute__((unused)) const unsigned char * data, __attribute__((unused)) const unsigned char * bitflipped, unsigned short * size, unsigned flags) {
unsigned short position, block, remainder = *size;
struct command * result = NULL;
*size = 0;
for (position = 0; remainder; position += block, remainder -= block) {
block = (remainder > MAX_COMMAND_COUNT) ? MAX_COMMAND_COUNT : remainder;
if (!(flags & 1) && (block <= (2 * SHORT_COMMAND_COUNT)) && (block > SHORT_COMMAND_COUNT)) block = SHORT_COMMAND_COUNT;
result = realloc(result, sizeof(struct command) * (1 + *size));
result[(*size) ++] = (struct command) {.command = 0, .count = block, .value = position};
}
return result;
}

141
tools/lz/options.c Normal file
View file

@ -0,0 +1,141 @@
#include "proto.h"
struct options get_options (int argc, char ** argv) {
struct options result = {.input = NULL, .output = NULL, .mode = 0, .alignment = 0, .method = COMPRESSION_METHODS};
const char * program_name = *argv;
int compressor = -1;
if (argc == 1) usage(program_name);
for (argv ++; *argv; argv ++) {
if (**argv != '-') break;
if (!1[*argv]) break;
if (!strcmp(*argv, "--")) {
argv ++;
break;
} else if (!(strcmp(*argv, "--text") && strcmp(*argv, "-t")))
result.mode = 1;
else if (!(strcmp(*argv, "--binary") && strcmp(*argv, "-b")))
result.mode = 0;
else if (!(strcmp(*argv, "--uncompress") && strcmp(*argv, "-u")))
result.mode = 2;
else if (!(strcmp(*argv, "--dump") && strcmp(*argv, "-d")))
result.mode = 3;
else if (!(strcmp(*argv, "--align") && strncmp(*argv, "-a", 2)))
result.alignment = parse_numeric_option_argument(&argv, 12);
else if (!(strcmp(*argv, "--method") && strncmp(*argv, "-m", 2)))
result.method = parse_numeric_option_argument(&argv, COMPRESSION_METHODS - 1);
else if (!(strcmp(*argv, "--compressor") && strncmp(*argv, "-c", 2)))
compressor = parse_compressor_option_argument(&argv);
else if (!(strcmp(*argv, "--optimize") && strcmp(*argv, "-o"))) {
result.method = COMPRESSION_METHODS;
compressor = -1;
} else if (!(strcmp(*argv, "--help") && strcmp(*argv, "-?")))
usage(program_name);
else if (!(strcmp(*argv, "--list") && strcmp(*argv, "-l")))
list_compressors();
else
error_exit(3, "unknown option: %s", *argv);
}
if (compressor >= 0) {
if (result.method >= COMPRESSION_METHODS) result.method = 0;
if (result.method >= compressors[compressor].methods)
error_exit(3, "method for the %s compressor must be between 0 and %u", compressors[compressor].name, compressors[compressor].methods - 1);
while (compressor > 0) result.method += compressors[-- compressor].methods;
}
if (*argv) {
if (strcmp(*argv, "-")) result.input = *argv;
if (*(++ argv)) {
if (argv[1]) error_exit(3, "too many command-line arguments");
if (strcmp(*argv, "-")) result.output = *argv;
}
}
return result;
}
unsigned parse_numeric_option_argument (char *** alp, unsigned limit) {
const char * option;
const char * value = get_argument_for_option(alp, &option);
char * error;
unsigned long result = strtoul(value, &error, 10);
if (*error) error_exit(3, "invalid argument to option %s", option);
if (result > limit) error_exit(3, "argument to option %s must be between 0 and %u", option, limit);
return result;
}
int parse_compressor_option_argument (char *** alp) {
const char * name = get_argument_for_option(alp, NULL);
if (!strcmp(name, "*")) return -1;
int result = -1;
unsigned length = strlen(name);
const struct compressor * compressor;
for (compressor = compressors; compressor -> name; compressor ++) {
if (strncmp(name, compressor -> name, length)) continue;
if (result >= 0) error_exit(3, "ambiguous compressor prefix: %s", name);
result = compressor - compressors;
}
if (result < 0) error_exit(3, "unknown compressor: %s", name);
return result;
}
const char * get_argument_for_option (char *** alp, const char ** option_name) {
// alp: argument list pointer (i.e., address of the current value of argv after indexing)
// will point at the last consumed argument on exit (since the caller will probably increment it once more)
const char * option;
const char * result;
if (1[**alp] == '-') {
option = *((*alp) ++);
result = **alp;
} else {
option_name_buffer[1] = 1[**alp];
option = option_name_buffer;
result = **alp + 2;
}
if (!(result && *result)) error_exit(3, "option %s requires an argument", option);
if (option_name) *option_name = option;
return result;
}
noreturn usage (const char * program_name) {
fprintf(stderr, "Usage: %s [<options>] [<source file> [<output>]]\n\n", program_name);
fputs("Execution mode:\n", stderr);
fputs(" -b, --binary Output the command stream as binary data (default).\n", stderr);
fputs(" -t, --text Output the command stream as text.\n", stderr);
fputs(" -u, --uncompress Process a compressed file and output the original data.\n", stderr);
fputs(" -d, --dump Process a compressed file and dump the command stream as\n", stderr);
fputs(" text (as if compressed with the --text option).\n", stderr);
fputs(" -l, --list List compressors and their method numbers.\n", stderr);
fputs(" -?, --help Print this help text and exit.\n", stderr);
fputs("Compression options:\n", stderr);
fputs(" -o, --optimize Use the best combination of compression\n", stderr);
fputs(" methods available (default).\n", stderr);
fputs(" -m<number>, --method <number> Use only one specific compression method.\n", stderr);
fprintf(stderr, " Valid method numbers are between 0 and %u.\n", COMPRESSION_METHODS - 1);
fputs(" -c<name>, --compressor <name> Use the specified compressor: the method\n", stderr);
fputs(" number will be relative to that compressor.\n", stderr);
fputs(" Any prefix of the compressor name may be\n", stderr);
fputs(" specified. Use * to indicate any compressor.\n", stderr);
fputs(" -a<number>, --align <number> Pad the compressed output with zeros until\n", stderr);
fputs(" the size has the specified number of low bits\n", stderr);
fputs(" cleared (default: 0).\n", stderr);
fputs("The source and output filenames can be given as - (or omitted) to use standard\n", stderr);
fputs("input and output. Use -- to indicate that subsequent arguments are file names.\n", stderr);
exit(3);
}
noreturn list_compressors (void) {
const struct compressor * compressor;
unsigned current, length = 10;
for (compressor = compressors; compressor -> name; compressor ++) if ((current = strlen(compressor -> name)) > length) length = current;
fprintf(stderr, "%-*s Offset Methods\n", length, "Compressor");
for (current = 0; current < length; current ++) putc('-', stderr);
fputs(" ------ -------\n", stderr);
current = 0;
for (compressor = compressors; compressor -> name; compressor ++) {
fprintf(stderr, "%-*s %6u %7u\n", length, compressor -> name, current, compressor -> methods);
current += compressor -> methods;
}
putc('\n', stderr);
fputs("Note: the offset indicates the compressor's lowest method number when the\n", stderr);
fputs("--compressor option is not given. When that option is used, every compressor's\n", stderr);
fputs("methods are numbered from zero.\n", stderr);
exit(3);
}

146
tools/lz/output.c Normal file
View file

@ -0,0 +1,146 @@
#include "proto.h"
void write_commands_to_textfile (const char * file, const struct command * commands, unsigned count, const unsigned char * input_stream,
unsigned char alignment) {
FILE * fp = file ? fopen(file, "w") : stdout;
if (!fp) error_exit(1, "could not open file %s for writing", file);
unsigned length = 0;
while (count --) {
write_command_to_textfile(fp, *commands, input_stream);
length += command_size(*(commands ++));
}
if (fputs("\tlzend\n", fp) < 0) error_exit(1, "could not write terminator to compressed output");
length = ~length & ((1 << alignment) - 1);
if (length --) {
int rv = fputs("\tdb 0", fp);
while ((rv >= 0) && length --) rv = fputs(", 0", fp);
if (rv >= 0) rv = -(putc('\n', fp) == EOF);
if (rv < 0) error_exit(1, "could not write padding to compressed output");
}
if (file) fclose(fp);
}
void write_commands_and_padding_to_textfile (const char * file, const struct command * commands, unsigned count, const unsigned char * input_stream,
unsigned padding_offset, unsigned padding_size) {
FILE * fp = file ? fopen(file, "w") : stdout;
if (!fp) error_exit(1, "could not open file %s for writing", file);
while (count --) write_command_to_textfile(fp, *(commands ++), input_stream);
if (fputs("\tlzend\n", fp) < 0) error_exit(1, "could not write terminator to compressed output");
if (padding_size) {
input_stream += padding_offset;
int rv = 0;
unsigned pos;
const char * prefix = "\tdb";
for (pos = 0; (rv >= 0) && (pos < padding_size); pos ++) {
if (input_stream[pos])
rv = fprintf(fp, "%s $%02hhx", prefix, input_stream[pos]);
else
rv = fprintf(fp, "%s 0", prefix);
prefix = ",";
}
if (rv >= 0) rv = -(putc('\n', fp) == EOF);
if (rv < 0) error_exit(1, "could not write padding to compressed output");
}
if (file) fclose(fp);
}
void write_command_to_textfile (FILE * fp, struct command command, const unsigned char * input_stream) {
if ((!command.count) || (command.count > MAX_COMMAND_COUNT)) error_exit(2, "invalid command in output stream");
int rv, pos;
const char * kind;
switch (command.command) {
case 0:
if ((rv = fprintf(fp, "\tlzdata")) < 0) break;
for (pos = 0; pos < command.count; pos ++) if ((rv = fprintf(fp, "%s$%02hhx", pos ? ", " : " ", input_stream[command.value + pos])) < 0) break;
rv = putc('\n', fp);
break;
case 1:
if ((command.value < 0) || (command.value > 255)) error_exit(2, "invalid command in output stream");
rv = fprintf(fp, "\tlzrepeat %u, $%02hhx\n", command.count, (unsigned char) command.value);
break;
case 2:
if (command.value < 0) error_exit(2, "invalid command in output stream");
rv = fprintf(fp, "\tlzrepeat %u, $%02hhx, $%02hhx\n", command.count, (unsigned char) command.value, (unsigned char) (command.value >> 8));
break;
case 3:
rv = fprintf(fp, "\tlzzero %u\n", command.count);
break;
case 4:
kind = "normal";
goto copy;
case 5:
kind = "flipped";
goto copy;
case 6:
kind = "reversed";
copy:
if ((command.value < -LOOKBACK_LIMIT) || (command.value >= MAX_FILE_SIZE)) error_exit(2, "invalid command in output stream");
if (command.value < 0)
rv = fprintf(fp, "\tlzcopy %s, %u, %d\n", kind, command.count, command.value);
else
rv = fprintf(fp, "\tlzcopy %s, %u, $%04hx\n", kind, command.count, (unsigned short) command.value);
break;
default:
error_exit(2, "invalid command in output stream");
}
if (rv < 0) error_exit(1, "could not write command to compressed output");
}
void write_commands_to_file (const char * file, const struct command * commands, unsigned count, const unsigned char * input_stream, unsigned char alignment) {
FILE * fp = file ? fopen(file, "wb") : stdout;
if (!fp) error_exit(1, "could not open file %s for writing", file);
unsigned length = 0;
while (count --) {
write_command_to_file(fp, *commands, input_stream);
length += command_size(*(commands ++));
}
if (putc(-1, fp) == EOF) error_exit(1, "could not write terminator to compressed output");
length = ~length & ((1 << alignment) - 1);
while (length --) if (putc(0, fp) == EOF) error_exit(1, "could not write padding to compressed output");
if (file) fclose(fp);
}
void write_command_to_file (FILE * fp, struct command command, const unsigned char * input_stream) {
if ((!command.count) || (command.count > MAX_COMMAND_COUNT)) error_exit(2, "invalid command in output stream");
unsigned char buf[4];
unsigned char * pos = buf;
int n;
command.count --;
if (command.count < SHORT_COMMAND_COUNT)
*(pos ++) = (command.command << 5) + command.count;
else {
*(pos ++) = 224 + (command.command << 2) + (command.count >> 8);
*(pos ++) = command.count;
}
switch (command.command) {
case 1: case 2:
if ((command.value < 0) || (command.value >= (1 << (command.command << 3)))) error_exit(2, "invalid command in output stream");
for (n = 0; n < command.command; n ++) *(pos ++) = command.value >> (n << 3);
case 0: case 3:
break;
default:
if ((command.value < -LOOKBACK_LIMIT) || (command.value >= MAX_FILE_SIZE)) error_exit(2, "invalid command in output stream");
if (command.value < 0)
*(pos ++) = command.value ^ 127;
else {
*(pos ++) = command.value >> 8;
*(pos ++) = command.value;
}
}
if (fwrite(buf, 1, pos - buf, fp) != (pos - buf)) error_exit(1, "could not write command to compressed output");
if (command.command) return;
command.count ++;
if (fwrite(input_stream + command.value, 1, command.count, fp) != command.count) error_exit(1, "could not write data to compressed output");
}
void write_raw_data_to_file (const char * file, const void * data, unsigned length) {
FILE * fp = file ? fopen(file, "w") : stdout;
if (!fp) error_exit(1, "could not open file %s for writing", file);
while (length) {
unsigned rv = fwrite(data, 1, length, fp);
if (!rv) error_exit(1, "could not write raw data to output");
data = (const char *) data + rv;
length -= rv;
}
if (file) fclose(fp);
}

56
tools/lz/packing.c Normal file
View file

@ -0,0 +1,56 @@
#include "proto.h"
void optimize (struct command * commands, unsigned short count) {
while (count && (commands -> command == 7)) commands ++, count --;
if (count < 2) return;
struct command * end = commands + count;
struct command * next;
for (next = commands + 1; next < end; next ++) {
if (next -> command == 7) continue;
if (
!(commands -> command) &&
(command_size(*next) == next -> count) &&
((commands -> count + next -> count) <= MAX_COMMAND_COUNT) &&
((commands -> count > SHORT_COMMAND_COUNT) || ((commands -> count + next -> count) <= SHORT_COMMAND_COUNT))
) {
commands -> count += next -> count;
next -> command = 7;
continue;
}
if (next -> command == commands -> command)
switch (commands -> command) {
case 0:
if ((commands -> value + commands -> count) != next -> value) break;
commands -> count += next -> count;
next -> command = 7;
if (commands -> count <= MAX_COMMAND_COUNT) continue;
next -> command = 0;
next -> value = commands -> value + MAX_COMMAND_COUNT;
next -> count = commands -> count - MAX_COMMAND_COUNT;
commands -> count = MAX_COMMAND_COUNT;
break;
case 1:
if (commands -> value != next -> value) break;
// fallthrough
case 3:
if ((commands -> count + next -> count) <= MAX_COMMAND_COUNT) {
commands -> count += next -> count;
next -> command = 7;
continue;
}
next -> count = (commands -> count + next -> count) - MAX_COMMAND_COUNT;
commands -> count = MAX_COMMAND_COUNT;
}
commands = next;
}
}
void repack (struct command ** commands, unsigned short * length) {
struct command * new_commands = malloc(sizeof(struct command) * *length);
struct command * current = new_commands;
unsigned short p;
for (p = 0; p < *length; p ++) if (p[*commands].command != 7) *(current ++) = p[*commands];
free(*commands);
*commands = new_commands;
*length = current - new_commands;
}

107
tools/lz/proto.h Normal file
View file

@ -0,0 +1,107 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#define NUM_COMPRESSORS 4
#define COMPRESSION_METHODS 96 /* sum of all values for the methods field in compressors */
#define MAX_FILE_SIZE 32768
#define SHORT_COMMAND_COUNT 32
#define MAX_COMMAND_COUNT 1024
#define LOOKBACK_LIMIT 128 /* highest negative valid count for a copy command */
#define LOOKAHEAD_LIMIT 3072 /* maximum lookahead distance for the first pass of multi-pass compression */
#define MULTIPASS_SKIP_THRESHOLD 64
#if __STDC_VERSION__ >= 201112L
// <noreturn.h> forces "noreturn void", which is silly and redundant; this is simpler
#define noreturn _Noreturn void
#else
#define noreturn void /* fallback */
#endif
struct command {
unsigned command: 3; // commands 0-6 as per compression spec; command 7 is used as a dummy placeholder
unsigned count: 12; // always equals the uncompressed data length
signed value: 17; // offset for commands 0 (into source) and 4-6 (into decompressed output); repeated bytes for commands 1-2
};
struct compressor {
unsigned methods;
const char * name;
struct command * (* function) (const unsigned char *, const unsigned char *, unsigned short *, unsigned);
};
struct options {
const char * input;
const char * output;
unsigned method; // method to use, or >= COMPRESSION_METHODS to try them all
unsigned char mode; // 0: compress, 1: compress to text, 2: uncompress, 3: dump commands as text
unsigned char alignment; // 1 << value
};
// global.c
extern const struct compressor compressors[];
extern const unsigned char bit_flipping_table[];
extern char option_name_buffer[];
// main.c
int main(int, char **);
struct command * compress(const unsigned char *, unsigned short *, unsigned);
// merging.c
struct command * select_optimal_sequence(struct command **, const unsigned short *, unsigned short *);
struct command * select_command_sequence(struct command **, const unsigned short *, int, unsigned short *);
struct command * merge_command_sequences(const struct command *, unsigned short, const struct command *, unsigned short, unsigned short *);
// mpcomp.c
struct command * try_compress_multi_pass(const unsigned char *, const unsigned char *, unsigned short *, unsigned);
struct command pick_command_for_pass(const unsigned char *, const unsigned char *, const unsigned char *, const short *, unsigned short,
unsigned short, unsigned);
struct command pick_repetition_for_pass(const unsigned char *, unsigned short, unsigned short, unsigned);
struct command pick_copy_for_pass(const unsigned char *, const unsigned char *, const short *, unsigned char, unsigned short, unsigned short, unsigned);
// nullcomp.c
struct command * store_uncompressed(const unsigned char *, const unsigned char *, unsigned short *, unsigned);
// options.c
struct options get_options(int, char **);
unsigned parse_numeric_option_argument(char ***, unsigned);
int parse_compressor_option_argument(char ***);
const char * get_argument_for_option(char ***, const char **);
noreturn usage(const char *);
noreturn list_compressors(void);
// output.c
void write_commands_to_textfile(const char *, const struct command *, unsigned, const unsigned char *, unsigned char);
void write_commands_and_padding_to_textfile(const char *, const struct command *, unsigned, const unsigned char *, unsigned, unsigned);
void write_command_to_textfile(FILE *, struct command, const unsigned char *);
void write_commands_to_file(const char *, const struct command *, unsigned, const unsigned char *, unsigned char);
void write_command_to_file(FILE *, struct command, const unsigned char *);
void write_raw_data_to_file(const char *, const void *, unsigned);
// packing.c
void optimize(struct command *, unsigned short);
void repack(struct command **, unsigned short *);
// repcomp.c
struct command * try_compress_repetitions(const unsigned char *, const unsigned char *, unsigned short *, unsigned);
struct command find_repetition_at_position(const unsigned char *, unsigned short, unsigned short);
// spcomp.c
struct command * try_compress_single_pass(const unsigned char *, const unsigned char *, unsigned short *, unsigned);
struct command find_best_copy(const unsigned char *, unsigned short, unsigned short, const unsigned char *, unsigned);
unsigned short scan_forwards(const unsigned char *, unsigned short, const unsigned char *, unsigned short, short *);
unsigned short scan_backwards(const unsigned char *, unsigned short, unsigned short, short *);
struct command find_best_repetition(const unsigned char *, unsigned short, unsigned short);
// uncomp.c
struct command * get_commands_from_file(const unsigned char *, unsigned short * restrict, unsigned short * restrict);
unsigned char * get_uncompressed_data(const struct command *, const unsigned char *, unsigned short *);
// util.c
noreturn error_exit(int, const char *, ...);
unsigned char * read_file_into_buffer(const char *, unsigned short *);
struct command pick_best_command(unsigned, struct command, ...);
int is_better(struct command, struct command);
short command_size(struct command);
unsigned short compressed_length(const struct command *, unsigned short);

63
tools/lz/repcomp.c Normal file
View file

@ -0,0 +1,63 @@
#include "proto.h"
/*
Repetitions compressor: compresses the data only using a subset of the available repetition commands.
Methods defined: 6
Flags values: the value plus one is taken as a bitfield indicating which kinds of repetition commands are used
(lowest bit to highest: repeat single byte (1), repeat two bytes (2), repeat zeros (3)).
*/
struct command * try_compress_repetitions (const unsigned char * data, __attribute__((unused)) const unsigned char * bitflipped, unsigned short * size, unsigned flags) {
unsigned short pos = 0, skipped = 0;
struct command * result = malloc(*size * sizeof(struct command));
struct command * current = result;
struct command candidate;
flags = (flags + 1) << 1;
while (pos < *size) {
candidate = find_repetition_at_position(data, pos, *size);
if ((candidate.command == 3) && !(flags & 8)) {
candidate.command = 1;
candidate.value = 0;
}
if ((candidate.command == 1) && !(flags & 2)) {
candidate.command = 2;
candidate.value |= candidate.value << 8;
}
if ((flags & (1 << candidate.command)) && (command_size(candidate) <= candidate.count)) {
if (skipped) *(current ++) = (struct command) {.command = 0, .count = skipped, .value = pos - skipped};
skipped = 0;
*(current ++) = candidate;
pos += candidate.count;
} else {
pos ++;
if ((++ skipped) == MAX_COMMAND_COUNT) {
*(current ++) = (struct command) {.command = 0, .count = MAX_COMMAND_COUNT, .value = pos - MAX_COMMAND_COUNT};
skipped = 0;
}
}
}
if (skipped) *(current ++) = (struct command) {.command = 0, .count = skipped, .value = pos - skipped};
*size = current - result;
result = realloc(result, *size * sizeof(struct command));
return result;
}
struct command find_repetition_at_position (const unsigned char * data, unsigned short position, unsigned short length) {
if ((position + 1) >= length) return data[position] ? ((struct command) {.command = 7}) : ((struct command) {.command = 3, .count = 1});
unsigned char value[2] = {data[position], data[position + 1]};
unsigned repcount, limit = length - position;
if (limit > MAX_COMMAND_COUNT) limit = MAX_COMMAND_COUNT;
for (repcount = 2; (repcount < limit) && (data[position + repcount] == value[repcount & 1]); repcount ++);
struct command result;
result.count = repcount;
if (*value != value[1]) {
if (!*value && (repcount < 3)) return (struct command) {.command = 3, .count = 1};
result.command = 2;
result.value = ((unsigned) (*value)) | (((unsigned) (value[1])) << 8);
} else if (*value) {
result.command = 1;
result.value = *value;
} else
result.command = 3;
return result;
}

141
tools/lz/spcomp.c Normal file
View file

@ -0,0 +1,141 @@
#include "proto.h"
/*
Single-pass compressor: attempts to compress the data in a single pass, selecting the best command at each
position within some constraints.
Methods defined: 72
Flags values:
Bit fields (will trigger alternate behavior if set):
1: prefer repetition commands over copy commands of equal count
2: don't emit a copy or repetition with a count equal to its size when the previous command is a literal (0) that
is not at maximum size (32 or 1024)
4: don't emit long copy commands
Selector values (pick one from each group and add them to the bit fields):
- Scan delay: number of bytes that are forced into literal (0) commands after each non-literal command:
0: 0 bytes
8: 1 byte
16: 2 bytes
- Copy command preference (when the command counts are tied), in order from most to least:
0: normal (4), reversed (6), flipped (5)
24: reversed (6), flipped (5), normal (4)
48: flipped (5), reversed (6), normal (4)
*/
struct command * try_compress_single_pass (const unsigned char * data, const unsigned char * bitflipped, unsigned short * length, unsigned flags) {
struct command * commands = malloc(sizeof(struct command) * *length);
memset(commands, -1, sizeof(struct command) * *length);
struct command * current_command = commands;
unsigned short position = 0, previous_data = 0;
unsigned char scan_delay = 0, scan_delay_flag = (flags >> 3) % 3;
struct command copy, repetition;
while (position < *length) {
copy = find_best_copy(data, position, *length, bitflipped, flags);
repetition = find_best_repetition(data, position, *length);
if (flags & 1)
*current_command = pick_best_command(2, repetition, copy);
else
*current_command = pick_best_command(2, copy, repetition);
*current_command = pick_best_command(2, (struct command) {.command = 0, .count = 1, .value = position}, *current_command);
if ((flags & 2) && (command_size(*current_command) == current_command -> count))
if (previous_data && (previous_data != SHORT_COMMAND_COUNT) && (previous_data != MAX_COMMAND_COUNT))
*current_command = (struct command) {.command = 0, .count = 1, .value = position};
if (scan_delay_flag) {
if (scan_delay >= scan_delay_flag)
scan_delay = 0;
else if (current_command -> command) {
scan_delay ++;
*current_command = (struct command) {.command = 0, .count = 1, .value = position};
}
}
if (current_command -> command)
previous_data = 0;
else
previous_data += current_command -> count;
position += (current_command ++) -> count;
}
optimize(commands, current_command - commands);
repack(&commands, length);
return commands;
}
struct command find_best_copy (const unsigned char * data, unsigned short position, unsigned short length, const unsigned char * bitflipped, unsigned flags) {
struct command simple = {.command = 7};
struct command flipped = simple, backwards = simple;
short count, offset;
if ((count = scan_forwards(data + position, length - position, data, position, &offset)))
simple = (struct command) {.command = 4, .count = count, .value = offset};
if ((count = scan_forwards(data + position, length - position, bitflipped, position, &offset)))
flipped = (struct command) {.command = 5, .count = count, .value = offset};
if ((count = scan_backwards(data, length - position, position, &offset)))
backwards = (struct command) {.command = 6, .count = count, .value = offset};
struct command command;
switch (flags / 24) {
case 0: command = pick_best_command(3, simple, backwards, flipped); break;
case 1: command = pick_best_command(3, backwards, flipped, simple); break;
case 2: command = pick_best_command(3, flipped, backwards, simple);
}
if ((flags & 4) && (command.count > SHORT_COMMAND_COUNT)) command.count = SHORT_COMMAND_COUNT;
return command;
}
unsigned short scan_forwards (const unsigned char * target, unsigned short limit, const unsigned char * source, unsigned short real_position, short * offset) {
unsigned short best_match, best_length = 0;
unsigned short current_length;
unsigned short position;
for (position = 0; position < real_position; position ++) {
if (source[position] != *target) continue;
for (current_length = 0; (current_length < limit) && (source[position + current_length] == target[current_length]); current_length ++);
if (current_length > MAX_COMMAND_COUNT) current_length = MAX_COMMAND_COUNT;
if (current_length < best_length) continue;
best_match = position;
best_length = current_length;
}
if (!best_length) return 0;
if ((best_match + LOOKBACK_LIMIT) >= real_position)
*offset = best_match - real_position;
else
*offset = best_match;
return best_length;
}
unsigned short scan_backwards (const unsigned char * data, unsigned short limit, unsigned short real_position, short * offset) {
if (real_position < limit) limit = real_position;
unsigned short best_match, best_length = 0;
unsigned short current_length;
unsigned short position;
for (position = 0; position < real_position; position ++) {
if (data[position] != data[real_position]) continue;
for (current_length = 0; (current_length <= position) && (current_length < limit) &&
(data[position - current_length] == data[real_position + current_length]); current_length ++);
if (current_length > MAX_COMMAND_COUNT) current_length = MAX_COMMAND_COUNT;
if (current_length < best_length) continue;
best_match = position;
best_length = current_length;
}
if (!best_length) return 0;
if ((best_match + LOOKBACK_LIMIT) >= real_position)
*offset = best_match - real_position;
else
*offset = best_match;
return best_length;
}
struct command find_best_repetition (const unsigned char * data, unsigned short position, unsigned short length) {
if ((position + 1) >= length) return data[position] ? ((struct command) {.command = 7}) : ((struct command) {.command = 3, .count = 1});
unsigned char value[2] = {data[position], data[position + 1]};
unsigned repcount, limit = length - position;
if (limit > MAX_COMMAND_COUNT) limit = MAX_COMMAND_COUNT;
for (repcount = 2; (repcount < limit) && (data[position + repcount] == value[repcount & 1]); repcount ++);
struct command result;
result.count = repcount;
if (*value != value[1]) {
if (!*value && (repcount < 3)) return (struct command) {.command = 3, .count = 1};
result.command = 2;
result.value = ((unsigned) (*value)) | (((unsigned) (value[1])) << 8);
} else if (*value) {
result.command = 1;
result.value = *value;
} else
result.command = 3;
return result;
}

92
tools/lz/uncomp.c Normal file
View file

@ -0,0 +1,92 @@
#include "proto.h"
struct command * get_commands_from_file (const unsigned char * data, unsigned short * restrict size, unsigned short * restrict slack) {
struct command * result = malloc(*size * sizeof(struct command));
unsigned short remaining = *size;
struct command * current = result;
const unsigned char * rp = data;
while (1) {
if (!(remaining --)) goto error;
current -> command = *rp >> 5;
current -> count = *(rp ++) & 31;
if (current -> command == 7) {
current -> command = current -> count >> 2;
current -> count = (current -> count & 3) << 8;
if (current -> command == 7) {
// long commands inside long commands are not allowed, but if the count is 0x300 here, it means that the original byte was 0xff
if (current -> count == 0x300) break;
goto error;
}
if (!(remaining --)) goto error;
current -> count |= *(rp ++);
}
current -> count ++;
switch (current -> command) {
case 0:
if (remaining <= current -> count) goto error;
current -> value = rp - data;
rp += current -> count;
remaining -= current -> count;
case 3:
break;
case 1: case 2: {
unsigned char p;
if (remaining <= current -> command) goto error;
current -> value = 0;
for (p = 0; p < current -> command; p ++) current -> value |= *(rp ++) << (p << 3);
remaining -= current -> command;
} break;
default:
if (!(remaining --)) goto error;
if ((current -> value = *(rp ++)) & 128)
current -> value = 127 - current -> value;
else {
if (!(remaining --)) goto error;
current -> value = (current -> value << 8) | *(rp ++);
}
}
current ++;
}
if (slack) *slack = *size - (rp - data);
*size = current - result;
return realloc(result, (*size ? *size : 1) * sizeof(struct command));
error:
free(result);
return NULL;
}
unsigned char * get_uncompressed_data (const struct command * commands, const unsigned char * compressed, unsigned short * size) {
const struct command * limit = commands + *size;
unsigned char * result = malloc(MAX_FILE_SIZE + MAX_COMMAND_COUNT);
unsigned char * current = result;
unsigned short p;
for (; commands < limit; commands ++) {
switch (commands -> command) {
case 0:
memcpy(current, compressed + commands -> value, commands -> count);
current += commands -> count;
break;
case 1: case 2:
for (p = 0; p < commands -> count; p ++) *(current ++) = commands -> value >> ((p % commands -> command) << 3);
break;
case 3:
memset(current, 0, commands -> count);
current += commands -> count;
break;
default: {
const unsigned char * ref = ((commands -> value < 0) ? current : result) + commands -> value;
for (p = 0; p < commands -> count; p ++) {
current[p] = ref[(commands -> command == 6) ? -(int) p : p];
if (commands -> command == 5) current[p] = bit_flipping_table[current[p]];
}
current += commands -> count;
}
}
if ((current - result) > MAX_FILE_SIZE) {
free(result);
return NULL;
}
}
*size = current - result;
return realloc(result, *size ? *size : 1);
}

54
tools/lz/util.c Normal file
View file

@ -0,0 +1,54 @@
#include "proto.h"
noreturn error_exit (int error_code, const char * error, ...) {
va_list ap;
va_start(ap, error);
fputs("error: ", stderr);
vfprintf(stderr, error, ap);
va_end(ap);
fputc('\n', stderr);
exit(error_code);
}
unsigned char * read_file_into_buffer (const char * file, unsigned short * size) {
FILE * fp = file ? fopen(file, "rb") : stdin;
if (!fp) error_exit(1, "could not open file %s for reading", file);
unsigned char * buf = malloc(MAX_FILE_SIZE + 1);
int rv = fread(buf, 1, MAX_FILE_SIZE + 1, fp);
if (file) fclose(fp);
if (rv < 0) error_exit(1, "could not read from file %s", file);
if (rv > MAX_FILE_SIZE) error_exit(1, "file %s is too big", file ? file : "<standard input>");
*size = rv;
return buf;
}
struct command pick_best_command (unsigned count, struct command command, ...) {
struct command result = command;
va_list ap;
va_start(ap, command);
while (-- count) {
command = va_arg(ap, struct command);
if (is_better(command, result)) result = command;
}
va_end(ap);
return result;
}
int is_better (struct command new, struct command old) {
if (new.command == 7) return 0;
if (old.command == 7) return 1;
short new_savings = new.count - command_size(new), old_savings = old.count - command_size(old);
return new_savings > old_savings;
}
short command_size (struct command command) {
short header_size = 1 + (command.count > SHORT_COMMAND_COUNT);
if (command.command & 4) return header_size + 1 + (command.value >= 0);
return header_size + command.command[(short []) {command.count, 1, 2, 0}];
}
unsigned short compressed_length (const struct command * commands, unsigned short count) {
unsigned short current, total = 0;
for (current = 0; current < count; current ++) if (commands[current].command != 7) total += command_size(commands[current]);
return total;
}

471
tools/make_patch.c Normal file
View file

@ -0,0 +1,471 @@
#define PROGRAM_NAME "make_patch"
#define USAGE_OPTS "labels.sym constants.sym patched.gbc original.gbc vc.patch.template vc.patch"
#include "common.h"
#include <ctype.h>
struct Buffer {
size_t item_size;
size_t size;
size_t capacity;
void *data;
};
struct Symbol {
struct Symbol *next;
unsigned int address;
unsigned int offset;
char name[]; // C99 FAM
};
struct Patch {
unsigned int offset;
unsigned int size;
};
struct Buffer *buffer_create(size_t item_size) {
struct Buffer *buffer = xmalloc(sizeof(*buffer));
buffer->item_size = item_size;
buffer->size = 0;
buffer->capacity = 0x10;
buffer->data = xmalloc(buffer->capacity * item_size);
return buffer;
}
void buffer_append(struct Buffer *buffer, const void *item) {
if (buffer->size >= buffer->capacity) {
buffer->capacity = (buffer->capacity + 1) * 2;
buffer->data = xrealloc(buffer->data, buffer->capacity * buffer->item_size);
}
memcpy((char *)buffer->data + (buffer->size++ * buffer->item_size), item, buffer->item_size);
}
void buffer_free(struct Buffer *buffer) {
free(buffer->data);
free(buffer);
}
void symbol_append(struct Symbol **symbols, const char *name, int bank, int address) {
size_t name_len = strlen(name) + 1;
struct Symbol *symbol = xmalloc(sizeof(*symbol) + name_len);
symbol->address = address;
symbol->offset = address < 0x8000
? (bank > 0 ? address + (bank - 1) * 0x4000 : address) // ROM addresses are relative to their bank
: address - 0x8000; // RAM addresses are relative to the start of all RAM
memcpy(symbol->name, name, name_len);
symbol->next = *symbols;
*symbols = symbol;
}
void symbol_free(struct Symbol *symbols) {
for (struct Symbol *next; symbols; symbols = next) {
next = symbols->next;
free(symbols);
}
}
const struct Symbol *symbol_find(const struct Symbol *symbols, const char *name) {
size_t name_len = strlen(name);
for (const struct Symbol *symbol = symbols; symbol; symbol = symbol->next) {
size_t sym_name_len = strlen(symbol->name);
if (name_len > sym_name_len) {
continue;
}
const char *sym_name = symbol->name;
if (name[0] == '.') {
// If `name` is a local label, compare it to the local part of `symbol->name`
sym_name += sym_name_len - name_len;
}
if (!strcmp(sym_name, name)) {
return symbol;
}
}
error_exit("Error: Unknown symbol: \"%s\"\n", name);
}
const struct Symbol *symbol_find_cat(const struct Symbol *symbols, const char *prefix, const char *suffix) {
char *sym_name = xmalloc(strlen(prefix) + strlen(suffix) + 1);
sprintf(sym_name, "%s%s", prefix, suffix);
const struct Symbol *symbol = symbol_find(symbols, sym_name);
free(sym_name);
return symbol;
}
int parse_number(const char *input, int base) {
char *endptr;
int n = (int)strtol(input, &endptr, base);
if (endptr == input || *endptr || n < 0) {
error_exit("Error: Cannot parse number: \"%s\"\n", input);
}
return n;
}
void parse_symbol_value(char *input, int *restrict bank, int *restrict address) {
char *colon = strchr(input, ':');
if (!colon) {
error_exit("Error: Cannot parse bank+address: \"%s\"\n", input);
}
*colon++ = '\0';
*bank = parse_number(input, 16);
*address = parse_number(colon, 16);
}
void parse_symbols(const char *filename, struct Symbol **symbols) {
FILE *file = xfopen(filename, 'r');
struct Buffer *buffer = buffer_create(1);
enum { SYM_PRE, SYM_VALUE, SYM_SPACE, SYM_NAME } state = SYM_PRE;
int bank = 0;
int address = 0;
for (;;) {
int c = getc(file);
if (c == EOF || c == '\n' || c == '\r' || c == ';' || (state == SYM_NAME && (c == ' ' || c == '\t'))) {
if (state == SYM_NAME) {
// The symbol name has ended; append the buffered symbol
buffer_append(buffer, &(char []){'\0'});
symbol_append(symbols, buffer->data, bank, address);
}
// Skip to the next line, ignoring anything after the symbol value and name
state = SYM_PRE;
while (c != EOF && c != '\n' && c != '\r') {
c = getc(file);
}
if (c == EOF) {
break;
}
} else if (c != ' ' && c != '\t') {
if (state == SYM_PRE || state == SYM_SPACE) {
// The symbol value or name has started; buffer its contents
if (++state == SYM_NAME) {
// The symbol name has started; parse the buffered value
buffer_append(buffer, &(char []){'\0'});
parse_symbol_value(buffer->data, &bank, &address);
}
buffer->size = 0;
}
buffer_append(buffer, &c);
} else if (state == SYM_VALUE) {
// The symbol value has ended; wait to see if a name comes after it
state = SYM_SPACE;
}
}
fclose(file);
buffer_free(buffer);
}
int strfind(const char *s, const char *list[], int count) {
for (int i = 0; i < count; i++) {
if (!strcmp(s, list[i])) {
return i;
}
}
return -1;
}
#define vstrfind(s, ...) strfind(s, (const char *[]){__VA_ARGS__}, COUNTOF((const char *[]){__VA_ARGS__}))
int parse_arg_value(const char *arg, bool absolute, const struct Symbol *symbols, const char *patch_name) {
// Comparison operators for "ConditionValueB" evaluate to their particular values
int op = vstrfind(arg, "==", ">", "<", ">=", "<=", "!=", "||");
if (op >= 0) {
return op == 6 ? 0x11 : op; // "||" is 0x11
}
// Literal numbers evaluate to themselves
if (isdigit((unsigned)arg[0]) || arg[0] == '+') {
return parse_number(arg, 0);
}
// Symbols evaluate to their offset or address, plus an optional offset mod
int offset_mod = 0;
char *plus = strchr(arg, '+');
if (plus) {
offset_mod = parse_number(plus, 0);
*plus = '\0';
}
const char *sym_name = !strcmp(arg, "@") ? patch_name : arg; // "@" is the current patch label
const struct Symbol *symbol = symbol_find(symbols, sym_name);
return (absolute ? symbol->offset : symbol->address) + offset_mod;
}
void interpret_command(char *command, const struct Symbol *current_hook, const struct Symbol *symbols, struct Buffer *patches, FILE *restrict new_rom, FILE *restrict orig_rom, FILE *restrict output) {
// Strip all leading spaces and all but one trailing space
int x = 0;
for (int i = 0; command[i]; i++) {
if (!isspace((unsigned)command[i]) || (i > 0 && !isspace((unsigned)command[i - 1]))) {
command[x++] = command[i];
}
}
command[x - (x > 0 && isspace((unsigned)command[x - 1]))] = '\0';
// Count the arguments
int argc = 0;
for (const char *c = command; *c; c++) {
if (isspace((unsigned)*c)) {
argc++;
}
}
// Get the arguments
char *argv[argc]; // VLA
char *arg = command;
for (int i = 0; i < argc; i++) {
while (*arg && !isspace((unsigned)*arg)) {
arg++;
}
if (!*arg) {
break;
}
*arg++ = '\0';
argv[i] = arg;
}
// Use the arguments
if (vstrfind(command, "patch", "PATCH", "patch_", "PATCH_", "patch/", "PATCH/") >= 0) {
if (argc > 2) {
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
}
if (!current_hook) {
error_exit("Error: No current patch for command: \"%s\"\n", command);
}
int current_offset = current_hook->offset + (argc > 0 ? parse_number(argv[0], 0) : 0);
if (fseek(orig_rom, current_offset, SEEK_SET)) {
error_exit("Error: Cannot seek to \"vc_patch %s\" in the original ROM\n", current_hook->name);
}
if (fseek(new_rom, current_offset, SEEK_SET)) {
error_exit("Error: Cannot seek to \"vc_patch %s\" in the new ROM\n", current_hook->name);
}
int length;
if (argc == 2) {
length = parse_number(argv[1], 0);
} else {
const struct Symbol *current_hook_end = symbol_find_cat(symbols, current_hook->name, "_End");
length = current_hook_end->offset - current_offset;
}
buffer_append(patches, &(struct Patch){current_offset, length});
bool modified = false;
if (length == 1) {
int c = getc(new_rom);
modified = c != getc(orig_rom);
fprintf(output, isupper((unsigned)command[0]) ? "0x%02X" : "0x%02x", c);
} else {
if (command[strlen(command) - 1] != '/') {
fprintf(output, command[strlen(command) - 1] == '_' ? "a%d: " : "a%d:", length);
}
for (int i = 0; i < length; i++) {
if (i) {
putc(' ', output);
}
int c = getc(new_rom);
modified |= c != getc(orig_rom);
fprintf(output, isupper((unsigned)command[0]) ? "%02X" : "%02x", c);
}
}
if (!modified) {
fprintf(stderr, PROGRAM_NAME ": Warning: \"vc_patch %s\" doesn't alter the ROM\n", current_hook->name);
}
} else if (vstrfind(command, "dws", "DWS", "dws_", "DWS_", "dws/", "DWS/") >= 0) {
if (argc < 1) {
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
}
if (command[strlen(command) - 1] != '/') {
fprintf(output, command[strlen(command) - 1] == '_' ? "a%d: " : "a%d:", argc * 2);
}
for (int i = 0; i < argc; i++) {
int value = parse_arg_value(argv[i], false, symbols, current_hook->name);
if (value > 0xffff) {
error_exit("Error: Invalid value for \"%s\" argument: 0x%x\n", command, value);
}
if (i) {
putc(' ', output);
}
fprintf(output, isupper((unsigned)command[0]) ? "%02X %02X": "%02x %02x", value & 0xff, value >> 8);
}
} else if (vstrfind(command, "db", "DB", "db_", "DB_", "db/", "DB/") >= 0) {
if (argc != 1) {
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
}
int value = parse_arg_value(argv[0], false, symbols, current_hook->name);
if (value > 0xff) {
error_exit("Error: Invalid value for \"%s\" argument: 0x%x\n", command, value);
}
if (command[strlen(command) - 1] != '/') {
fputs(command[strlen(command) - 1] == '_' ? "a1: " : "a1:", output);
}
fprintf(output, isupper((unsigned)command[0]) ? "%02X" : "%02x", value);
} else if (vstrfind(command, "hex", "HEX", "HEx", "Hex", "heX", "hEX", "hex~", "HEX~", "HEx~", "Hex~", "heX~", "hEX~") >= 0) {
if (argc != 1 && argc != 2) {
error_exit("Error: Invalid arguments for command: \"%s\"\n", command);
}
int value = parse_arg_value(argv[0], command[strlen(command) - 1] != '~', symbols, current_hook->name);
int padding = argc > 1 ? parse_number(argv[1], 0) : 2;
if (vstrfind(command, "HEx", "HEx~") >= 0) {
fprintf(output, "0x%0*X%02x", padding - 2, value >> 8, value & 0xff);
} else if (vstrfind(command, "Hex", "Hex~") >= 0) {
fprintf(output, "0x%0*X%03x", padding - 3, value >> 12, value & 0xfff);
} else if (vstrfind(command, "heX", "heX~") >= 0) {
fprintf(output, "0x%0*x%02X", padding - 2, value >> 8, value & 0xff);
} else if (vstrfind(command, "hEX", "hEX~") >= 0) {
fprintf(output, "0x%0*x%03X", padding - 3, value >> 12, value & 0xfff);
} else {
fprintf(output, isupper((unsigned)command[0]) ? "0x%0*X" : "0x%0*x", padding, value);
}
} else {
error_exit("Error: Unknown command: \"%s\"\n", command);
}
}
void skip_to_next_line(FILE *restrict input, FILE *restrict output) {
for (int c = getc(input); c != EOF; c = getc(input)) {
putc(c, output);
if (c == '\n' || c == '\r') {
break;
}
}
}
struct Buffer *process_template(const char *template_filename, const char *patch_filename, FILE *restrict new_rom, FILE *restrict orig_rom, const struct Symbol *symbols) {
FILE *input = xfopen(template_filename, 'r');
FILE *output = xfopen(patch_filename, 'w');
struct Buffer *patches = buffer_create(sizeof(struct Patch));
struct Buffer *buffer = buffer_create(1);
// The ROM checksum will always differ
buffer_append(patches, &(struct Patch){0x14e, 2});
// The Stadium data (see stadium.c) will always differ
unsigned int rom_size = (unsigned int)xfsize("", orig_rom);
if (rom_size == 128 * 0x4000) {
unsigned int stadium_size = 24 + 6 + 2 + 128 * 2 * 2;
buffer_append(patches, &(struct Patch){rom_size - stadium_size, stadium_size});
}
// Fill in the template
const struct Symbol *current_hook = NULL;
for (int c = getc(input); c != EOF; c = getc(input)) {
switch (c) {
case ';':
// ";" comments until the end of the line
putc(c, output);
skip_to_next_line(input, output);
break;
case '{':
// "{...}" is a template command; buffer its contents
buffer->size = 0;
for (c = getc(input); c != EOF && c != '}'; c = getc(input)) {
buffer_append(buffer, &c);
}
buffer_append(buffer, &(char []){'\0'});
// Interpret the command in the context of the current patch
interpret_command(buffer->data, current_hook, symbols, patches, new_rom, orig_rom, output);
break;
case '[':
// "[...]" is a patch label; buffer its contents
putc(c, output);
bool alternate = false;
buffer->size = 0;
for (c = getc(input); c != EOF; c = getc(input)) {
if (!alternate && c == '@') {
// "@" designates an alternate name for the ".VC_" label
alternate = true;
buffer->size = 0;
} else if (c == ']') {
putc(c, output);
break;
} else {
if (!alternate) {
putc(c, output);
if (!isalnum(c) && c != '_') {
// Convert non-identifier characters to underscores
c = '_';
}
}
buffer_append(buffer, &c);
}
}
buffer_append(buffer, &(char []){'\0'});
// The current patch should have a corresponding ".VC_" label
current_hook = symbol_find_cat(symbols, ".VC_", buffer->data);
skip_to_next_line(input, output);
break;
default:
putc(c, output);
}
}
rewind(orig_rom);
rewind(new_rom);
fclose(input);
fclose(output);
buffer_free(buffer);
return patches;
}
int compare_patch(const void *patch1, const void *patch2) {
unsigned int offset1 = ((const struct Patch *)patch1)->offset;
unsigned int offset2 = ((const struct Patch *)patch2)->offset;
return offset1 > offset2 ? 1 : offset1 < offset2 ? -1 : 0;
}
bool verify_completeness(FILE *restrict orig_rom, FILE *restrict new_rom, struct Buffer *patches) {
qsort(patches->data, patches->size, patches->item_size, compare_patch);
for (unsigned int offset = 0, index = 0; ; offset++) {
int orig_byte = getc(orig_rom);
int new_byte = getc(new_rom);
if (orig_byte == EOF || new_byte == EOF) {
return orig_byte == new_byte;
}
struct Patch *patch = &((struct Patch *)patches->data)[index];
if (index < patches->size && patch->offset == offset) {
if (fseek(orig_rom, patch->size, SEEK_CUR)) {
return false;
}
if (fseek(new_rom, patch->size, SEEK_CUR)) {
return false;
}
offset += patch->size;
index++;
} else if (orig_byte != new_byte) {
fprintf(stderr, PROGRAM_NAME ": Warning: Unpatched difference at offset: 0x%x\n", offset);
fprintf(stderr, " Original ROM value: 0x%02x\n", orig_byte);
fprintf(stderr, " Patched ROM value: 0x%02x\n", new_byte);
fprintf(stderr, " Current patch offset: 0x%06x\n", patch->offset);
return false;
}
}
}
int main(int argc, char *argv[]) {
if (argc != 7) {
usage_exit(1);
}
struct Symbol *symbols = NULL;
parse_symbols(argv[1], &symbols);
parse_symbols(argv[2], &symbols);
FILE *new_rom = xfopen(argv[3], 'r');
FILE *orig_rom = xfopen(argv[4], 'r');
struct Buffer *patches = process_template(argv[5], argv[6], new_rom, orig_rom, symbols);
if (!verify_completeness(orig_rom, new_rom, patches)) {
fprintf(stderr, PROGRAM_NAME ": Warning: Not all ROM differences are defined by \"%s\"\n", argv[6]);
}
symbol_free(symbols);
fclose(new_rom);
fclose(orig_rom);
buffer_free(patches);
return 0;
}

76
tools/palfix.py Normal file
View file

@ -0,0 +1,76 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Usage: python palfix.py image.png
Fix the palette format of the input image. Colored images (Pokémon or trainer
sprites) will become indexed, with a palette sorted {white, light color, dark
color, black}. Grayscale images will become two-bit grayscale.
"""
import sys
import png
def rgb8_to_rgb5(c):
r, g, b = c
return (r // 8, g // 8, b // 8)
def rgb5_to_rgb8(c):
r, g, b = c
return (r * 8 + r // 4, g * 8 + g // 4, b * 8 + b // 4)
def invert(c):
r, g, b = c
return (31 - r, 31 - g, 31 - b)
def luminance(c):
r, g, b = c
return 0.299 * r**2 + 0.587 * g**2 + 0.114 * b**2
def rgb5_pixels(row):
yield from (rgb8_to_rgb5(row[x:x+3]) for x in range(0, len(row), 4))
def is_grayscale(palette):
return (palette == ((31, 31, 31), (21, 21, 21), (10, 10, 10), (0, 0, 0)) or
palette == ((31, 31, 31), (20, 20, 20), (10, 10, 10), (0, 0, 0)))
def fix_pal(filename):
with open(filename, 'rb') as file:
width, height, rows = png.Reader(file).asRGBA8()[:3]
rows = list(rows)
b_and_w = {(0, 0, 0), (31, 31, 31)}
colors = {c for row in rows for c in rgb5_pixels(row)} - b_and_w
if not colors:
colors = {(21, 21, 21), (10, 10, 10)}
elif len(colors) == 1:
c = colors.pop()
colors = {c, invert(c)}
elif len(colors) != 2:
return False
palette = tuple(sorted(colors | b_and_w, key=luminance, reverse=True))
assert len(palette) == 4
rows = [list(map(palette.index, rgb5_pixels(row))) for row in rows]
if is_grayscale(palette):
rows = [[3 - c for c in row] for row in rows]
writer = png.Writer(width, height, greyscale=True, bitdepth=2, compression=9)
else:
palette = tuple(map(rgb5_to_rgb8, palette))
writer = png.Writer(width, height, palette=palette, bitdepth=8, compression=9)
with open(filename, 'wb') as file:
writer.write(file, rows)
return True
def main():
if len(sys.argv) < 2:
print(f'Usage: {sys.argv[0]} pic.png', file=sys.stderr)
sys.exit(1)
for filename in sys.argv[1:]:
if not filename.lower().endswith('.png'):
print(f'{filename} is not a .png file!', file=sys.stderr)
elif not fix_pal(filename):
print(f'{filename} has too many colors!', file=sys.stderr)
if __name__ == '__main__':
main()

2357
tools/png.py Normal file

File diff suppressed because it is too large Load diff

23
tools/png_dimensions.c Normal file
View file

@ -0,0 +1,23 @@
#define PROGRAM_NAME "png_dimensions"
#define USAGE_OPTS "front.png front.dimensions"
#include "common.h"
uint8_t read_png_dimensions(const char *filename) {
uint32_t width_px = read_png_width(filename);
if (width_px != 40 && width_px != 48 && width_px != 56) {
error_exit("Not a valid width for \"%s\": %" PRIu32 " px\n", filename, width_px);
}
uint8_t width_tiles = (uint8_t)(width_px / 8);
return (width_tiles << 4) | width_tiles;
}
int main(int argc, char *argv[]) {
if (argc < 3) {
usage_exit(1);
}
uint8_t output_byte = read_png_dimensions(argv[1]);
write_u8(argv[2], &output_byte, 1);
return 0;
}

195
tools/pokemon_animation.c Normal file
View file

@ -0,0 +1,195 @@
#define PROGRAM_NAME "pokemon_animation"
#define USAGE_OPTS "[-h|--help] [-b|--bitmasks] [-f|--frames] front.animated.tilemap front.dimensions"
#include "common.h"
struct Options {
bool use_bitmasks;
bool use_frames;
};
void parse_args(int argc, char *argv[], struct Options *options) {
struct option long_options[] = {
{"bitmasks", no_argument, 0, 'b'},
{"frames", no_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{0}
};
for (int opt; (opt = getopt_long(argc, argv, "bfh", long_options)) != -1;) {
switch (opt) {
case 'b':
options->use_bitmasks = true;
break;
case 'f':
options->use_frames = true;
break;
case 'h':
usage_exit(0);
break;
default:
usage_exit(1);
}
}
}
struct Frame {
uint8_t *data;
int size;
int bitmask;
};
struct Frames {
struct Frame *frames;
int num_frames;
};
struct Bitmask {
uint8_t *data;
int bitlength;
};
struct Bitmasks {
struct Bitmask *bitmasks;
int num_bitmasks;
};
int bitmask_exists(const struct Bitmask *bitmask, const struct Bitmasks *bitmasks) {
for (int i = 0; i < bitmasks->num_bitmasks; i++) {
struct Bitmask existing = bitmasks->bitmasks[i];
if (bitmask->bitlength != existing.bitlength) {
continue;
}
bool match = true;
int length = (bitmask->bitlength + 7) / 8;
for (int j = 0; j < length; j++) {
if (bitmask->data[j] != existing.data[j]) {
match = false;
break;
}
}
if (match) {
return i;
}
}
return -1;
}
void make_frames(const uint8_t *tilemap, long tilemap_size, int width, struct Frames *frames, struct Bitmasks *bitmasks) {
int num_tiles_per_frame = width * width;
int num_frames = tilemap_size / num_tiles_per_frame - 1;
frames->frames = xmalloc((sizeof *frames->frames) * num_frames);
frames->num_frames = num_frames;
bitmasks->bitmasks = xmalloc((sizeof *bitmasks->bitmasks) * num_frames);
bitmasks->num_bitmasks = 0;
const uint8_t *first_frame = &tilemap[0];
const uint8_t *this_frame = &tilemap[num_tiles_per_frame];
for (int i = 0; i < num_frames; i++) {
struct Frame *frame = xmalloc(sizeof *frame);
frame->data = xmalloc(num_tiles_per_frame);
frame->size = 0;
struct Bitmask *bitmask = xmalloc(sizeof *bitmask);
bitmask->data = xcalloc((num_tiles_per_frame + 7) / 8);
bitmask->bitlength = 0;
for (int j = 0; j < num_tiles_per_frame; j++) {
if (bitmask->bitlength % 8 == 0) {
bitmask->data[bitmask->bitlength / 8] = 0;
}
bitmask->data[bitmask->bitlength / 8] >>= 1;
if (this_frame[j] != first_frame[j]) {
frame->data[frame->size] = this_frame[j];
frame->size++;
bitmask->data[bitmask->bitlength / 8] |= (1 << 7);
}
bitmask->bitlength++;
}
// tile order ABCDEFGHIJKLMNOP... becomes db order %HGFEDCBA %PONMLKJI ...
int last = bitmask->bitlength - 1;
bitmask->data[last / 8] >>= (7 - (last % 8));
frame->bitmask = bitmask_exists(bitmask, bitmasks);
if (frame->bitmask == -1) {
frame->bitmask = bitmasks->num_bitmasks;
bitmasks->bitmasks[bitmasks->num_bitmasks] = *bitmask;
bitmasks->num_bitmasks++;
} else {
free(bitmask->data);
free(bitmask);
}
frames->frames[i] = *frame;
this_frame += num_tiles_per_frame;
}
}
void print_frames(struct Frames *frames) {
for (int i = 0; i < frames->num_frames; i++) {
printf("\tdw .frame%d\n", i + 1);
}
for (int i = 0; i < frames->num_frames; i++) {
const struct Frame *frame = &frames->frames[i];
printf(".frame%d\n", i + 1);
printf("\tdb $%02x ; bitmask\n", frame->bitmask);
if (frame->size > 0) {
for (int j = 0; j < frame->size; j++) {
if (j % 12 == 0) {
if (j) {
putchar('\n');
}
printf("\tdb $%02x", frame->data[j]);
} else {
printf(", $%02x", frame->data[j]);
}
}
putchar('\n');
}
}
}
void print_bitmasks(const struct Bitmasks *bitmasks) {
for (int i = 0; i < bitmasks->num_bitmasks; i++) {
struct Bitmask bitmask = bitmasks->bitmasks[i];
printf("; %d\n", i);
int length = (bitmask.bitlength + 7) / 8;
for (int j = 0; j < length; j++) {
printf("\tdb %%");
for (int k = 0; k < 8; k++) {
putchar(((bitmask.data[j] >> (7 - k)) & 1) ? '1' : '0');
}
putchar('\n');
}
}
}
int main(int argc, char *argv[]) {
struct Options options = {0};
parse_args(argc, argv, &options);
argc -= optind;
argv += optind;
if (argc < 2) {
usage_exit(1);
}
int width;
read_dimensions(argv[1], &width);
long tilemap_size;
uint8_t *tilemap = read_u8(argv[0], &tilemap_size);
struct Frames frames = {0};
struct Bitmasks bitmasks = {0};
make_frames(tilemap, tilemap_size, width, &frames, &bitmasks);
if (options.use_frames) {
print_frames(&frames);
}
if (options.use_bitmasks) {
print_bitmasks(&bitmasks);
}
free(tilemap);
return 0;
}

View file

@ -0,0 +1,171 @@
#define PROGRAM_NAME "pokemon_animation_graphics"
#define USAGE_OPTS "[-h|--help] [-o|--output front.animated.2bpp] [-t|--tilemap front.animated.tilemap] [--girafarig] front.2bpp front.dimensions"
#include "common.h"
struct Options {
const char *out_filename;
const char *map_filename;
bool girafarig;
};
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);
}
}
}
#define TILE_SIZE 16
void transpose_tiles(uint8_t *tiles, int width, int size) {
uint8_t *new_tiles = xmalloc(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);
free(new_tiles);
}
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) {
if (!memcmp(tile, &tiles[preferred_tile_id * TILE_SIZE], TILE_SIZE)) {
return preferred_tile_id;
}
}
for (int i = 0; i < num_tiles; i++) {
if (!memcmp(tile, &tiles[i * TILE_SIZE], TILE_SIZE)) {
return i;
}
}
return -1;
}
uint8_t *read_tiles(const char *filename, int width, long *tiles_size) {
int frame_size = width * width * TILE_SIZE;
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, width);
}
int num_frames = *tiles_size / frame_size;
for (int i = 0; i < num_frames; i++) {
transpose_tiles(&tiles[i * frame_size], width, frame_size);
}
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;
}
uint8_t *data = xmalloc(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);
}
}
if (girafarig) {
// Add a duplicate of tile 0 to the end
DATA_APPEND_TILES(0, 1);
}
#undef DATA_APPEND_TILES
write_u8(filename, data, num_tiles * TILE_SIZE);
free(data);
}
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 = xmalloc(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;
}
// 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 = data[index];
}
data[i] = tile;
}
write_u8(filename, data, size);
free(data);
}
int main(int argc, char *argv[]) {
struct Options options = {0};
parse_args(argc, argv, &options);
argc -= optind;
argv += optind;
if (argc < 2) {
usage_exit(1);
}
int width;
read_dimensions(argv[1], &width);
long tiles_size;
uint8_t *tiles = read_tiles(argv[0], width, &tiles_size);
if (options.out_filename) {
write_graphics(options.out_filename, tiles, tiles_size, width * width, options.girafarig);
}
if (options.map_filename) {
write_tilemap(options.map_filename, tiles, tiles_size, width * width, options.girafarig);
}
free(tiles);
return 0;
}

38
tools/rgb555.py Normal file
View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Usage: python rgb555.py image.png
Round all colors of the input image to RGB555.
"""
import sys
import png
def rgb8_to_rgb5(c):
return (c & 0b11111000) | (c >> 5)
def round_pal(filename):
with open(filename, 'rb') as file:
width, height, rows = png.Reader(file).asRGBA8()[:3]
rows = list(rows)
for row in rows:
del row[3::4]
rows = [[rgb8_to_rgb5(c) for c in row] for row in rows]
writer = png.Writer(width, height, greyscale=False, bitdepth=8, compression=9)
with open(filename, 'wb') as file:
writer.write(file, rows)
def main():
if len(sys.argv) < 2:
print(f'Usage: {sys.argv[0]} pic.png', file=sys.stderr)
sys.exit(1)
for filename in sys.argv[1:]:
if not filename.lower().endswith('.png'):
print(f'{filename} is not a .png file!', file=sys.stderr)
round_pal(filename)
if __name__ == '__main__':
main()

98
tools/scan_includes.c Normal file
View file

@ -0,0 +1,98 @@
#define PROGRAM_NAME "scan_includes"
#define USAGE_OPTS "[-h|--help] [-s|--strict] filename.asm"
#include "common.h"
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");
if (!f) {
if (strict) {
error_exit("Could not open file \"%s\": %s\n", filename, strerror(errno));
} else {
return;
}
}
long size = xfsize(filename, f);
char *contents = xmalloc(size + 1);
xfread((uint8_t *)contents, size, filename, f);
fclose(f);
contents[size] = '\0';
for (char *ptr = contents; ptr && ptr - contents < size; ptr++) {
bool is_incbin = false, is_include = false;
switch (*ptr) {
case ';':
ptr = strchr(ptr, '\n');
if (!ptr) {
fprintf(stderr, "%s: no newline at end of file\n", filename);
}
break;
case '"':
ptr++;
ptr = strchr(ptr, '"');
if (ptr) {
ptr++;
} else {
fprintf(stderr, "%s: unterminated string\n", filename);
}
break;
case 'I':
case 'i':
is_incbin = !strncmp(ptr, "INCBIN", 6) || !strncmp(ptr, "incbin", 6);
is_include = !strncmp(ptr, "INCLUDE", 7) || !strncmp(ptr, "include", 7);
if (is_incbin || is_include) {
ptr = strchr(ptr, '"');
if (ptr) {
ptr++;
char *include_path = ptr;
size_t length = strcspn(ptr, "\"");
ptr += length + 1;
include_path[length] = '\0';
printf("%s ", include_path);
if (is_include) {
scan_file(include_path, strict);
}
}
}
break;
}
}
free(contents);
}
int main(int argc, char *argv[]) {
bool strict = false;
parse_args(argc, argv, &strict);
argc -= optind;
argv += optind;
if (argc < 1) {
usage_exit(1);
}
scan_file(argv[0], strict);
return 0;
}

203
tools/stadium.c Normal file
View file

@ -0,0 +1,203 @@
#define PROGRAM_NAME "stadium"
#define USAGE_OPTS "[-h|--help] [-e|--european] pokecrystal.gbc"
#include "common.h"
void parse_args(int argc, char *argv[], bool *european) {
struct option long_options[] = {
{"european", no_argument, 0, 'e'},
{"help", no_argument, 0, 'h'},
{0}
};
for (int opt; (opt = getopt_long(argc, argv, "eh", long_options)) != -1;) {
switch (opt) {
case 'e':
*european = true;
break;
case 'h':
usage_exit(0);
break;
default:
usage_exit(1);
}
}
}
// A matching ROM has 128 banks
#define NUM_BANKS 128
// ROM banks are 0x4000 bytes
#define BANK_SIZE 0x4000
// A matching ROM is 2 MB
#define ROM_SIZE (NUM_BANKS * BANK_SIZE)
// The Game Boy cartridge header stores a global checksum at 0x014E-0x014F
#define GLOBAL_OFF 0x014E
// ASCII "base" header + 2-byte version (Crystal only)
uint8_t base10[6] = {'b', 'a', 's', 'e', 1, 0};
// "base" header + version + 2-byte CRC
#define BASE_HEADER_SIZE (COUNTOF(base10) + 2)
// bit flags per bank indicating if it matches the base ROM's bank
#define BASE_DATA_SIZE (NUM_BANKS / 8)
// "base" header + version + CRC + bank bit flags
#define BASE_TOTAL_SIZE (BASE_HEADER_SIZE + BASE_DATA_SIZE)
// The base data is stored before the Stadium data
#define BASE_OFF (N64PS3_OFF - BASE_TOTAL_SIZE)
// ASCII "N64PS3" header (Stadium G/S was the third Japanese Stadium release for N64)
uint8_t n64ps3[6] = {'N', '6', '4', 'P', 'S', '3'};
// "N64PS3" header + 2-byte CRC
#define N64PS3_HEADER_SIZE (COUNTOF(n64ps3) + 2)
// 2-byte checksums per half-bank
#define N64PS3_DATA_SIZE (NUM_BANKS * 2 * 2)
// "N64PS3" header + CRC + half-bank checksums
#define N64PS3_TOTAL_SIZE (N64PS3_HEADER_SIZE + N64PS3_DATA_SIZE)
// The Stadium data is stored at the end of the ROM
#define N64PS3_OFF (ROM_SIZE - N64PS3_TOTAL_SIZE)
// The CRC polynomial value
#define CRC_POLY 0xC387
// The CRC initial value (also used for checksums)
#define CRC_INIT 0xFEFE
// The CRC initial value for Crystal base data
#define CRC_INIT_BASE 0xACDE
// The CRC lookup table
uint16_t crc_table[256];
#define SET_U16BE(file, v) do { \
(file)[0] = (uint8_t)(((v) & 0xFF00) >> 8); \
(file)[1] = (uint8_t)(((v) & 0x00FF) >> 0); \
} while (0)
// CRCs of every bank in the base ROM, crystal_base0.bin
uint16_t base0_crcs[NUM_BANKS] = {
0x9650, 0x8039, 0x2D8F, 0xD75A, 0xAC50, 0x5D55, 0xE94B, 0x9886,
0x2A46, 0xB5AC, 0xC3D3, 0x79C4, 0xCE55, 0xA95E, 0xEF78, 0x9B50,
0x82BA, 0x8716, 0x5895, 0xAD33, 0x4EF0, 0xE434, 0xC521, 0xBFB1,
0xB352, 0xA497, 0xCA84, 0xD3F5, 0x3C79, 0xB61A, 0xAE1B, 0xF314,
0x00B3, 0x7C0A, 0x1018, 0x7F6B, 0x1CFF, 0x15AF, 0x4078, 0xE473,
0x081C, 0x4B9D, 0x2FFC, 0xD9D0, 0x2CBA, 0xCD8C, 0x004C, 0x773C,
0xF040, 0x3585, 0xF924, 0x6FD5, 0xC5E4, 0xD918, 0x1228, 0x1C86,
0x21C0, 0x77F3, 0x6206, 0x0110, 0x152F, 0x0F74, 0xCEDF, 0xBBFE,
0xE382, 0x5C15, 0xFD4D, 0x954C, 0xD2D9, 0xCA2F, 0x14B1, 0x9D2F,
0x172C, 0xEA0C, 0x4EAD, 0x604B, 0x0659, 0xF4C5, 0x4168, 0xD151,
0x58C7, 0x99BF, 0x77D3, 0xCDEC, 0x61B5, 0x1A48, 0xD614, 0x7FB0,
0x91D5, 0x812A, 0x812A, 0x82B2, 0xDCE2, 0x9067, 0x6DB3, 0x3DC7,
0xDCB8, 0xA1CE, 0x9C21, 0x4A23, 0xB50F, 0x63E6, 0xE78A, 0x9238,
0x644D, 0x1BD6, 0xB5B6, 0x1AB9, 0x9D07, 0xC849, 0x6992, 0x10CA,
0x4453, 0xA3A1, 0x5A18, 0xAFE0, 0x7F2B, 0xFC38, 0xFC38, 0xBA98,
0x5AEB, 0xFC38, 0xFC38, 0xFC38, 0xFC38, 0xEFAD, 0x6D83, 0xFC38
};
// CRCs of every bank in the European base ROM, crystal_base1.bin
uint16_t base1_crcs[NUM_BANKS] = {
0x5416, 0xFD37, 0xC4A4, 0xBC37, 0x9458, 0xB489, 0xE94B, 0x9906,
0x2A46, 0xDEA9, 0x17F4, 0xF447, 0xCE55, 0xD843, 0xC5B2, 0xAE13,
0x4E91, 0x3984, 0xD984, 0xD02F, 0x77B8, 0x4D8D, 0x1F8C, 0x7185,
0xBA34, 0xA497, 0xE813, 0xFF97, 0x245E, 0xB61A, 0xCEF0, 0x8BF4,
0xA786, 0x4CE5, 0xA9B8, 0x1988, 0xEF53, 0x2A24, 0x4588, 0x6084,
0x2609, 0x4B9D, 0x8C33, 0xD9D0, 0x2CBA, 0xCD8C, 0xDA4F, 0xE020,
0xF040, 0x3585, 0x2B21, 0xAEEA, 0xC5E4, 0xD918, 0x1228, 0x1C86,
0x78B3, 0xF4B1, 0x7577, 0x0110, 0x152F, 0x0F74, 0xCCDD, 0x3444,
0x58A8, 0x1FB0, 0xDACE, 0x954C, 0xD2D9, 0xF7CB, 0xEE99, 0xA5F0,
0x172C, 0xEA0C, 0x4EAD, 0x604B, 0x0659, 0xF4C5, 0x4168, 0xD151,
0x58C7, 0x99BF, 0x77D3, 0xCDEC, 0x61B5, 0x1A48, 0xD614, 0x7FB0,
0x91D5, 0x812A, 0x812A, 0x82B2, 0x5C2C, 0x91E6, 0x79C5, 0xF2BF,
0xDCB8, 0xA1CE, 0x9C21, 0x579B, 0x4B59, 0x21F5, 0xB2B6, 0x58AD,
0xC91D, 0xB96F, 0x4DCE, 0xBA03, 0x9D07, 0x7A7E, 0xC77E, 0xB8AA,
0xF7E4, 0xA7A4, 0x22E8, 0xAFE0, 0xE0C8, 0xFC38, 0xFC38, 0x2277,
0x5AEB, 0xFC38, 0xFC38, 0x4314, 0x25B0, 0xCE7B, 0x12FA, 0xDD05
};
uint16_t calculate_checksum(uint16_t checksum, uint8_t *file, size_t size) {
for (size_t i = 0; i < size; i++) {
checksum += file[i];
}
return checksum;
}
uint16_t calculate_crc(uint16_t crc, uint8_t *file, size_t size) {
for (size_t i = 0; i < size; i++) {
crc = (crc >> 8) ^ crc_table[(crc & 0xFF) ^ file[i]];
}
return crc;
}
void calculate_checksums(uint8_t *file, bool european) {
// Initialize the CRC table
for (uint16_t i = 0; i < COUNTOF(crc_table); i++) {
uint16_t rem = 0;
for (uint16_t y = 0, c = i; y < 8; y++, c >>= 1) {
rem = (rem >> 1) ^ ((rem ^ c) & 1 ? CRC_POLY : 0);
}
crc_table[i] = rem;
}
// Clear the global checksum
SET_U16BE(file + GLOBAL_OFF, 0);
// Initialize the base data (this should be free space anyway)
memset(file + BASE_OFF, 0, BASE_TOTAL_SIZE);
memcpy(file + BASE_OFF, base10, COUNTOF(base10));
// Use the appropriate base CRCs
uint16_t *base_crcs = base0_crcs;
if (european) {
base_crcs = base1_crcs;
file[BASE_OFF + COUNTOF(base10) - 1] = 1;
}
// Calculate the base data bits using bank CRCs
// Bits indicate if the bank CRC matches the base one
for (size_t i = 0; i < BASE_DATA_SIZE; i++) {
uint8_t bits = 0;
for (size_t j = 0; j < 8; j++) {
size_t bank = i * 8 + j;
uint16_t crc = calculate_crc(CRC_INIT, file + bank * BANK_SIZE, BANK_SIZE);
bits |= (crc == base_crcs[bank]) << j;
}
file[BASE_OFF + BASE_HEADER_SIZE + i] = bits;
}
// Calculate the CRC of the base data
uint16_t crc = calculate_crc(CRC_INIT_BASE, file + BASE_OFF, BASE_TOTAL_SIZE);
SET_U16BE(file + BASE_OFF + BASE_HEADER_SIZE - 2, crc);
// Initialize the Stadium data (this should be free space anyway)
memset(file + N64PS3_OFF, 0, N64PS3_TOTAL_SIZE);
memcpy(file + N64PS3_OFF, n64ps3, COUNTOF(n64ps3));
// Calculate the half-bank checksums
for (size_t i = 0; i < NUM_BANKS * 2; i++) {
uint16_t checksum = calculate_checksum(CRC_INIT, file + i * BANK_SIZE / 2, BANK_SIZE / 2);
SET_U16BE(file + N64PS3_OFF + N64PS3_HEADER_SIZE + i * 2, checksum);
}
// Calculate the CRC of the half-bank checksums
crc = calculate_crc(CRC_INIT, file + N64PS3_OFF + N64PS3_HEADER_SIZE, N64PS3_DATA_SIZE);
SET_U16BE(file + N64PS3_OFF + N64PS3_HEADER_SIZE - 2, crc);
// Calculate the global checksum
uint16_t globalsum = calculate_checksum(0, file, ROM_SIZE);
SET_U16BE(file + GLOBAL_OFF, globalsum);
}
int main(int argc, char *argv[]) {
bool european = false;
parse_args(argc, argv, &european);
argc -= optind;
argv += optind;
if (argc < 1) {
usage_exit(1);
}
char *filename = argv[0];
long filesize;
uint8_t *file = read_u8(filename, &filesize);
if (filesize == ROM_SIZE) {
calculate_checksums(file, european);
}
write_u8(filename, file, filesize);
return 0;
}

52
tools/sym_comments.py Normal file
View file

@ -0,0 +1,52 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Usage: python sym_comments.py file.asm [pokecrystal.sym] > file_commented.asm
Comments each label in file.asm with its bank:address from the sym file.
"""
import sys
import re
def main():
if len(sys.argv) not in {2, 3}:
print(f'Usage: {sys.argv[0]} file.asm [pokecrystal.sym] > file_commented.asm', file=sys.stderr)
sys.exit(1)
wram_name = sys.argv[1]
sym_name = sys.argv[2] if len(sys.argv) == 3 else 'pokecrystal.sym'
sym_def_rx = re.compile(r'(^{sym})(:.*)|(^\.{sym})(.*)'.format(sym=r'[A-Za-z_][A-Za-z0-9_#@]*'))
sym_addrs = {}
with open(sym_name, 'r', encoding='utf-8') as file:
for line in file:
line = line.split(';', 1)[0].rstrip()
parts = line.split(' ', 1)
if len(parts) != 2:
continue
addr, sym = parts
sym_addrs[sym] = addr
with open(wram_name, 'r', encoding='utf-8') as file:
cur_label = None
for line in file:
line = line.rstrip()
if (m = re.match(sym_def_rx, line)):
sym, rest = m.group(1), m.group(2)
if sym is None and rest is None:
sym, rest = m.group(3), m.group(4)
key = sym
if not sym.startswith('.'):
cur_label = sym
elif cur_label:
key = cur_label + sym
if key in sym_addrs:
addr = sym_addrs[key]
line = sym + rest + ' ; ' + addr
print(line)
if __name__ == '__main__':
main()

106
tools/unique.py Normal file
View file

@ -0,0 +1,106 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Usage: python unique.py [-f|--flip] [-x|--cross] image.png
Erase duplicate tiles from an input image.
-f or --flip counts X/Y mirrored tiles as duplicates.
-x or --cross erases with a cross instead of a blank tile.
"""
import sys
import png
def rgb5_pixels(row):
yield from (tuple(c // 8 for c in row[x:x+3]) for x in range(0, len(row), 4))
def rgb8_pixels(row):
yield from (c * 8 + c // 4 for pixel in row for c in pixel)
def gray_pixels(row):
yield from (pixel[0] // 10 for pixel in row)
def rows_to_tiles(rows, width, height):
assert len(rows) == height and len(rows[0]) == width
yield from (tuple(tuple(row[x:x+8]) for row in rows[y:y+8])
for y in range(0, height, 8) for x in range(0, width, 8))
def tiles_to_rows(tiles, width, height):
assert width % 8 == 0 and height % 8 == 0
width, height = width // 8, height // 8
tiles = list(tiles)
assert len(tiles) == width * height
tile_rows = (tiles[y:y+width] for y in range(0, width * height, width))
yield from ([tile[y][x] for tile in tile_row for x in range(8)]
for tile_row in tile_rows for y in range(8))
def tile_variants(tile, flip):
yield tile
if flip:
yield tile[::-1]
yield tuple(row[::-1] for row in tile)
yield tuple(row[::-1] for row in tile[::-1])
def unique_tiles(tiles, flip, cross):
if cross:
blank = [[(0, 0, 0)] * 8 for _ in range(8)]
for y in range(8):
blank[y][y] = blank[y][7 - y] = (31, 31, 31)
blank = tuple(tuple(row) for row in blank)
else:
blank = tuple(tuple([(31, 31, 31)] * 8) for _ in range(8))
seen = set()
for tile in tiles:
if any(variant in seen for variant in tile_variants(tile, flip)):
yield blank
else:
yield tile
seen.add(tile)
def is_grayscale(colors):
return (colors.issubset({(31, 31, 31), (21, 21, 21), (10, 10, 10), (0, 0, 0)}) or
colors.issubset({(31, 31, 31), (20, 20, 20), (10, 10, 10), (0, 0, 0)}))
def erase_duplicates(filename, flip, cross):
with open(filename, 'rb') as file:
width, height, rows = png.Reader(file).asRGBA8()[:3]
rows = [list(rgb5_pixels(row)) for row in rows]
if width % 8 or height % 8:
return False
tiles = unique_tiles(rows_to_tiles(rows, width, height), flip, cross)
rows = list(tiles_to_rows(tiles, width, height))
if is_grayscale({c for row in rows for c in row}):
rows = [list(gray_pixels(row)) for row in rows]
writer = png.Writer(width, height, greyscale=True, bitdepth=2, compression=9)
else:
rows = [list(rgb8_pixels(row)) for row in rows]
writer = png.Writer(width, height, greyscale=False, bitdepth=8, compression=9)
with open(filename, 'wb') as file:
writer.write(file, rows)
return True
def main():
flip = cross = False
while True:
if len(sys.argv) < 2:
print(f'Usage: {sys.argv[0]} [-f|--flip] [-x|--cross] tileset.png', file=sys.stderr)
sys.exit(1)
elif sys.argv[1] in {'-f', '--flip'}:
flip = True
elif sys.argv[1] in {'-x', '--cross'}:
cross = True
elif sys.argv[1] in {'-fx', '-xf'}:
flip = cross = True
else:
break
sys.argv.pop(1)
for filename in sys.argv[1:]:
if not filename.lower().endswith('.png'):
print(f'{filename} is not a .png file!', file=sys.stderr)
elif not erase_duplicates(filename, flip, cross):
print(f'{filename} is not divisible into 8x8 tiles!', file=sys.stderr)
if __name__ == '__main__':
main()