Trim the SGB tilemap (claiming back nearly 1kB of space) - pret/pokered GitHub Wiki

The SGB tilemap is a large 32 * 32 area of 16-bit tile data. That's a whole 2kB chunk that sits on the ROM. It turns out that a large portion of it is filled with zeros: the GB screen area in the center, and the last 256 bottom bytes. By using the same SGB load function as in Gen2, it's possible to trim all this blank data, and add the zeros on the fly.

Contents

  1. Edit SGB copy functions
  2. Add a tilemap trimming tool
  3. Trim the SGB tilemap
  4. (Optional) Trim the border palettes

1. Edit SGB copy functions

First edit engine/gfx/palettes.asm. We want to add a new check for loading the SGB tilemap to VRAM, besides the existing ones (plain copy, and border Gfx copy).

LoadSGB:
	xor a
	ld [wOnSGB], a
	call CheckSGB
	ret nc
	ld a, 1
	ld [wOnSGB], a
	ld a, [wOnCGB]
	and a
	jr z, .notCGB
	ret
.notCGB
	di
	call PrepareSuperNintendoVRAMTransfer
	ei
	ld a, 1
	ld [wCopyingSGBTileData], a
	ld de, ChrTrnPacket
	ld hl, SGBBorderGraphics
	call CopyGfxToSuperNintendoVRAM
-	xor a
+	ld a, 2
	ld [wCopyingSGBTileData], a
CopyGfxToSuperNintendoVRAM:
	di
	push de
	call DisableLCD
	ld a, $e4
	ldh [rBGP], a
	ld de, vChars1
	ld a, [wCopyingSGBTileData]
	and a
-	jr z, .notCopyingTileData
+	jr z, .standardCopy
+	dec a
+	jr z, .borderTiles
+ ; tilemap
+	call CopySGBTilemapPals
+	jr .next
+ .borderTiles
	call CopySGBBorderTiles
	jr .next
- .notCopyingTileData
+ .standardCopy
	ld bc, 256 tiles
	call CopyData
.next

Insert these two functions anywhere in this file (I chose to add it after CopySGBBorderTiles).

CopySGBTilemapPals:
	ld bc, (6 + SCREEN_WIDTH + 6) * 5 * 2
	call CopyData
	ld b, SCREEN_HEIGHT
.loop
	push bc
	ld bc, 6 * 2
	call CopyData
	ld bc, SCREEN_WIDTH * 2
	call ClearBytes
	ld bc, 6 * 2
	call CopyData
	pop bc
	dec b
	jr nz, .loop
	ld bc, (6 + SCREEN_WIDTH + 6) * 5 * 2
	call CopyData
	ld bc, $100
	call ClearBytes
	ld bc, 3 * 16 * COLOR_SIZE ; 3 SNES palettes
	call CopyData
	ret

ClearBytes::
; clear bc bytes of data starting from de
.loop
	xor a
	ld [de], a
	inc de
	dec bc
	ld a, c
	or b
	jr nz, .loop
	ret

2. Add a tilemap trimming tool

Now we'll automate the cropping of the GB screen area within the tilemap file. Create a new file named trim_sgb_tilemap.c in tools/, and paste the following lines.

#define PROGRAM_NAME "trim_sgb_tilemap"
#define USAGE_OPTS "[-h|--help] [-r|--revert] infile.tilemap outfile.tilemap"

#include "common.h"

void parse_args(int argc, char *argv[], bool *revert) {
    struct option long_options[] = {
        {"revert", no_argument, 0, 'r'},
        {"help", no_argument, 0, 'h'},
        {0}
    };
    for (int opt; (opt = getopt_long(argc, argv, "rh", long_options)) != -1;) {
        switch (opt) {
            case 'r':
                *revert = true;
                break;
            case 'h':
                usage_exit(0);
                break;
            default:
                usage_exit(1);
        }
    }
}

uint8_t output[0x700];
int frame_start = (5 * 32 + 6) * 2;

void trim_tilemap(const uint8_t *data, long filesize) {
    int pos = 0;
    int frame_end = (22 * 32 + 26) * 2;

    for (int i = 0; i < frame_start; i++) {
        output[pos++] = data[i];
    }

    for (int row = 0; row < 17; row++) {
        for (int  i = 0; i < 12 * 2; i++) {
            output[pos++] = data[frame_start + 20 * 2 + row * 32 * 2 + i];
        }
    }

    for (int i = frame_end; i < filesize; i++) {
        output[pos++] = data[i];
    }
}

void expand_tilemap(const uint8_t *data, long filesize) {
    int pos = 0;
    int frame_end = frame_start + 18 * 12 * 2;

    for (int i = 0; i < frame_start; i++) {
        output[pos++] = data[i];
    }

    for (int row = 0; row < 18; row++) {
        for (int i = 0; i < 20 * 2; i++) {
            output[pos++] = 0;
        }
        for (int  i = 0; i < 12 * 2; i++) {
            output[pos++] = data[frame_start + row * 12 * 2 + i];
        }
    }

    for (int i = frame_end; i < filesize; i++) {
        output[pos++] = data[i];
    }
}

