321 lines
12 KiB
C
321 lines
12 KiB
C
// MIT License
|
|
|
|
// Copyright (c) 2020 Vadim Grigoruk @nesbox // grigoruk@gmail.com
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
#include "cart.h"
|
|
|
|
#if defined(BUILD_DEPRECATED)
|
|
#include "tools.h"
|
|
#include "ext/gif.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
|
|
#if defined(DINGUX) && !defined(static_assert)
|
|
#define static_assert _Static_assert
|
|
#endif
|
|
|
|
typedef enum
|
|
{
|
|
CHUNK_DUMMY, // 0
|
|
CHUNK_TILES, // 1
|
|
CHUNK_SPRITES, // 2
|
|
CHUNK_COVER_DEP, // 3 - deprecated chunk
|
|
CHUNK_MAP, // 4
|
|
CHUNK_CODE, // 5
|
|
CHUNK_FLAGS, // 6
|
|
CHUNK_TEMP2, // 7
|
|
CHUNK_TEMP3, // 8
|
|
CHUNK_SAMPLES, // 9
|
|
CHUNK_WAVEFORM, // 10
|
|
CHUNK_TEMP4, // 11
|
|
CHUNK_PALETTE, // 12
|
|
CHUNK_PATTERNS_DEP, // 13 - deprecated chunk
|
|
CHUNK_MUSIC, // 14
|
|
CHUNK_PATTERNS, // 15
|
|
CHUNK_CODE_ZIP, // 16
|
|
CHUNK_DEFAULT, // 17
|
|
CHUNK_SCREEN, // 18
|
|
CHUNK_BINARY, // 19
|
|
CHUNK_LANG, // 20
|
|
} ChunkType;
|
|
|
|
typedef struct
|
|
{
|
|
u32 type:5; // ChunkType
|
|
u32 bank:TIC_BANK_BITS;
|
|
u32 size:TIC_BANKSIZE_BITS; // max chunk size is 64K
|
|
u32 temp:8;
|
|
} Chunk;
|
|
|
|
static_assert(sizeof(Chunk) == 4, "tic_chunk_size");
|
|
|
|
static const u8 Sweetie16[] = {0x1a, 0x1c, 0x2c, 0x5d, 0x27, 0x5d, 0xb1, 0x3e, 0x53, 0xef, 0x7d, 0x57, 0xff, 0xcd, 0x75, 0xa7, 0xf0, 0x70, 0x38, 0xb7, 0x64, 0x25, 0x71, 0x79, 0x29, 0x36, 0x6f, 0x3b, 0x5d, 0xc9, 0x41, 0xa6, 0xf6, 0x73, 0xef, 0xf7, 0xf4, 0xf4, 0xf4, 0x94, 0xb0, 0xc2, 0x56, 0x6c, 0x86, 0x33, 0x3c, 0x57};
|
|
static const u8 Waveforms[] = {0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe};
|
|
|
|
static s32 chunkSize(const Chunk* chunk)
|
|
{
|
|
return chunk->size == 0 && chunk->type == CHUNK_CODE ? TIC_BANK_SIZE : chunk->size;
|
|
}
|
|
|
|
void tic_cart_load(tic_cartridge* cart, const u8* buffer, s32 size)
|
|
{
|
|
memset(cart, 0, sizeof(tic_cartridge));
|
|
const u8* end = buffer + size;
|
|
|
|
#define LOAD_CHUNK(to) memcpy(&to, ptr, MIN(sizeof(to), chunk->size ? chunk->size : TIC_BANK_SIZE))
|
|
|
|
// load palette chunk first
|
|
{
|
|
const u8* ptr = buffer;
|
|
while (ptr < end)
|
|
{
|
|
const Chunk* chunk = (Chunk*)ptr;
|
|
ptr += sizeof(Chunk);
|
|
|
|
switch (chunk->type)
|
|
{
|
|
case CHUNK_PALETTE:
|
|
LOAD_CHUNK(cart->banks[chunk->bank].palette);
|
|
break;
|
|
case CHUNK_DEFAULT:
|
|
memcpy(&cart->banks[chunk->bank].palette, Sweetie16, sizeof Sweetie16);
|
|
memcpy(&cart->banks[chunk->bank].sfx.waveforms, Waveforms, sizeof Waveforms);
|
|
break;
|
|
default: break;
|
|
}
|
|
|
|
ptr += chunkSize(chunk);
|
|
}
|
|
|
|
#if defined(BUILD_DEPRECATED)
|
|
// workaround to support ancient carts without palette
|
|
// load DB16 palette if it not exists
|
|
if (EMPTY(cart->bank0.palette.vbank0.data))
|
|
{
|
|
static const u8 DB16[] = { 0x14, 0x0c, 0x1c, 0x44, 0x24, 0x34, 0x30, 0x34, 0x6d, 0x4e, 0x4a, 0x4e, 0x85, 0x4c, 0x30, 0x34, 0x65, 0x24, 0xd0, 0x46, 0x48, 0x75, 0x71, 0x61, 0x59, 0x7d, 0xce, 0xd2, 0x7d, 0x2c, 0x85, 0x95, 0xa1, 0x6d, 0xaa, 0x2c, 0xd2, 0xaa, 0x99, 0x6d, 0xc2, 0xca, 0xda, 0xd4, 0x5e, 0xde, 0xee, 0xd6 };
|
|
memcpy(cart->bank0.palette.vbank0.data, DB16, sizeof DB16);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
struct CodeChunk {s32 size; const char* data;} code[TIC_BANKS] = {0};
|
|
struct BinaryChunk {s32 size; const char* data;} binary[TIC_BINARY_BANKS] = {0};
|
|
|
|
{
|
|
const u8* ptr = buffer;
|
|
while(ptr < end)
|
|
{
|
|
const Chunk* chunk = (Chunk*)ptr;
|
|
ptr += sizeof(Chunk);
|
|
|
|
switch(chunk->type)
|
|
{
|
|
case CHUNK_TILES: LOAD_CHUNK(cart->banks[chunk->bank].tiles); break;
|
|
case CHUNK_SPRITES: LOAD_CHUNK(cart->banks[chunk->bank].sprites); break;
|
|
case CHUNK_MAP: LOAD_CHUNK(cart->banks[chunk->bank].map); break;
|
|
case CHUNK_SAMPLES: LOAD_CHUNK(cart->banks[chunk->bank].sfx.samples); break;
|
|
case CHUNK_WAVEFORM: LOAD_CHUNK(cart->banks[chunk->bank].sfx.waveforms); break;
|
|
case CHUNK_MUSIC: LOAD_CHUNK(cart->banks[chunk->bank].music.tracks); break;
|
|
case CHUNK_PATTERNS: LOAD_CHUNK(cart->banks[chunk->bank].music.patterns); break;
|
|
case CHUNK_FLAGS: LOAD_CHUNK(cart->banks[chunk->bank].flags); break;
|
|
case CHUNK_SCREEN: LOAD_CHUNK(cart->banks[chunk->bank].screen); break;
|
|
case CHUNK_LANG: LOAD_CHUNK(cart->lang); break;
|
|
case CHUNK_BINARY:
|
|
binary[chunk->bank] = (struct BinaryChunk){chunkSize(chunk), ptr};
|
|
break;
|
|
case CHUNK_CODE:
|
|
code[chunk->bank] = (struct CodeChunk){chunkSize(chunk), ptr};
|
|
break;
|
|
#if defined(BUILD_DEPRECATED)
|
|
case CHUNK_CODE_ZIP:
|
|
tic_tool_unzip(cart->code.data, TIC_CODE_SIZE, ptr, chunk->size);
|
|
break;
|
|
case CHUNK_COVER_DEP:
|
|
{
|
|
// workaround to load deprecated cover section
|
|
gif_image* image = gif_read_data(ptr, chunk->size);
|
|
|
|
if (image)
|
|
{
|
|
if(image->width == TIC80_WIDTH && image->height == TIC80_HEIGHT)
|
|
for (s32 i = 0; i < TIC80_WIDTH * TIC80_HEIGHT; i++)
|
|
tic_tool_poke4(cart->bank0.screen.data, i,
|
|
tic_nearest_color(cart->bank0.palette.vbank0.colors, (const tic_rgb*)&image->palette[image->buffer[i]], TIC_PALETTE_SIZE));
|
|
|
|
gif_close(image);
|
|
}
|
|
}
|
|
break;
|
|
case CHUNK_PATTERNS_DEP:
|
|
{
|
|
// workaround to load deprecated music patterns section
|
|
// and automatically convert volume value to a command
|
|
tic_patterns* ptrns = &cart->banks[chunk->bank].music.patterns;
|
|
LOAD_CHUNK(*ptrns);
|
|
for(s32 i = 0; i < MUSIC_PATTERNS; i++)
|
|
for(s32 r = 0; r < MUSIC_PATTERN_ROWS; r++)
|
|
{
|
|
tic_track_row* row = &ptrns->data[i].rows[r];
|
|
if(row->note >= NoteStart && row->command == tic_music_cmd_empty)
|
|
{
|
|
row->command = tic_music_cmd_volume;
|
|
row->param2 = row->param1 = MAX_VOLUME - row->param1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
default: break;
|
|
}
|
|
|
|
ptr += chunkSize(chunk);
|
|
}
|
|
#undef LOAD_CHUNK
|
|
|
|
{
|
|
u32 total_size = 0;
|
|
char* ptr = cart->binary.data;
|
|
RFOR(const struct BinaryChunk*, chunk, binary)
|
|
if (chunk->size)
|
|
{
|
|
memcpy(ptr, chunk->data, chunk->size);
|
|
ptr += chunk->size;
|
|
total_size += chunk->size;
|
|
}
|
|
cart->binary.size = total_size;
|
|
}
|
|
|
|
if (!*cart->code.data)
|
|
{
|
|
char* ptr = cart->code.data;
|
|
RFOR(const struct CodeChunk*, chunk, code)
|
|
if (chunk->data)
|
|
{
|
|
memcpy(ptr, chunk->data, chunk->size);
|
|
ptr += chunk->size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static s32 calcBufferSize(const void* buffer, s32 size)
|
|
{
|
|
const u8* ptr = (u8*)buffer + size - 1;
|
|
const u8* end = (u8*)buffer;
|
|
|
|
while(ptr >= end)
|
|
{
|
|
if(*ptr) break;
|
|
|
|
ptr--;
|
|
size--;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static u8* saveFixedChunk(u8* buffer, ChunkType type, const void* from, s32 size, s32 bank)
|
|
{
|
|
if(size)
|
|
{
|
|
Chunk chunk = {.type = type, .bank = bank, .size = size, .temp = 0};
|
|
memcpy(buffer, &chunk, sizeof(Chunk));
|
|
buffer += sizeof(Chunk);
|
|
|
|
memcpy(buffer, from, size);
|
|
buffer += size;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static u8* saveChunk(u8* buffer, ChunkType type, const void* from, s32 size, s32 bank)
|
|
{
|
|
s32 chunkSize = calcBufferSize(from, size);
|
|
|
|
return saveFixedChunk(buffer, type, from, chunkSize, bank);
|
|
}
|
|
|
|
s32 tic_cart_save(const tic_cartridge* cart, u8* buffer)
|
|
{
|
|
u8* start = buffer;
|
|
|
|
#define SAVE_CHUNK(ID, FROM, BANK) saveChunk(buffer, ID, &FROM, sizeof(FROM), BANK)
|
|
|
|
tic_waveforms defaultWaveforms = {0};
|
|
tic_palettes defaultPalettes = {0};
|
|
|
|
memcpy(&defaultWaveforms, Waveforms, sizeof Waveforms);
|
|
memcpy(&defaultPalettes, Sweetie16, sizeof Sweetie16);
|
|
|
|
for(s32 i = 0; i < TIC_BANKS; i++)
|
|
{
|
|
if(memcmp(&cart->banks[i].sfx.waveforms, &defaultWaveforms, sizeof defaultWaveforms) == 0
|
|
&& memcmp(&cart->banks[i].palette, &defaultPalettes, sizeof defaultPalettes) == 0)
|
|
{
|
|
Chunk chunk = {CHUNK_DEFAULT, i, 0, 0};
|
|
memcpy(buffer, &chunk, sizeof chunk);
|
|
buffer += sizeof chunk;
|
|
}
|
|
else
|
|
{
|
|
buffer = SAVE_CHUNK(CHUNK_PALETTE, cart->banks[i].palette, i);
|
|
buffer = SAVE_CHUNK(CHUNK_WAVEFORM, cart->banks[i].sfx.waveforms, i);
|
|
}
|
|
|
|
buffer = SAVE_CHUNK(CHUNK_TILES, cart->banks[i].tiles, i);
|
|
buffer = SAVE_CHUNK(CHUNK_SPRITES, cart->banks[i].sprites, i);
|
|
buffer = SAVE_CHUNK(CHUNK_MAP, cart->banks[i].map, i);
|
|
buffer = SAVE_CHUNK(CHUNK_SAMPLES, cart->banks[i].sfx.samples, i);
|
|
buffer = SAVE_CHUNK(CHUNK_PATTERNS, cart->banks[i].music.patterns, i);
|
|
buffer = SAVE_CHUNK(CHUNK_MUSIC, cart->banks[i].music.tracks, i);
|
|
buffer = SAVE_CHUNK(CHUNK_FLAGS, cart->banks[i].flags, i);
|
|
buffer = SAVE_CHUNK(CHUNK_SCREEN, cart->banks[i].screen, i);
|
|
}
|
|
|
|
const char* ptr;
|
|
if (cart->binary.size)
|
|
{
|
|
ptr = cart->binary.data;
|
|
s32 remaining = cart->binary.size;
|
|
for (s32 i = cart->binary.size / TIC_BANK_SIZE; i >= 0; --i, ptr += TIC_BANK_SIZE)
|
|
{
|
|
buffer = saveFixedChunk(buffer, CHUNK_BINARY, ptr, MIN(remaining, TIC_BANK_SIZE), i);
|
|
remaining -= TIC_BANK_SIZE;
|
|
}
|
|
}
|
|
|
|
ptr = cart->code.data;
|
|
for(s32 i = strlen(ptr) / TIC_BANK_SIZE; i >= 0; --i, ptr += TIC_BANK_SIZE)
|
|
buffer = saveFixedChunk(buffer, CHUNK_CODE, ptr, MIN(strlen(ptr), TIC_BANK_SIZE), i);
|
|
|
|
if(cart->lang)
|
|
SAVE_CHUNK(CHUNK_LANG, cart->lang, 0);
|
|
|
|
#undef SAVE_CHUNK
|
|
|
|
return (s32)(buffer - start);
|
|
}
|