From 4b093322cbd6142997aa4f5c34b63f4c68a4cfdb Mon Sep 17 00:00:00 2001 From: John Tsiombikas Date: Fri, 3 Jul 2020 19:03:17 +0300 Subject: [PATCH] SNES first test --- .gitignore | 8 ++ Makefile | 17 +++ hwregs.inc | 125 ++++++++++++++++++ img2snes/Makefile | 24 ++++ img2snes/src/dynarr.c | 141 +++++++++++++++++++++ img2snes/src/dynarr.h | 80 ++++++++++++ img2snes/src/image.c | 237 +++++++++++++++++++++++++++++++++++ img2snes/src/image.h | 26 ++++ img2snes/src/main.c | 334 +++++++++++++++++++++++++++++++++++++++++++++++++ snes.ld | 12 ++ test.asm | 224 +++++++++++++++++++++++++++++++++ 11 files changed, 1228 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 hwregs.inc create mode 100644 img2snes/Makefile create mode 100644 img2snes/src/dynarr.c create mode 100644 img2snes/src/dynarr.h create mode 100644 img2snes/src/image.c create mode 100644 img2snes/src/image.h create mode 100644 img2snes/src/main.c create mode 100644 snes.ld create mode 100644 test.asm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c8f7c7a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.o +*.d +*.swp +*.png +data.inc +*.sfc +img2snes/img2snes +link.map diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ebe9b8a --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +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 diff --git a/hwregs.inc b/hwregs.inc new file mode 100644 index 0000000..672aedf --- /dev/null +++ b/hwregs.inc @@ -0,0 +1,125 @@ + ; 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 + diff --git a/img2snes/Makefile b/img2snes/Makefile new file mode 100644 index 0000000..b578678 --- /dev/null +++ b/img2snes/Makefile @@ -0,0 +1,24 @@ +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) diff --git a/img2snes/src/dynarr.c b/img2snes/src/dynarr.c new file mode 100644 index 0000000..59bbf8c --- /dev/null +++ b/img2snes/src/dynarr.c @@ -0,0 +1,141 @@ +/* dynarr - dynamic resizable C array data structure + * author: John Tsiombikas + * license: public domain + */ +#include +#include +#include +#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; +} diff --git a/img2snes/src/dynarr.h b/img2snes/src/dynarr.h new file mode 100644 index 0000000..8690b5a --- /dev/null +++ b/img2snes/src/dynarr.h @@ -0,0 +1,80 @@ +/* dynarr - dynamic resizable C array data structure + * author: John Tsiombikas + * 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 +#include +#include +#include +#include +#include +#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; iscansz); + 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; iheight; 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; iheight; 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; ibpp / 8); + dptr += dst->pitch; + sptr += src->pitch; + } +} diff --git a/img2snes/src/image.h b/img2snes/src/image.h new file mode 100644 index 0000000..b9f24b4 --- /dev/null +++ b/img2snes/src/image.h @@ -0,0 +1,26 @@ +#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_ */ diff --git a/img2snes/src/main.c b/img2snes/src/main.c new file mode 100644 index 0000000..4b7f4bc --- /dev/null +++ b/img2snes/src/main.c @@ -0,0 +1,334 @@ +#include +#include +#include +#include +#include +#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 [ ... ]\n", argv0); + printf("Options:\n"); + printf(" -o : 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 %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; ibpp/2; i++) { + ptr = tpix; + for(y=0; y= 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; iscansz * 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= 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