int main(int argc, char *argv[]) {
    bool revert = false;
    parse_args(argc, argv, &revert);

    argc -= optind;
    argv += optind;
    if (argc < 1) {
        usage_exit(1);
    }

    int in_size  = revert ? 0x430 : 0x700;

    long filesize;
    uint8_t *data = read_u8(argv[0], &filesize);
    if (filesize != in_size) {
        error_exit("Input SGB tilemap has wrong size.");
    }

    if (revert) {
        expand_tilemap(data, filesize);
    } else {
        trim_tilemap(data, filesize);
    }
    write_u8(argv[1], output, revert ? 0x700 : 0x430);

    free(data);
    return 0;
}

Edit tools/Makefile to add this file to build:

tools := \
	gfx \
	make_patch \
	pkmncompress \
+	trim_sgb_tilemap \
	scan_includes

Add a line to tools/.gitignore.

gfx
make_patch
pkmncompress
+ trim_sgb_tilemap 
scan_includes

The tool has a --reverse option that can be used to restore a full 32 * 32 tilemap from a cropped one, if needed.

3. Trim the SGB tilemap

Edit data/sgb/sgb_border.asm:

BorderPalettes:
IF DEF(_RED)
-	INCBIN "gfx/sgb/red_border.tilemap"
+	INCBIN "gfx/sgb/red_border.sgb.tilemap"
ENDC
IF DEF(_BLUE)
-	INCBIN "gfx/sgb/blue_border.tilemap"
+	INCBIN "gfx/sgb/blue_border.sgb.tilemap"
ENDC

-	ds $100

Finally, edit Makefile:

clean: tidy
	find gfx \
	     \( -iname '*.1bpp' \
	        -o -iname '*.2bpp' \
-	        -o -iname '*.pic' \) \
+	        -o -iname '*.pic' \
+           -o -iname '*.sgb.tilemap' \) \
	     -delete
%.pic: %.2bpp
	tools/pkmncompress $< $@

+ %.sgb.tilemap: %.tilemap
+	tools/trim_sgb_tilemap $< $@


### File extensions that are never generated and should be manually created

That's it, the net decrease is about 900 bytes.

4. (Optional) Trim the border palettes

If you have kept the original borders (or if your own custom borders still use only 4 colors in each border palette), you can trim all the unused color slots, and pad with zeros during function call. Edit the previous CopySGBTilemapPals function:

CopySGBTilemapPals:
	ld bc, (6 + SCREEN_WIDTH + 6) * 5 * 2
	call CopyData
	ld b, SCREEN_HEIGHT
.loop
	push bc
	ld bc, 6 * 2
	call CopyData
	ld bc, SCREEN_WIDTH * 2
	call ClearBytes
	ld bc, 6 * 2
	call CopyData
	pop bc
	dec b
	jr nz, .loop
	ld bc, (6 + SCREEN_WIDTH + 6) * 5 * 2
	call CopyData
	ld bc, $100
	call ClearBytes
-	ld bc, 3 * 16 * COLOR_SIZE ; 3 SNES palettes
-	call CopyData
+	ld c, 3
+ .pal_loop
+	push bc
+	ld bc, 4 * COLOR_SIZE
+	call CopyData
+	ld bc, 12 * COLOR_SIZE
+	call ClearBytes
+	pop bc
+	dec c
+	jr nz, .pal_loop
	ret

Edit data/sgb/sgb_border.asm:

IF DEF(_RED)
	RGB 30,29,29 ; PAL_SGB1
	RGB 25,22,25
	RGB 25,17,21
	RGB 24,14,12
ENDC
IF DEF(_BLUE)
	RGB 0,0,0 ; PAL_SGB1 (the first color is not defined, but if used, turns up as 30,29,29... o_O)
	RGB 10,17,26
	RGB 5,9,20
	RGB 16,20,27
ENDC

-	ds $18

IF DEF(_RED)
	RGB 30,29,29 ; PAL_SGB2
	RGB 22,31,16
	RGB 27,20,6
	RGB 15,15,15
ENDC
IF DEF(_BLUE)
	RGB 30,29,29 ; PAL_SGB2
	RGB 27,11,6
	RGB 5,9,20
	RGB 28,25,15
ENDC

-	ds $18

IF DEF(_RED)
	RGB 30,29,29 ; PAL_SGB3
	RGB 31,31,17
	RGB 18,21,29
	RGB 15,15,15
ENDC
IF DEF(_BLUE)
	RGB 30,29,29 ; PAL_SGB3
	RGB 12,15,11
	RGB 5,9,20
	RGB 14,22,17
ENDC

-	ds $18

Another 36 bytes trimmed, though this time the gain is minimal.