mirror of
https://github.com/thornAvery/jep-hack.git
synced 2026-02-06 16:15:24 +13:00
First Commit
Upload literally everything from the pokecrystal16 expand-move-ID branch
This commit is contained in:
commit
2f8a41f833
4618 changed files with 480386 additions and 0 deletions
10
tools/.gitignore
vendored
Normal file
10
tools/.gitignore
vendored
Normal 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
38
tools/Makefile
Normal 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
240
tools/bpp2png.c
Normal 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
151
tools/common.h
Normal 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
56
tools/consts.py
Normal 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
78
tools/free_space.awk
Normal 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
300
tools/gfx.c
Normal 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
6497
tools/lodepng/lodepng.c
Normal file
File diff suppressed because it is too large
Load diff
2019
tools/lodepng/lodepng.h
Normal file
2019
tools/lodepng/lodepng.h
Normal file
File diff suppressed because it is too large
Load diff
35
tools/lz/global.c
Normal file
35
tools/lz/global.c
Normal 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
54
tools/lz/main.c
Normal 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
102
tools/lz/merging.c
Normal 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
112
tools/lz/mpcomp.c
Normal 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
20
tools/lz/nullcomp.c
Normal 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
141
tools/lz/options.c
Normal 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
146
tools/lz/output.c
Normal 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
56
tools/lz/packing.c
Normal 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
107
tools/lz/proto.h
Normal 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
63
tools/lz/repcomp.c
Normal 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
141
tools/lz/spcomp.c
Normal 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
92
tools/lz/uncomp.c
Normal 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
54
tools/lz/util.c
Normal 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
471
tools/make_patch.c
Normal 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
76
tools/palfix.py
Normal 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
2357
tools/png.py
Normal file
File diff suppressed because it is too large
Load diff
23
tools/png_dimensions.c
Normal file
23
tools/png_dimensions.c
Normal 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
195
tools/pokemon_animation.c
Normal 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;
|
||||
}
|
||||
171
tools/pokemon_animation_graphics.c
Normal file
171
tools/pokemon_animation_graphics.c
Normal 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
38
tools/rgb555.py
Normal 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
98
tools/scan_includes.c
Normal 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
203
tools/stadium.c
Normal 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
52
tools/sym_comments.py
Normal 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
106
tools/unique.py
Normal 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue