--- /dev/null
+*.o
+*.d
+*.swp
+*.png
+data.inc
+*.sfc
+img2snes/img2snes
+link.map
--- /dev/null
+test.sfc: test.o
+ ld65 -o $@ -m link.map -C snes.ld $<
+
+test.o: test.asm hwregs.inc data.inc
+
+%.o: %.asm
+ ca65 -o $@ --cpu 65816 $<
+
+data.inc: logo4bpp.png img2snes/img2snes
+ img2snes/img2snes -o data.inc logo4bpp.png
+
+img2snes/img2snes:
+ $(MAKE) -C img2snes
+
+.PHONY: clean
+clean:
+ rm -f test.sfc test.o
--- /dev/null
+ ; PPU write-only
+ REG_INIDISP = $2100
+ REG_OBSEL = $2101
+ REG_OAMADDL = $2102
+ REG_OAMADDH = $2103
+ REG_OAMDATA = $2104
+ REG_BGMODE = $2105
+ REG_MOSAIC = $2106
+ REG_BG1SC = $2107
+ REG_BG2SC = $2108
+ REG_BG3SC = $2109
+ REG_BG4SC = $210a
+ REG_BG12NBA = $210b
+ REG_BG34NBA = $210c
+ REG_BG1HOFFS = $210d
+ REG_BG1VOFFS = $210e
+ REG_BG2HOFFS = $210f
+ REG_BG2VOFFS = $2110
+ REG_BG3HOFFS = $2111
+ REG_BG3VOFFS = $2112
+ REG_BG4HOFFS = $2113
+ REG_BG4VOFFS = $2114
+ REG_VMAINC = $2115
+ REG_VMADDL = $2116
+ REG_VMADDH = $2117
+ REG_VMDATAL = $2118
+ REG_VMDATAH = $2119
+ REG_M7SEL = $211a
+ REG_M7A = $211b
+ REG_M7B = $211c
+ REG_M7C = $211d
+ REG_M7D = $211e
+ REG_M7X = $211f
+ REG_M7Y = $2120
+ REG_CGADD = $2121
+ REG_CGDATA = $2122
+ REG_W12SEL = $2123
+ REG_W34SEL = $2124
+ REG_WOBJSEL = $2125
+ REG_WH0 = $2126
+ REG_WH1 = $2127
+ REG_WH2 = $2128
+ REG_WH3 = $2129
+ REG_WBGLOG = $212a
+ REG_WOBJLOG = $212b
+ REG_TM = $212c
+ REG_TS = $212d
+ REG_TMW = $212e
+ REG_TSW = $212f
+ REG_CGWSEL = $2130
+ REG_CGADSUB = $2131
+ REG_COLDATA = $2132
+ REG_SETINI = $2133
+ ; PPU read-only
+ REG_MPYL = $2134
+ REG_MPYM = $2135
+ REG_MPYH = $2136
+ REG_SLHV = $2137
+ REG_RDOAM = $2138
+ REG_RDVRAML = $2139
+ REG_RDVRAMH = $213a
+ REG_RDCGRAM = $213b
+ REG_OPHCT = $213c
+ REG_OPVCT = $213d
+ REG_STAT77 = $213e
+ REG_STAT78 = $213f
+
+ REG_NMITIMEN = $4200
+ REG_WRIO = $4201
+ REG_WRMPYA = $4202
+ REG_WRMPYB = $4203
+ REG_WRDIVL = $4204
+ REG_WRDIVH = $4205
+ REG_WRDIVB = $4206
+ REG_HTIMEL = $4207
+ REG_HTIMEH = $4208
+ REG_VTIMEL = $4209
+ REG_VTIMEH = $420a
+ REG_MEMSEL = $420d
+
+ ; DMA
+ REG_MDMAEN = $420b
+ REG_HDMAEN = $420c
+
+ REG_DMAP_BASE = $4300
+ REG_BBAD_BASE = $4301
+ REG_A1TL_BASE = $4302
+ REG_A1TH_BASE = $4303
+ REG_A1B_BASE = $4304
+ REG_DASL_BASE = $4305
+ REG_DASH_BASE = $4306
+ REG_DASB_BASE = $4307
+ REG_A2AL_BASE = $4308
+ REG_A2AH_BASE = $4309
+ REG_NTRL_BASE = $430a
+
+ .define REG_DMAP(n) (REG_DMAP_BASE | (n << 4))
+ .define REG_BBAD(n) (REG_BBAD_BASE | (n << 4))
+ .define REG_A1TL(n) (REG_A1TL_BASE | (n << 4))
+ .define REG_A1TH(n) (REG_A1TH_BASE | (n << 4))
+ .define REG_A1B(n) (REG_A1B_BASE | (n << 4))
+ .define REG_DASL(n) (REG_DASL_BASE | (n << 4))
+ .define REG_DASH(n) (REG_DASH_BASE | (n << 4))
+ .define REG_DASB(n) (REG_DASB_BASE | (n << 4))
+ .define REG_A2AL(n) (REG_A2AL_BASE | (n << 4))
+ .define REG_A2AH(n) (REG_A2AH_BASE | (n << 4))
+ .define REG_NTRL(n) (REG_NTRL_BASE | (n << 4))
+
+
+ .macro setreg reg, val
+ lda #val
+ sta reg
+ .endmacro
+
+ .macro fblank onoff
+ lda #($0f | (onoff << 7))
+ sta REG_INIDISP
+ .endmacro
+
+ .macro setpal idx, r, g, b
+ setreg REG_CGADD, idx
+ setreg REG_CGDATA, (r | g << 5)
+ setreg REG_CGDATA, (g >> 3 | b << 2)
+ .endmacro
+
--- /dev/null
+PREFIX = /usr/local
+
+src = $(wildcard src/*.c)
+obj = $(src:.c=.o)
+bin = img2snes
+
+CFLAGS = -pedantic -Wall -g
+LDFLAGS = -lpng -lz
+
+$(bin): $(obj)
+ $(CC) -o $@ $(obj) $(LDFLAGS)
+
+.PHONY: clean
+clean:
+ rm -f $(obj) $(bin)
+
+.PHONY: install
+install: $(bin)
+ mkdir -p $(DESTDIR)$(PREFIX)/bin
+ cp $(bin) $(DESTDIR)$(PREFIX)/bin/$(bin)
+
+.PHONY: uninstall
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/bin/$(bin)
--- /dev/null
+/* dynarr - dynamic resizable C array data structure
+ * author: John Tsiombikas <nuclear@member.fsf.org>
+ * license: public domain
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "dynarr.h"
+
+/* The array descriptor keeps auxilliary information needed to manipulate
+ * the dynamic array. It's allocated adjacent to the array buffer.
+ */
+struct arrdesc {
+ int nelem, szelem;
+ int max_elem;
+ int bufsz; /* not including the descriptor */
+};
+
+#define DESC(x) ((struct arrdesc*)((char*)(x) - sizeof(struct arrdesc)))
+
+void *dynarr_alloc(int elem, int szelem)
+{
+ struct arrdesc *desc;
+
+ if(!(desc = malloc(elem * szelem + sizeof *desc))) {
+ return 0;
+ }
+ desc->nelem = desc->max_elem = elem;
+ desc->szelem = szelem;
+ desc->bufsz = elem * szelem;
+ return (char*)desc + sizeof *desc;
+}
+
+void dynarr_free(void *da)
+{
+ if(da) {
+ free(DESC(da));
+ }
+}
+
+void *dynarr_resize(void *da, int elem)
+{
+ int newsz;
+ void *tmp;
+ struct arrdesc *desc;
+
+ if(!da) return 0;
+ desc = DESC(da);
+
+ newsz = desc->szelem * elem;
+
+ if(!(tmp = realloc(desc, newsz + sizeof *desc))) {
+ return 0;
+ }
+ desc = tmp;
+
+ desc->nelem = desc->max_elem = elem;
+ desc->bufsz = newsz;
+ return (char*)desc + sizeof *desc;
+}
+
+int dynarr_empty(void *da)
+{
+ return DESC(da)->nelem ? 0 : 1;
+}
+
+int dynarr_size(void *da)
+{
+ return DESC(da)->nelem;
+}
+
+
+void *dynarr_clear(void *da)
+{
+ return dynarr_resize(da, 0);
+}
+
+/* stack semantics */
+void *dynarr_push(void *da, void *item)
+{
+ struct arrdesc *desc;
+ int nelem;
+
+ desc = DESC(da);
+ nelem = desc->nelem;
+
+ if(nelem >= desc->max_elem) {
+ /* need to resize */
+ struct arrdesc *tmp;
+ int newsz = desc->max_elem ? desc->max_elem * 2 : 1;
+
+ if(!(tmp = dynarr_resize(da, newsz))) {
+ fprintf(stderr, "failed to resize\n");
+ return da;
+ }
+ da = tmp;
+ desc = DESC(da);
+ desc->nelem = nelem;
+ }
+
+ if(item) {
+ memcpy((char*)da + desc->nelem * desc->szelem, item, desc->szelem);
+ }
+ desc->nelem++;
+ return da;
+}
+
+void *dynarr_pop(void *da)
+{
+ struct arrdesc *desc;
+ int nelem;
+
+ desc = DESC(da);
+ nelem = desc->nelem;
+
+ if(!nelem) return da;
+
+ if(nelem <= desc->max_elem / 3) {
+ /* reclaim space */
+ struct arrdesc *tmp;
+ int newsz = desc->max_elem / 2;
+
+ if(!(tmp = dynarr_resize(da, newsz))) {
+ fprintf(stderr, "failed to resize\n");
+ return da;
+ }
+ da = tmp;
+ desc = DESC(da);
+ desc->nelem = nelem;
+ }
+ desc->nelem--;
+
+ return da;
+}
+
+void *dynarr_finalize(void *da)
+{
+ struct arrdesc *desc = DESC(da);
+ memmove(desc, da, desc->bufsz);
+ return desc;
+}
--- /dev/null
+/* dynarr - dynamic resizable C array data structure
+ * author: John Tsiombikas <nuclear@member.fsf.org>
+ * license: public domain
+ */
+#ifndef DYNARR_H_
+#define DYNARR_H_
+
+/* usage example:
+ * -------------
+ * int *arr = dynarr_alloc(0, sizeof *arr);
+ *
+ * int x = 10;
+ * arr = dynarr_push(arr, &x);
+ * x = 5;
+ * arr = dynarr_push(arr, &x);
+ * x = 42;
+ * arr = dynarr_push(arr, &x);
+ *
+ * for(i=0; i<dynarr_size(arr); i++) {
+ * printf("%d\n", arr[i]);
+ * }
+ * dynarr_free(arr);
+ */
+
+void *dynarr_alloc(int elem, int szelem);
+void dynarr_free(void *da);
+void *dynarr_resize(void *da, int elem);
+
+/* dynarr_empty returns non-zero if the array is empty
+ * Complexity: O(1) */
+int dynarr_empty(void *da);
+/* dynarr_size returns the number of elements in the array
+ * Complexity: O(1) */
+int dynarr_size(void *da);
+
+void *dynarr_clear(void *da);
+
+/* stack semantics */
+void *dynarr_push(void *da, void *item);
+void *dynarr_pop(void *da);
+
+/* Finalize the array. No more resizing is possible after this call.
+ * Use free() instead of dynarr_free() to deallocate a finalized array.
+ * Returns pointer to the finalized array.
+ * dynarr_finalize can't fail.
+ * Complexity: O(n)
+ */
+void *dynarr_finalize(void *da);
+
+/* helper macros */
+#define DYNARR_RESIZE(da, n) \
+ do { (da) = dynarr_resize((da), (n)); } while(0)
+#define DYNARR_CLEAR(da) \
+ do { (da) = dynarr_clear(da); } while(0)
+#define DYNARR_PUSH(da, item) \
+ do { (da) = dynarr_push((da), (item)); } while(0)
+#define DYNARR_POP(da) \
+ do { (da) = dynarr_pop(da); } while(0)
+
+/* utility macros to push characters to a string. assumes and maintains
+ * the invariant that the last element is always a zero
+ */
+#define DYNARR_STRPUSH(da, c) \
+ do { \
+ char cnull = 0, ch = (char)(c); \
+ (da) = dynarr_pop(da); \
+ (da) = dynarr_push((da), &ch); \
+ (da) = dynarr_push((da), &cnull); \
+ } while(0)
+
+#define DYNARR_STRPOP(da) \
+ do { \
+ char cnull = 0; \
+ (da) = dynarr_pop(da); \
+ (da) = dynarr_pop(da); \
+ (da) = dynarr_push((da), &cnull); \
+ } while(0)
+
+
+#endif /* DYNARR_H_ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <png.h>
+#include "image.h"
+
+int alloc_image(struct image *img, int x, int y, int bpp)
+{
+ memset(img, 0, sizeof *img);
+ img->width = x;
+ img->height = y;
+ img->bpp = bpp;
+ img->scansz = img->pitch = x * bpp / 8;
+
+ if(!(img->pixels = malloc(y * img->scansz))) {
+ fprintf(stderr, "failed to allocate %dx%d (%dbpp) pixel buffer\n", x, y, bpp);
+ return -1;
+ }
+
+ /* just a guess, assume the user will fill the details, but set up reasonable
+ * defaults just in case...
+ */
+ if(bpp <= 8) {
+ img->nchan = 1;
+ img->cmap_ncolors = 1 << bpp;
+ } else if(bpp <= 24) {
+ img->nchan = 3;
+ } else {
+ img->nchan = 4;
+ }
+ return 0;
+}
+
+int load_image(struct image *img, const char *fname)
+{
+ int i;
+ FILE *fp;
+ png_struct *png;
+ png_info *info;
+ int chan_bits, color_type;
+ png_uint_32 xsz, ysz;
+ png_color *palette;
+ unsigned char **scanline;
+ unsigned char *dptr;
+
+ if(!(fp = fopen(fname, "rb"))) {
+ fprintf(stderr, "failed to open: %s: %s\n", fname, strerror(errno));
+ return -1;
+ }
+
+ if(!(png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0))) {
+ fclose(fp);
+ return -1;
+ }
+ if(!(info = png_create_info_struct(png))) {
+ fclose(fp);
+ png_destroy_read_struct(&png, 0, 0);
+ return -1;
+ }
+ if(setjmp(png_jmpbuf(png))) {
+ fclose(fp);
+ png_destroy_read_struct(&png, &info, 0);
+ return -1;
+ }
+
+ png_init_io(png, fp);
+ png_read_png(png, info, 0, 0);
+
+ png_get_IHDR(png, info, &xsz, &ysz, &chan_bits, &color_type, 0, 0, 0);
+ img->width = xsz;
+ img->height = ysz;
+ img->nchan = png_get_channels(png, info);
+ img->bpp = img->nchan * chan_bits;
+ img->scansz = img->pitch = xsz * img->bpp / 8;
+ img->cmap_ncolors = 0;
+
+ if(color_type == PNG_COLOR_TYPE_PALETTE) {
+ png_get_PLTE(png, info, &palette, &img->cmap_ncolors);
+ memcpy(img->cmap, palette, img->cmap_ncolors * sizeof *img->cmap);
+ }
+
+ if(!(img->pixels = malloc(ysz * img->scansz))) {
+ perror("failed to allocate pixel buffer");
+ fclose(fp);
+ png_destroy_read_struct(&png, &info, 0);
+ return -1;
+ }
+ dptr = img->pixels;
+
+ scanline = (unsigned char**)png_get_rows(png, info);
+ for(i=0; i<ysz; i++) {
+ memcpy(dptr, scanline[i], img->scansz);
+ dptr += img->pitch;
+ }
+
+ fclose(fp);
+ png_destroy_read_struct(&png, &info, 0);
+ return 0;
+}
+
+int save_image(struct image *img, const char *fname)
+{
+ int i, chan_bits, coltype;
+ FILE *fp;
+ png_struct *png;
+ png_info *info;
+ png_text txt;
+ unsigned char **scanline = 0;
+ unsigned char *pptr;
+
+ if(!(fp = fopen(fname, "wb"))) {
+ fprintf(stderr, "save_image: failed to open: %s: %s\n", fname, strerror(errno));
+ return -1;
+ }
+
+ if(!(png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0))) {
+ fclose(fp);
+ return -1;
+ }
+ if(!(info = png_create_info_struct(png))) {
+ png_destroy_write_struct(&png, 0);
+ fclose(fp);
+ return -1;
+ }
+
+ txt.compression = PNG_TEXT_COMPRESSION_NONE;
+ txt.key = "Software";
+ txt.text = "img2tiles";
+ txt.text_length = 0;
+
+ if(setjmp(png_jmpbuf(png))) {
+ png_destroy_write_struct(&png, &info);
+ free(scanline);
+ fclose(fp);
+ return -1;
+ }
+
+ switch(img->nchan) {
+ case 1:
+ if(img->cmap_ncolors > 0) {
+ coltype = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ coltype = PNG_COLOR_TYPE_GRAY;
+ }
+ break;
+ case 2:
+ coltype = PNG_COLOR_TYPE_GRAY_ALPHA;
+ break;
+ case 3:
+ coltype = PNG_COLOR_TYPE_RGB;
+ break;
+ case 4:
+ coltype = PNG_COLOR_TYPE_RGB_ALPHA;
+ break;
+ }
+
+ chan_bits = img->bpp / img->nchan;
+ png_set_IHDR(png, info, img->width, img->height, chan_bits, coltype, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+ png_set_text(png, info, &txt, 1);
+
+ if(img->cmap_ncolors > 0) {
+ png_set_PLTE(png, info, (png_color*)img->cmap, img->cmap_ncolors);
+ }
+
+ if(!(scanline = malloc(img->height * sizeof *scanline))) {
+ png_destroy_write_struct(&png, &info);
+ fclose(fp);
+ return -1;
+ }
+
+ pptr = img->pixels;
+ for(i=0; i<img->height; i++) {
+ scanline[i] = pptr;
+ pptr += img->pitch;
+ }
+ png_set_rows(png, info, scanline);
+
+ png_init_io(png, fp);
+ png_write_png(png, info, 0, 0);
+ png_destroy_write_struct(&png, &info);
+ free(scanline);
+ fclose(fp);
+ return 0;
+}
+
+
+int cmp_image(struct image *a, struct image *b)
+{
+ int i;
+ unsigned char *aptr = a->pixels;
+ unsigned char *bptr = b->pixels;
+
+ if(a->width != b->width || a->height != b->height || a->bpp != b->bpp || a->nchan != b->nchan) {
+ return -1;
+ }
+
+ for(i=0; i<a->height; i++) {
+ if(memcmp(aptr, bptr, a->scansz) != 0) {
+ return -1;
+ }
+ aptr += a->pitch;
+ bptr += b->pitch;
+ }
+ return 0;
+}
+
+void blit(struct image *src, int sx, int sy, int w, int h, struct image *dst, int dx, int dy)
+{
+ int i;
+ unsigned char *sptr, *dptr;
+
+ assert(src->bpp == dst->bpp);
+ assert(src->nchan == dst->nchan);
+
+ if(sx < 0) { w += sx; sx = 0; }
+ if(sy < 0) { h += sy; sy = 0; }
+ if(dx < 0) { w += dx; sx -= dx; dx = 0; }
+ if(dy < 0) { h += dy; sy -= dy; dy = 0; }
+ if(sx + w >= src->width) w = src->width - sx;
+ if(sy + h >= src->height) h = src->height - sy;
+ if(dx + w >= dst->width) w = dst->width - dx;
+ if(dy + h >= dst->height) h = dst->height - dy;
+
+ if(w <= 0 || h <= 0) return;
+
+ sptr = src->pixels + sy * src->pitch + sx * src->bpp / 8;
+ dptr = dst->pixels + dy * dst->pitch + dx * dst->bpp / 8;
+
+ for(i=0; i<h; i++) {
+ memcpy(dptr, sptr, w * dst->bpp / 8);
+ dptr += dst->pitch;
+ sptr += src->pitch;
+ }
+}
--- /dev/null
+#ifndef IMAGE_H_
+#define IMAGE_H_
+
+struct cmapent {
+ unsigned char r, g, b;
+};
+
+struct image {
+ int width, height;
+ int bpp;
+ int nchan;
+ int scansz, pitch;
+ int cmap_ncolors;
+ struct cmapent cmap[256];
+ unsigned char *pixels;
+};
+
+int alloc_image(struct image *img, int x, int y, int bpp);
+int load_image(struct image *img, const char *fname);
+int save_image(struct image *img, const char *fname);
+
+int cmp_image(struct image *a, struct image *b);
+
+void blit(struct image *src, int sx, int sy, int w, int h, struct image *dst, int dx, int dy);
+
+#endif /* IMAGE_H_ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include "image.h"
+#include "dynarr.h"
+
+void print_usage(const char *argv0);
+int proc_image(const char *fname);
+void wrtiles(FILE *fp, const char *name, struct image *timg, int ntiles);
+void wrtilemap(FILE *fp, const char *name, int *tmap, int xtiles, int ytiles);
+void wrpalette(FILE *fp, const char *name, struct cmapent *cmap, int ncol);
+
+int tile_xsz = 8, tile_ysz = 8;
+const char *output_filename;
+
+int main(int argc, char **argv)
+{
+ int i;
+
+ for(i=1; i<argc; i++) {
+ if(argv[i][0] == '-') {
+ if(argv[i][2] == 0) {
+ switch(argv[i][1]) {
+ case 'o':
+ output_filename = argv[++i];
+ break;
+
+ case 't':
+ if(sscanf(argv[++i], "%dx%d", &tile_xsz, &tile_ysz) != 2) {
+ fprintf(stderr, "-t must be followed by the tile size (WxH)\n");
+ return 1;
+ }
+ break;
+
+ case 'h':
+ print_usage(argv[0]);
+ return 0;
+
+ default:
+ fprintf(stderr, "invalid option: %s\n", argv[i]);
+ print_usage(argv[0]);
+ return 1;
+ }
+ } else {
+ fprintf(stderr, "invalid option: %s\n", argv[i]);
+ print_usage(argv[0]);
+ return 1;
+ }
+
+ } else {
+ if(proc_image(argv[i]) == -1) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void print_usage(const char *argv0)
+{
+ printf("Usage: %s [options] <img1> [<img2> ... <imgN>]\n", argv0);
+ printf("Options:\n");
+ printf(" -o <file>: output file\n");
+ printf(" -t WxH: tile size (default 8x8)\n");
+ printf(" -h: print usage and exit\n\n");
+}
+
+int find_tile(struct image *tile, struct image *tiles)
+{
+ int i, count;
+ count = dynarr_size(tiles);
+ for(i=0; i<count; i++) {
+ if(cmp_image(tile, tiles + i) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int proc_image(const char *fname)
+{
+ int i, j, k, idx, xtiles, ytiles, ntiles, result = -1;
+ FILE *fp;
+ struct image img, tile;
+ struct image *tiles = 0;
+ unsigned char *tiles_pixels, *tptr;
+ unsigned char *sptr;
+ int *tilemap = 0, *mapptr;
+ char *basename, *suffix, *outfile;
+
+ if(load_image(&img, fname) == -1) {
+ fprintf(stderr, "failed to load image: %s\n", fname);
+ return -1;
+ }
+ basename = alloca(strlen(fname) + 1);
+ strcpy(basename, fname);
+ if((suffix = strrchr(basename, '/'))) {
+ basename = suffix + 1;
+ }
+ if((suffix = strchr(basename, '.'))) {
+ *suffix = 0;
+ }
+
+ xtiles = img.width / tile_xsz;
+ ytiles = img.height / tile_ysz;
+ if(img.width % tile_xsz != 0 || img.height % tile_ysz != 0) {
+ fprintf(stderr, "image size (%dx%d) not evenly divisible into %dx%d tiles\n",
+ img.width, img.height, tile_xsz, tile_ysz);
+ goto err;
+ }
+
+ if(!(tilemap = malloc(xtiles * ytiles * sizeof *tilemap))) {
+ fprintf(stderr, "failed to allocate %dx%d tilemap\n", xtiles, ytiles);
+ goto err;
+ }
+ mapptr = tilemap;
+
+ if(!(tiles = dynarr_alloc(0, sizeof *tiles))) {
+ fprintf(stderr, "failed to allocate tile array\n");
+ goto err;
+ }
+ /* alloc a contiguous buffer for the full tileset pixels, to make it easier to write it
+ * out as a single image in the end
+ */
+ if(!(tiles_pixels = dynarr_alloc(0, tile_xsz * tile_ysz * img.bpp / 8))) {
+ fprintf(stderr, "failed to allocate tile pixel buffer\n");
+ goto err;
+ }
+
+ tile = img;
+ tile.width = tile_xsz;
+ tile.height = tile_ysz;
+ tile.scansz = tile_xsz * tile.bpp / 8;
+
+ sptr = img.pixels;
+ for(i=0; i<ytiles; i++) {
+ for(j=0; j<xtiles; j++) {
+ tile.pixels = sptr;
+
+ if((idx = find_tile(&tile, tiles)) == -1) {
+ /* we don't have a duplicate of this tile */
+ idx = dynarr_size(tiles);
+
+ if(!(tiles = dynarr_push(tiles, 0))) {
+ goto err;
+ }
+ if(!(tptr = dynarr_push(tiles_pixels, 0))) {
+ goto err;
+ }
+
+ tiles[idx] = tile;
+ tiles[idx].scansz = tiles[idx].pitch = tile.scansz;
+
+ /* did the array get relocated? */
+ if(tptr != tiles_pixels) {
+ tiles_pixels = tptr;
+ /* make each tile's pixels pointer point to the right place in the large pixelbuffer */
+ for(k=0; k<idx+1; k++) {
+ tiles[k].pixels = tptr;
+ tptr += tile_ysz * tiles[idx].pitch;
+ }
+ } else {
+ /* otherwise just set the new one */
+ tiles[idx].pixels = tiles_pixels + idx * tile_ysz * tiles[idx].pitch;
+ }
+
+ blit(&tile, 0, 0, tile_xsz, tile_ysz, tiles + idx, 0, 0);
+ }
+
+ *mapptr++ = idx;
+
+ sptr += tile.scansz;
+ }
+ sptr += (tile_ysz - 1) * tile.pitch;
+ }
+
+ ntiles = dynarr_size(tiles);
+ fprintf(stderr, "%s (%dx%d) -> %d tiles, %d unique\n", fname, img.width, img.height,
+ xtiles * ytiles, ntiles);
+
+ assert(ntiles > 0);
+
+ /* make a big image out of the tiles and write it out */
+ tile = tiles[0];
+ tile.height = ntiles * tile_ysz;
+
+ if(output_filename) {
+ outfile = (char*)output_filename;
+ } else {
+ outfile = alloca(strlen(basename) + 5);
+ sprintf(outfile, "%s.asm", basename);
+ }
+
+ if(!(fp = fopen(outfile, "w"))) {
+ fprintf(stderr, "failed to open output file: %s: %s\n", outfile, strerror(errno));
+ goto err;
+ }
+ wrtiles(fp, basename, &tile, ntiles);
+ wrtilemap(fp, basename, tilemap, xtiles, ytiles);
+ wrpalette(fp, basename, tile.cmap, tile.cmap_ncolors);
+ fclose(fp);
+
+ result = 0;
+
+err:
+ dynarr_free(tiles_pixels);
+ dynarr_free(tiles);
+ free(tilemap);
+ free(img.pixels);
+ return result;
+}
+
+
+unsigned int packbitplane4bpp(unsigned char *pix, int bit)
+{
+ int i;
+ unsigned int val = 0;
+
+ for(i=0; i<4; i++) {
+ val = (val << 1) | ((*pix >> (bit + 4)) & 1);
+ val = (val << 1) | ((*pix >> bit) & 1);
+ pix++;
+ }
+
+ return val;
+}
+
+void wrtile4bpp(FILE *fp, unsigned char *tpix, struct image *timg)
+{
+ int i, x, y, curx = 0;
+ unsigned char *ptr;
+ unsigned int val;
+
+ /* for each pair of bitplanes */
+ for(i=0; i<timg->bpp/2; i++) {
+ ptr = tpix;
+ for(y=0; y<tile_ysz; y++) {
+ for(x=0; x<tile_xsz/8; x++) {
+ val = packbitplane4bpp(ptr, i * 2);
+ val |= packbitplane4bpp(ptr, i * 2 + 1) << 8;
+
+ if(curx == 0) {
+ curx = 7 + fprintf(fp, "\t.word $%x", (unsigned int)val);
+ } else {
+ curx += fprintf(fp, ", $%x", (unsigned int)val);
+ }
+ if(curx >= 75) {
+ fputc('\n', fp);
+ curx = 0;
+ }
+ }
+ ptr += tile_xsz * timg->bpp / 8;
+ }
+ }
+
+ if(curx != 0) {
+ fputc('\n', fp);
+ }
+}
+
+void wrtiles(FILE *fp, const char *name, struct image *timg, int ntiles)
+{
+ int i, j;
+ unsigned char *ptr = timg->pixels;
+
+ fprintf(fp, "\n\t%s_num_tiles = %d\n", name, ntiles);
+ fprintf(fp, "\t%s_tiles_width = %d\n", name, timg->width);
+ fprintf(fp, "\t%s_tiles_height = %d\n", name, timg->height);
+ fprintf(fp, "\t%s_tiles_bpp = %d\n", name, timg->bpp);
+ fprintf(fp, "%s_tiles:\n", name);
+
+ for(i=0; i<ntiles; i++) {
+ fprintf(fp, "\t; tile %d:", i);
+ for(j=0; j<timg->scansz * tile_ysz; j++) {
+ fprintf(fp, " %x", ptr[j]);
+ }
+ fputc('\n', fp);
+
+ switch(timg->bpp) {
+ case 4:
+ wrtile4bpp(fp, ptr, timg);
+ break;
+
+ default:
+ fprintf(stderr, "unsupported bpp: %d\n", timg->bpp);
+ return;
+ }
+ ptr += timg->scansz * tile_ysz;
+ fputc('\n', fp);
+ }
+
+ fputc('\n', fp);
+}
+
+void wrtilemap(FILE *fp, const char *name, int *tmap, int xtiles, int ytiles)
+{
+ int i, sz, curx = 0;
+
+ fprintf(fp, "\n\t%s_tilemap_cols = %d\n", name, xtiles);
+ fprintf(fp, "\t%s_tilemap_rows = %d\n", name, ytiles);
+ fprintf(fp, "%s_tilemap:\n", name);
+
+ sz = xtiles * ytiles;
+ for(i=0; i<sz; i++) {
+ if(curx == 0) {
+ curx = 7 + fprintf(fp, "\t.word %u", (unsigned int)tmap[i]);
+ } else {
+ curx += fprintf(fp, ", %u", (unsigned int)tmap[i]);
+ }
+ if(curx >= 75) {
+ fprintf(fp, "\n");
+ curx = 0;
+ }
+ }
+
+ fprintf(fp, "\n\n");
+}
+
+void wrpalette(FILE *fp, const char *name, struct cmapent *cmap, int ncol)
+{
+ int i;
+
+ fprintf(fp, "\n\t%s_cmap_colors = %d\n", name, ncol);
+ fprintf(fp, "%s_cmap:\n", name);
+ for(i=0; i<ncol; i++) {
+ unsigned int col = (cmap[i].r * 31 / 255) | ((cmap[i].g * 31 / 255) << 5) |
+ ((cmap[i].b * 31 / 255) << 10);
+ fprintf(fp, "\t.word $%04x\n", col);
+ }
+ fprintf(fp, "\n\n");
+}
--- /dev/null
+MEMORY {
+ WRAM: start = 0, size = $2000;
+ ROM: start = $8000, size = $8000;
+}
+
+SEGMENTS {
+ code: load = ROM, type = ro;
+ rodata: load = ROM, type = ro;
+ data: load = ROM, run = WRAM, type = rw, define = yes;
+ bss: load = WRAM, type = bss, define = yes;
+ carthdr: load = ROM, start = $ffc0, type = ro;
+}
--- /dev/null
+ .p816
+ .include "hwregs.inc"
+
+ .segment "rodata"
+ .include "data.inc"
+
+ .segment "code"
+
+ vmem_tiles_offs = 4096
+
+ sei
+ clc
+ xce
+
+ rep #$10
+ .i16
+ ldx #$1fff
+ txs
+ sep #$10
+ .i8
+
+ jsr snes_init
+ setreg REG_BGMODE, $02 ; mode 2, 8x8 tiles
+ setreg REG_BG12NBA, $1 ; tiles at offs 4kb
+ setreg REG_TM, $1 ; main screen: BG1
+
+ ldx #0
+cmap_loop:
+ txa
+ lsr
+ sta REG_CGADD
+ lda logo4bpp_cmap,x
+ inx
+ sta REG_CGDATA
+ lda logo4bpp_cmap,x
+ inx
+ sta REG_CGDATA
+ cpx #32 ; 16 entries 2 bytes each
+ bne cmap_loop
+
+ pea logo4bpp_tiles_width * logo4bpp_tiles_height / 2
+ pea logo4bpp_tiles
+ pea vmem_tiles_offs
+ jsr copy_vmem
+ rep #$20
+ .a16
+ pla
+ pla
+ pla
+ sep #$20
+ .a8
+
+ pea logo4bpp_tilemap_rows * logo4bpp_tilemap_cols * 2
+ pea logo4bpp_tilemap
+ pea 0
+ jsr copy_vmem
+ rep #$20
+ .a16
+ pla
+ pla
+ pla
+ sep #$20
+ .a8
+
+ fblank 0
+
+halt:
+ jmp halt
+
+ ; copy_vmem(vmem_offset, src, num_words)
+copy_vmem:
+ rep #$30 ; 16bit accumulator and index registers
+ .a16
+ .i16
+ phd ; save d
+ tsc ; and make it point to the stack
+ tcd
+ sep #$20 ; restore 8bit accum
+ .a8
+ ; stack frame
+ ; $1 saved D
+ ; $3 return address
+ ; $5 vmem_offs
+ ; $7 src
+ ; $9 num_words
+
+ lda #$80 ; auto incerment after wiriting high byte
+ sta REG_VMAINC
+ lda $5
+ sta REG_VMADDL
+ lda $6
+ sta REG_VMADDH
+ ldy #0
+@loop:
+ lda ($7),y
+ sta REG_VMDATAL
+ iny
+ lda ($7),y
+ sta REG_VMDATAH
+ iny
+ cpy $9
+ bne @loop
+
+ pld
+ sep #$10 ; back to 8bit index registers
+ .i8
+ rts
+
+
+snes_init:
+ fblank 1
+ stz REG_OBSEL
+ stz REG_OAMADDL
+ stz REG_OAMADDH
+ stz REG_OAMDATA
+ stz REG_OAMDATA
+ stz REG_BGMODE
+ stz REG_MOSAIC
+ stz REG_BG1SC
+ stz REG_BG2SC
+ stz REG_BG3SC
+ stz REG_BG4SC
+ stz REG_BG12NBA
+ stz REG_BG34NBA
+ stz REG_BG1HOFFS
+ stz REG_BG1HOFFS
+ stz REG_BG1VOFFS
+ stz REG_BG1VOFFS
+ stz REG_BG2HOFFS
+ stz REG_BG2HOFFS
+ stz REG_BG2VOFFS
+ stz REG_BG2VOFFS
+ stz REG_BG3HOFFS
+ stz REG_BG3HOFFS
+ stz REG_BG3VOFFS
+ stz REG_BG3VOFFS
+ stz REG_BG4HOFFS
+ stz REG_BG4HOFFS
+ stz REG_BG4VOFFS
+ stz REG_BG4VOFFS
+ setreg REG_VMAINC, $80
+ stz REG_VMADDL
+ stz REG_VMADDH
+ stz REG_VMDATAL
+ stz REG_VMDATAH
+ stz REG_M7SEL
+ stz REG_M7A
+ stz REG_M7A
+ stz REG_M7B
+ stz REG_M7B
+ stz REG_M7C
+ stz REG_M7C
+ stz REG_M7D
+ stz REG_M7D
+ stz REG_M7X
+ stz REG_M7X
+ stz REG_M7Y
+ stz REG_M7Y
+ stz REG_CGADD
+ stz REG_CGDATA
+ stz REG_CGDATA
+ stz REG_W12SEL
+ stz REG_W34SEL
+ stz REG_WH0
+ stz REG_WH1
+ stz REG_WH2
+ stz REG_WH3
+ stz REG_WBGLOG
+ stz REG_WOBJLOG
+ stz REG_TM
+ stz REG_TS
+ stz REG_TMW
+ stz REG_TSW
+ setreg REG_CGWSEL, $30
+ stz REG_CGADSUB
+ setreg REG_COLDATA, $e0
+ stz REG_SETINI
+ stz REG_NMITIMEN
+ setreg REG_WRIO, $ff
+ stz REG_WRMPYA
+ stz REG_WRMPYB
+ stz REG_WRDIVL
+ stz REG_WRDIVH
+ stz REG_WRDIVB
+ stz REG_HTIMEL
+ stz REG_HTIMEH
+ stz REG_VTIMEL
+ stz REG_VTIMEH
+ stz REG_MDMAEN
+ stz REG_HDMAEN
+ stz REG_MEMSEL
+ rts
+
+ ; cartridge header
+ .segment "carthdr"
+ .byte "TEST "
+ .byte $20 ; fast ROM, LoROM mapping
+ .byte 0 ; ROM only
+ .byte 1 ; ROM banks 1 = 32k
+ .byte 0 ; RAM size
+ .byte 2 ; country europe/oceania/asia
+ .byte 0 ; developer id: none
+ .byte 0 ; ROM version
+ .word $ffff ; checksum complement
+ .word 0 ; checksum
+ ; 65816 vectors
+ .word 0, 0
+ .word 0 ; cop
+ .word 0 ; brk
+ .word 0 ; abort
+ .word 0 ; NMI (vblank)
+ .word 0
+ .word 0 ; IRQ
+ ; 6502 vectors
+ .word 0, 0
+ .word 0 ; cop
+ .word 0
+ .word 0 ; abort
+ .word 0 ; NMI (vblank)
+ .word $8000 ; reset
+ .word 0 ; IRQ/BRK
+
+
+ ; vi:ft=asm_ca65: