-
-
Notifications
You must be signed in to change notification settings - Fork 561
Description
Hi,
Very cool console you have! I like it very much! I only have issues with the PNG files. The way how TIC-80 currently handles them is unacceptable for several reasons:
- First, it's not supported everywhere, only in studio and in surf (but not from command line for example).
- Second, it does not work on systems with case-sensitive file systems (due to the LoadPngCart call depends on file extension!)
- Third, and this is the biggest problem, a real no-go for me, is the way how TIC-80 embeds data generates CORRUPT png files! Tools are refusing to resize the image because of that, or worse, they might corrupt the cartridge data silently.
Here's a proposed solution that fixes all of these problems at once and as a side effect improves performance a lot. This renders the use of LoadPngCart
completely unnecessary, it Just Works(tm) and gets the cartridge from a png file without actually decoding the data:
diff --git a/src/cart.c b/src/cart.c
index 9be32c7..1c6079d 100644
--- a/src/cart.c
+++ b/src/cart.c
@@ -87,6 +87,28 @@ void tic_cart_load(tic_cartridge* cart, const u8* buffer, s32 size)
#define LOAD_CHUNK(to) memcpy(&to, ptr, MIN(sizeof(to), chunk->size ? retro_le_to_cpu16(chunk->size) : TIC_BANK_SIZE))
+ // check if the cartridge buffer is a PNG file
+ if (!memcmp(buffer, "\x89PNG", 4))
+ {
+ s32 siz;
+ const u8* ptr = buffer + 8;
+ while (ptr < end)
+ {
+ siz = ((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]) + 12;
+ if (!memcmp(ptr + 4, "caRt", 4))
+ {
+ buffer = ptr + 8;
+ size = siz;
+ end = buffer + size;
+ break;
+ }
+ ptr += siz;
+ }
+ // error, no TIC-80 cartridge chunk in PNG???
+ if (ptr >= end)
+ return;
+ }
+
// load palette chunk first
{
const u8* ptr = buffer;
The advantage is, this patch is extremely simple, dependency-free and it does not require to decode the png at all! It just iterates through png chunks without interpreting them.
I've chosen the chunk magic to be caRt
. This isn't some foolishness, rather selected in accordance to the PNG Specification section 9.8. "Use of private chunks", and means ancillary, private, safe-to-copy chunk.
As for creating such VALID png files, here's an extremely simple command line tool to do that (I've written it deliberately in a way so that you can reuse the tic2png
function in TIC-80). Again, this is totally dependency-free (note no png libraries used!), and does not require to decode nor to encode png data, all it does is just manipulating png chunks. It simply inserts a caRt
chunk with the cartridge data as the last chunk (well, the one before the IEND
chunk). It also takes care of the case if the png already had a caRt
chunk.
/*
* tic2png.c
*
* Copyright (C) 2022 bzt (bztsrc@gitlab) MIT license
*
* @brief Small dependency-free tool to embed a .tic cartridge into a .png file
* Compilation: gcc tic2png.c -o tic2png
*/
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* the png specification is very strict, *do not* change uppercase/lowercase */
#define TICMAGIC "caRt"
/**
* Add a tic file into a png chunk
*/
int tic2png(uint8_t *tic, size_t ticsize, uint8_t **png, size_t *pngsize)
{
int i, j;
uint32_t crc, crc_table[256], size;
uint8_t *buf, *in, *out, *end, *ptr = tic;
size_t s;
if(!tic || !png || !*png || memcmp(*png, "\x89PNG", 4) || !pngsize || *pngsize < 8+12+12) return 0;
end = *png + *pngsize;
buf = malloc(*pngsize + ticsize + 12);
if(!buf) return 0;
s = 8; in = *png; out = buf; memcpy(out, in, 8); in += 8; out += 8;
/* copy all chunks, except tic chunk */
while(in < end - 12 && memcmp(in + 4, "IEND", 4)) {
size = ((in[0] << 24) | (in[1] << 16) | (in[2] << 8) | in[3]) + 12;
if(memcmp(in + 4, TICMAGIC, 4)) { memcpy(out, in, size); out += size; s += size; }
in += size;
}
/* add new tic chunk */
s += 4;
*out++ = (ticsize >> 24) & 0xff; *out++ = (ticsize >> 16) & 0xff; *out++ = (ticsize >> 8) & 0xff; *out++ = (ticsize >> 0) & 0xff;
for(i = 0; i < 256; i++) {
crc = (uint32_t)i; for(j = 0; j < 8; j++) { crc = crc & 1 ? 0xedb88320L ^ (crc >> 1) : (crc >> 1); crc_table[i] = crc;
}
crc = 0xffffffff;
for(i = 0; i < 4; i++, s++, out++) { *out = TICMAGIC[i]; crc = crc_table[(crc ^ *out) & 0xff] ^ (crc >> 8); }
for(; ticsize; ticsize--, s++, ptr++, out++) { *out = *ptr; crc = crc_table[(crc ^ *out) & 0xff] ^ (crc >> 8); }
crc ^= 0xffffffff; s += 4;
*out++ = (crc >> 24) & 0xff; *out++ = (crc >> 16) & 0xff; *out++ = (crc >> 8) & 0xff; *out++ = (crc >> 0) & 0xff;
/* add end chunk */
memcpy(out, in, 12);
s += 12;
/* replace original png buffer with the new one */
free(*png);
*png = buf;
*pngsize = s;
return 1;
}
/**
* Read in one file into memory
*/
uint8_t *readfile(char *fn, size_t *size)
{
FILE *f;
uint8_t *buf = NULL;
f = fopen(fn, "rb");
if(f) {
fseek(f, 0, SEEK_END);
*size = ftell(f);
fseek(f, 0, SEEK_SET);
buf = malloc(*size);
if(!buf) { fclose(f); fprintf(stderr, "tic2png: unable to allocate memory\r\n"); exit(1); }
fread(buf, 1, *size, f);
fclose(f);
} else {
fprintf(stderr, "tic2png: unable to read %s\r\n", fn);
exit(1);
}
return buf;
}
/**
* Main procedure
*/
int main(int argc, char **argv)
{
FILE *f;
int ret;
size_t ticsize, pngsize, outsize;
uint8_t *tic, *png, *out;
/* check arguments */
if(argc < 3) {
printf("tic2png - by bzt MIT\r\n\r\n %s <.tic file> <.png file>\r\n", argv[0]);
return 1;
}
/* read in files */
tic = readfile(argv[1], &ticsize);
png = readfile(argv[2], &pngsize);
/* add tic to png */
if(!tic2png(tic, ticsize, &png, &pngsize)) {
fprintf(stderr, "tic2png: unable to add png chunk\r\n");
return 1;
}
/* save new png file */
f = fopen(argv[2], "wb");
if(f) {
fwrite(png, 1, pngsize, f);
fclose(f);
} else {
fprintf(stderr, "tic2png: unable to write %s\r\n", argv[2]);
return 1;
}
return 0;
}
All these are licensed under MIT, feel free to use them in TIC-80.
Cheers,
bzt