mirror of https://github.com/bsnes-emu/bsnes.git
Merge branch 'bess' into gbs
This commit is contained in:
commit
817c4a7752
34
BESS.md
34
BESS.md
|
@ -30,14 +30,30 @@ BESS uses a block format where each block contains the following header:
|
|||
|
||||
Every block is followed by another blocked, until the END block is reached. If an implementation encounters an unsupported block, it should be completely ignored (Should not have any effect and should not trigger a failure).
|
||||
|
||||
#### NAME block
|
||||
|
||||
The NAME block uses the `'NAME'` identifier, and is an optional block that contains the name of the emulator that created this save state. While optional, it is highly recommended to be included in every implementation – it allows the user to know which emulator and version is compatible with the native save state format contained in this file. When used, this block should come first.
|
||||
|
||||
The length of the NAME block is variable, and it only contains the name and version of the originating emulator in ASCII.
|
||||
|
||||
|
||||
#### INFO block
|
||||
|
||||
The INFO block uses the `'INFO'` identifier, and is an optional block that contains information about the ROM this save state originates from. When used, this block should come before `CORE` but after `NAME`. This block is 0x12 bytes long, and it follows this structure:
|
||||
|
||||
| Offset | Content |
|
||||
|--------|--------------------------------------------------|
|
||||
| 0x00 | Bytes 0x134-0x143 from the ROM (Title) |
|
||||
| 0x10 | Bytes 0x14E-0x14F from the ROM (Global checksum) |
|
||||
|
||||
#### CORE block
|
||||
|
||||
The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, except for the `NAME` block (But an implementation should allow unknown blocks to appear before it for future compatibility).
|
||||
The CORE block uses the `'CORE'` identifier, and is a required block that contains both core state information, as well as basic information about the BESS version used. This block must be the first block, unless the `NAME` or `INFO` blocks exist then it must come directly after them. An implementation should not enforce block order on blocks unknown to it for future compatibility.
|
||||
|
||||
The length of the CORE block is 0xD0 bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows:
|
||||
|
||||
| Offset | Content |
|
||||
|--------|---------------------------------------|
|
||||
| Offset | Content |
|
||||
|--------|----------------------------------------|
|
||||
| 0x00 | Major BESS version as a 16-bit integer |
|
||||
| 0x02 | Minor BESS version as a 16-bit integer |
|
||||
|
||||
|
@ -90,11 +106,11 @@ The values of memory-mapped registers should be written 'as-is' to memory as if
|
|||
| 0x9C | The offset of RAM from file start (32-bit integer) |
|
||||
| 0xA0 | The size of VRAM (32-bit integer) |
|
||||
| 0xA4 | The offset of VRAM from file start (32-bit integer) |
|
||||
| 0xA9 | The size of MBC RAM (32-bit integer) |
|
||||
| 0xA8 | The size of MBC RAM (32-bit integer) |
|
||||
| 0xAC | The offset of MBC RAM from file start (32-bit integer) |
|
||||
| 0xB0 | The size of OAM (=0xA0, 32-bit integer) |
|
||||
| 0xB4 | The offset of OAM from file start (32-bit integer) |
|
||||
| 0xB9 | The size of HRAM (=0x7F, 32-bit integer) |
|
||||
| 0xB8 | The size of HRAM (=0x7F, 32-bit integer) |
|
||||
| 0xBC | The offset of HRAM from file start (32-bit integer) |
|
||||
| 0xC0 | The size of background palettes (=0x40 or 0, 32-bit integer) |
|
||||
| 0xC4 | The offset of background palettes from file start (32-bit integer) |
|
||||
|
@ -105,12 +121,6 @@ The contents of large buffers are stored outside of BESS structure so data from
|
|||
|
||||
An implementation needs handle size mismatches gracefully. For example, if too large MBC RAM size is specified, the superfluous data should be ignored. On the other hand, if a too small VRAM size is specified (For example, if it's a save state from an emulator emulating a CGB in DMG mode, and it didn't save the second CGB VRAM bank), the implementation is expected to set that extra bank to all zeros.
|
||||
|
||||
#### NAME block
|
||||
|
||||
The NAME block uses the `'NAME'` identifier, and is an optional block that contains the name of the emulator that created this save state. While optional, it is highly recommended to be included in every implementation – it allows the user to know which emulator and version is compatible with the native save state format contained in this file. When used, this block should come first.
|
||||
|
||||
The length of the NAME block is variable, and it only contains the name and version of the originating emulator in ASCII.
|
||||
|
||||
#### XOAM block
|
||||
|
||||
The XOAM block uses the `'XOAM'` identifier, and is an optional block that contains the data of extra OAM (addresses `0xFEA0-0xFEFF`). This block length must be `0x60`. Implementations that do not emulate this extra range are free to ignore the excess bytes, and to not create this block.
|
||||
|
@ -172,7 +182,7 @@ The length of this block is 0x11 bytes long and it follows the following structu
|
|||
|
||||
The SGB block uses the `'SGB '` identifier, and is an optional block that is only used while emulating an SGB or SGB2 *and* SGB commands enabled. Implementations must not save this block on other models or when SGB commands are disabled, and should assume SGB commands are disabled if this block is missing.
|
||||
|
||||
The length of this block is 0x39 bytes and it follows the following structure:
|
||||
The length of this block is 0x39 bytes, but implementations should allow and ignore excess data in this block for extensions. The block follows the following structure:
|
||||
|
||||
| Offset | Content |
|
||||
|--------|--------------------------------------------------------------------------------------------------------------------------|
|
||||
|
|
84
Core/gb.c
84
Core/gb.c
|
@ -695,6 +695,15 @@ typedef struct {
|
|||
uint8_t padding5[3];
|
||||
} GB_vba_rtc_time_t;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint8_t mr4;
|
||||
uint8_t reserved;
|
||||
uint64_t last_rtc_second;
|
||||
uint8_t rtc_data[4];
|
||||
} GB_tpp1_rtc_save_t;
|
||||
|
||||
typedef union {
|
||||
struct __attribute__((packed)) {
|
||||
GB_rtc_time_t rtc_real;
|
||||
|
@ -712,6 +721,18 @@ typedef union {
|
|||
} vba64;
|
||||
} GB_rtc_save_t;
|
||||
|
||||
static void GB_fill_tpp1_save_data(GB_gameboy_t *gb, GB_tpp1_rtc_save_t *data)
|
||||
{
|
||||
data->magic = BE32('TPP1');
|
||||
data->version = BE16(0x100);
|
||||
data->mr4 = gb->tpp1_mr4;
|
||||
data->reserved = 0;
|
||||
data->last_rtc_second = LE64(time(NULL));
|
||||
unrolled for (unsigned i = 4; i--;) {
|
||||
data->rtc_data[i] = gb->rtc_real.data[i ^ 3];
|
||||
}
|
||||
}
|
||||
|
||||
int GB_save_battery_size(GB_gameboy_t *gb)
|
||||
{
|
||||
if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
|
||||
|
@ -722,6 +743,11 @@ int GB_save_battery_size(GB_gameboy_t *gb)
|
|||
if (gb->cartridge_type->mbc_type == GB_HUC3) {
|
||||
return gb->mbc_ram_size + sizeof(GB_huc3_rtc_time_t);
|
||||
}
|
||||
|
||||
if (gb->cartridge_type->mbc_type == GB_TPP1) {
|
||||
return gb->mbc_ram_size + sizeof(GB_tpp1_rtc_save_t);
|
||||
}
|
||||
|
||||
GB_rtc_save_t rtc_save_size;
|
||||
return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0);
|
||||
}
|
||||
|
@ -736,7 +762,13 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size)
|
|||
|
||||
memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size);
|
||||
|
||||
if (gb->cartridge_type->mbc_type == GB_HUC3) {
|
||||
if (gb->cartridge_type->mbc_type == GB_TPP1) {
|
||||
buffer += gb->mbc_ram_size;
|
||||
GB_tpp1_rtc_save_t rtc_save;
|
||||
GB_fill_tpp1_save_data(gb, &rtc_save);
|
||||
memcpy(buffer, &rtc_save, sizeof(rtc_save));
|
||||
}
|
||||
else if (gb->cartridge_type->mbc_type == GB_HUC3) {
|
||||
buffer += gb->mbc_ram_size;
|
||||
|
||||
#ifdef GB_BIG_ENDIAN
|
||||
|
@ -799,7 +831,16 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path)
|
|||
fclose(f);
|
||||
return EIO;
|
||||
}
|
||||
if (gb->cartridge_type->mbc_type == GB_HUC3) {
|
||||
if (gb->cartridge_type->mbc_type == GB_TPP1) {
|
||||
GB_tpp1_rtc_save_t rtc_save;
|
||||
GB_fill_tpp1_save_data(gb, &rtc_save);
|
||||
|
||||
if (fwrite(&rtc_save, sizeof(rtc_save), 1, f) != 1) {
|
||||
fclose(f);
|
||||
return EIO;
|
||||
}
|
||||
}
|
||||
else if (gb->cartridge_type->mbc_type == GB_HUC3) {
|
||||
#ifdef GB_BIG_ENDIAN
|
||||
GB_huc3_rtc_time_t rtc_save = {
|
||||
__builtin_bswap64(gb->last_rtc_second),
|
||||
|
@ -854,6 +895,14 @@ int GB_save_battery(GB_gameboy_t *gb, const char *path)
|
|||
return errno;
|
||||
}
|
||||
|
||||
static void GB_load_tpp1_save_data(GB_gameboy_t *gb, const GB_tpp1_rtc_save_t *data)
|
||||
{
|
||||
gb->last_rtc_second = LE64(data->last_rtc_second);
|
||||
unrolled for (unsigned i = 4; i--;) {
|
||||
gb->rtc_real.data[i ^ 3] = data->rtc_data[i];
|
||||
}
|
||||
}
|
||||
|
||||
void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
|
||||
{
|
||||
memcpy(gb->mbc_ram, buffer, MIN(gb->mbc_ram_size, size));
|
||||
|
@ -861,6 +910,22 @@ void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t
|
|||
goto reset_rtc;
|
||||
}
|
||||
|
||||
if (gb->cartridge_type->mbc_type == GB_TPP1) {
|
||||
GB_tpp1_rtc_save_t rtc_save;
|
||||
if (size - gb->mbc_ram_size < sizeof(rtc_save)) {
|
||||
goto reset_rtc;
|
||||
}
|
||||
memcpy(&rtc_save, buffer + gb->mbc_ram_size, sizeof(rtc_save));
|
||||
|
||||
GB_load_tpp1_save_data(gb, &rtc_save);
|
||||
|
||||
if (gb->last_rtc_second > time(NULL)) {
|
||||
/* We must reset RTC here, or it will not advance. */
|
||||
goto reset_rtc;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (gb->cartridge_type->mbc_type == GB_HUC3) {
|
||||
GB_huc3_rtc_time_t rtc_save;
|
||||
if (size - gb->mbc_ram_size < sizeof(rtc_save)) {
|
||||
|
@ -970,6 +1035,21 @@ void GB_load_battery(GB_gameboy_t *gb, const char *path)
|
|||
goto reset_rtc;
|
||||
}
|
||||
|
||||
if (gb->cartridge_type->mbc_type == GB_TPP1) {
|
||||
GB_tpp1_rtc_save_t rtc_save;
|
||||
if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) {
|
||||
goto reset_rtc;
|
||||
}
|
||||
|
||||
GB_load_tpp1_save_data(gb, &rtc_save);
|
||||
|
||||
if (gb->last_rtc_second > time(NULL)) {
|
||||
/* We must reset RTC here, or it will not advance. */
|
||||
goto reset_rtc;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (gb->cartridge_type->mbc_type == GB_HUC3) {
|
||||
GB_huc3_rtc_time_t rtc_save;
|
||||
if (fread(&rtc_save, sizeof(rtc_save), 1, f) != 1) {
|
||||
|
|
12
Core/gb.h
12
Core/gb.h
|
@ -98,6 +98,13 @@ typedef union {
|
|||
uint8_t days;
|
||||
uint8_t high;
|
||||
};
|
||||
struct {
|
||||
uint8_t seconds;
|
||||
uint8_t minutes;
|
||||
uint8_t hours:5;
|
||||
uint8_t weekday:3;
|
||||
uint8_t weeks;
|
||||
} tpp1;
|
||||
uint8_t data[5];
|
||||
} GB_rtc_time_t;
|
||||
|
||||
|
@ -538,6 +545,7 @@ struct GB_gameboy_internal_s {
|
|||
uint64_t last_rtc_second;
|
||||
bool rtc_latch;
|
||||
uint32_t rtc_cycles;
|
||||
uint8_t tpp1_mr4;
|
||||
);
|
||||
|
||||
/* Video Display */
|
||||
|
@ -806,8 +814,6 @@ void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *
|
|||
void *GB_get_user_data(GB_gameboy_t *gb);
|
||||
void GB_set_user_data(GB_gameboy_t *gb, void *data);
|
||||
|
||||
|
||||
|
||||
int GB_load_boot_rom(GB_gameboy_t *gb, const char *path);
|
||||
void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size);
|
||||
int GB_load_rom(GB_gameboy_t *gb, const char *path);
|
||||
|
@ -877,5 +883,5 @@ unsigned GB_get_screen_width(GB_gameboy_t *gb);
|
|||
unsigned GB_get_screen_height(GB_gameboy_t *gb);
|
||||
double GB_get_usual_frame_rate(GB_gameboy_t *gb);
|
||||
unsigned GB_get_player_count(GB_gameboy_t *gb);
|
||||
|
||||
|
||||
#endif /* GB_h */
|
||||
|
|
|
@ -185,30 +185,13 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
|
|||
case 0: return gb->tpp1_rom_bank;
|
||||
case 1: return gb->tpp1_rom_bank >> 8;
|
||||
case 2: return gb->tpp1_ram_bank;
|
||||
case 3: return gb->rumble_strength | (((gb->rtc_real.high & 0xC0) ^ 0x40) >> 4);
|
||||
case 3: return gb->rumble_strength | gb->tpp1_mr4;
|
||||
}
|
||||
case 2:
|
||||
case 3:
|
||||
break; // Read RAM
|
||||
case 5:
|
||||
switch (addr & 3) {
|
||||
case 0: { // Week count
|
||||
unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days);
|
||||
if (gb->rtc_latched.high & 0x20) {
|
||||
return total_days / 7 - 1;
|
||||
}
|
||||
return total_days / 7;
|
||||
}
|
||||
case 1: { // Week count
|
||||
unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days);
|
||||
if (gb->rtc_latched.high & 0x20) {
|
||||
return gb->rtc_latched.hours | 0xe0; // Hours and weekday
|
||||
}
|
||||
return gb->rtc_latched.hours | ((total_days % 7) << 5); // Hours and weekday
|
||||
}
|
||||
case 2: return gb->rtc_latched.minutes;
|
||||
case 3: return gb->rtc_latched.seconds;
|
||||
}
|
||||
return gb->rtc_latched.data[(addr & 3) ^ 3];
|
||||
default:
|
||||
return 0xFF;
|
||||
}
|
||||
|
@ -625,20 +608,17 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real));
|
||||
break;
|
||||
case 0x11: {
|
||||
uint8_t flags = gb->rtc_real.high & 0xc0;
|
||||
memcpy(&gb->rtc_real, &gb->rtc_latched, sizeof(gb->rtc_real));
|
||||
gb->rtc_real.high &= ~0xe0;
|
||||
gb->rtc_real.high |= flags;
|
||||
break;
|
||||
}
|
||||
case 0x14:
|
||||
gb->rtc_real.high &= ~0x80;
|
||||
gb->tpp1_mr4 &= ~0x8;
|
||||
break;
|
||||
case 0x18:
|
||||
gb->rtc_real.high |= 0x40;
|
||||
gb->tpp1_mr4 &= ~0x4;
|
||||
break;
|
||||
case 0x19:
|
||||
gb->rtc_real.high &= ~0x40;
|
||||
gb->tpp1_mr4 |= 0x4;
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
|
@ -776,32 +756,7 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
case 3:
|
||||
break;
|
||||
case 5:
|
||||
switch (addr & 3) {
|
||||
case 0: {
|
||||
unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days);
|
||||
total_days = total_days % 7 + value * 7;
|
||||
bool had_illegal_weekday = gb->rtc_latched.high & 0x20;
|
||||
gb->rtc_latched.days = total_days;
|
||||
gb->rtc_latched.high = total_days >> 8;
|
||||
if (had_illegal_weekday) {
|
||||
gb->rtc_latched.high |= 0x20;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 1: {
|
||||
unsigned total_days = (((gb->rtc_latched.high & 7) << 8) + gb->rtc_latched.days);
|
||||
total_days = total_days / 7 * 7 + (value >> 5);
|
||||
gb->rtc_latched.hours = value & 0x1F;
|
||||
gb->rtc_latched.days = total_days;
|
||||
gb->rtc_latched.high = total_days >> 8;
|
||||
if ((value & 0xE0) == 0xE0) { // Illegal weekday
|
||||
gb->rtc_latched.high |= 0x20;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 2: gb->rtc_latched.minutes = value; return;
|
||||
case 3: gb->rtc_latched.seconds = value; return;
|
||||
}
|
||||
gb->rtc_latched.data[(addr & 3) ^ 3] = value;
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
|
|
|
@ -82,6 +82,12 @@ typedef struct __attribute__((packed)) {
|
|||
uint8_t multiplayer_state;
|
||||
} BESS_SGB_t;
|
||||
|
||||
typedef struct __attribute__((packed)){
|
||||
BESS_block_t header;
|
||||
char title[0x10];
|
||||
uint8_t checksum[2];
|
||||
} BESS_INFO_t;
|
||||
|
||||
/* Same RTC format as used by VBA, BGB and SameBoy in battery saves*/
|
||||
typedef struct __attribute__((packed)){
|
||||
BESS_block_t header;
|
||||
|
@ -106,6 +112,14 @@ typedef struct __attribute__((packed)){
|
|||
GB_huc3_rtc_time_t data;
|
||||
} BESS_HUC3_t;
|
||||
|
||||
typedef struct __attribute__((packed)){
|
||||
BESS_block_t header;
|
||||
uint64_t last_rtc_second;
|
||||
uint8_t real_rtc_data[4];
|
||||
uint8_t latched_rtc_data[4];
|
||||
uint8_t mr4;
|
||||
} BESS_TPP1_t;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t address;
|
||||
uint8_t value;
|
||||
|
@ -216,7 +230,7 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart)
|
|||
case GB_HUC3:
|
||||
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_HUC3_t);
|
||||
case GB_TPP1:
|
||||
return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_RTC_t);
|
||||
return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t) + sizeof(BESS_TPP1_t);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,12 +255,13 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb)
|
|||
{
|
||||
return GB_get_save_state_size_no_bess(gb) +
|
||||
// BESS
|
||||
+ sizeof(BESS_CORE_t)
|
||||
+ sizeof(BESS_block_t) // NAME
|
||||
+ sizeof(BESS_NAME) - 1
|
||||
+ sizeof(BESS_INFO_t) // INFO
|
||||
+ sizeof(BESS_CORE_t)
|
||||
+ sizeof(BESS_XOAM_t)
|
||||
+ (gb->sgb? sizeof(BESS_SGB_t) : 0)
|
||||
+ bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3 block
|
||||
+ bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3/TPP1 block
|
||||
+ sizeof(BESS_block_t) // END block
|
||||
+ sizeof(BESS_footer_t);
|
||||
}
|
||||
|
@ -530,6 +545,22 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
|
|||
goto error;
|
||||
}
|
||||
|
||||
/* BESS INFO */
|
||||
|
||||
static const BESS_block_t bess_info = {BE32('INFO'), LE32(sizeof(BESS_INFO_t) - sizeof(BESS_block_t))};
|
||||
|
||||
if (file->write(file, &bess_info, sizeof(bess_info)) != sizeof(bess_info)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (file->write(file, gb->rom + 0x134, 0x10) != 0x10) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (file->write(file, gb->rom + 0x14e, 2) != 2) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* BESS CORE */
|
||||
|
||||
bess_core.header = (BESS_block_t){BE32('CORE'), LE32(sizeof(bess_core) - sizeof(bess_core.header))};
|
||||
|
@ -607,7 +638,22 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool appe
|
|||
|
||||
save_bess_mbc_block(gb, file);
|
||||
if (gb->cartridge_type->has_rtc) {
|
||||
if (gb->cartridge_type ->mbc_type != GB_HUC3) {
|
||||
if (gb->cartridge_type ->mbc_type == GB_TPP1) {
|
||||
BESS_TPP1_t bess_tpp1 = {0,};
|
||||
bess_tpp1.header = (BESS_block_t){BE32('TPP1'), LE32(sizeof(bess_tpp1) - sizeof(bess_tpp1.header))};
|
||||
|
||||
bess_tpp1.last_rtc_second = LE64(gb->last_rtc_second);
|
||||
unrolled for (unsigned i = 4; i--;) {
|
||||
bess_tpp1.real_rtc_data[i] = gb->rtc_real.data[i ^ 3];
|
||||
bess_tpp1.latched_rtc_data[i] = gb->rtc_latched.data[i ^ 3];
|
||||
}
|
||||
bess_tpp1.mr4 = gb->tpp1_mr4;
|
||||
|
||||
if (file->write(file, &bess_tpp1, sizeof(bess_tpp1)) != sizeof(bess_tpp1)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else if (gb->cartridge_type ->mbc_type != GB_HUC3) {
|
||||
BESS_RTC_t bess_rtc = {0,};
|
||||
bess_rtc.header = (BESS_block_t){BE32('RTC '), LE32(sizeof(bess_rtc) - sizeof(bess_rtc.header))};
|
||||
bess_rtc.real.seconds = gb->rtc_real.seconds;
|
||||
|
@ -936,6 +982,23 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
|
|||
file->read(file, emulator_name, LE32(block.size));
|
||||
}
|
||||
break;
|
||||
case BE32('INFO'): {
|
||||
BESS_INFO_t bess_info = {0,};
|
||||
if (LE32(block.size) != sizeof(bess_info) - sizeof(block)) goto parse_error;
|
||||
if (file->read(file, &bess_info.header + 1, LE32(block.size)) != LE32(block.size)) goto error;
|
||||
if (memcmp(bess_info.title, gb->rom + 0x134, sizeof(bess_info.title))) {
|
||||
char ascii_title[0x11] = {0,};
|
||||
for (unsigned i = 0; i < 0x10; i++) {
|
||||
if (bess_info.title[i] < 0x20 || bess_info.title[i] > 0x7E) break;
|
||||
ascii_title[i] = bess_info.title[i];
|
||||
}
|
||||
GB_log(gb, "Save state was made on another ROM: '%s'\n", ascii_title);
|
||||
}
|
||||
else if (memcmp(bess_info.checksum, gb->rom + 0x14E, 2)) {
|
||||
GB_log(gb, "Save state was potentially made on another revision of the same ROM.\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BE32('XOAM'):
|
||||
if (!found_core) goto parse_error;
|
||||
if (LE32(block.size) != 96) goto parse_error;
|
||||
|
@ -957,39 +1020,62 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
|
|||
BESS_RTC_t bess_rtc;
|
||||
if (LE32(block.size) != sizeof(bess_rtc) - sizeof(block)) goto parse_error;
|
||||
if (file->read(file, &bess_rtc.header + 1, LE32(block.size)) != LE32(block.size)) goto error;
|
||||
if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type != GB_HUC3) {
|
||||
gb->rtc_real.seconds = bess_rtc.real.seconds;
|
||||
gb->rtc_real.minutes = bess_rtc.real.minutes;
|
||||
gb->rtc_real.hours = bess_rtc.real.hours;
|
||||
gb->rtc_real.days = bess_rtc.real.days;
|
||||
gb->rtc_real.high = bess_rtc.real.high;
|
||||
gb->rtc_latched.seconds = bess_rtc.latched.seconds;
|
||||
gb->rtc_latched.minutes = bess_rtc.latched.minutes;
|
||||
gb->rtc_latched.hours = bess_rtc.latched.hours;
|
||||
gb->rtc_latched.days = bess_rtc.latched.days;
|
||||
gb->rtc_latched.high = bess_rtc.latched.high;
|
||||
gb->last_rtc_second = LE64(bess_rtc.last_rtc_second);
|
||||
if (!gb->cartridge_type->has_rtc || gb->cartridge_type->mbc_type != GB_MBC3) break;
|
||||
save.rtc_real.seconds = bess_rtc.real.seconds;
|
||||
save.rtc_real.minutes = bess_rtc.real.minutes;
|
||||
save.rtc_real.hours = bess_rtc.real.hours;
|
||||
save.rtc_real.days = bess_rtc.real.days;
|
||||
save.rtc_real.high = bess_rtc.real.high;
|
||||
save.rtc_latched.seconds = bess_rtc.latched.seconds;
|
||||
save.rtc_latched.minutes = bess_rtc.latched.minutes;
|
||||
save.rtc_latched.hours = bess_rtc.latched.hours;
|
||||
save.rtc_latched.days = bess_rtc.latched.days;
|
||||
save.rtc_latched.high = bess_rtc.latched.high;
|
||||
if (gb->rtc_mode == GB_RTC_MODE_SYNC_TO_HOST) {
|
||||
save.last_rtc_second = MIN(LE64(bess_rtc.last_rtc_second), time(NULL));
|
||||
}
|
||||
|
||||
break;
|
||||
case BE32('HUC3'):
|
||||
if (!found_core) goto parse_error;
|
||||
BESS_HUC3_t bess_huc3;
|
||||
if (LE32(block.size) != sizeof(bess_huc3) - sizeof(block)) goto parse_error;
|
||||
if (file->read(file, &bess_huc3.header + 1, LE32(block.size)) != LE32(block.size)) goto error;
|
||||
if (gb->cartridge_type->mbc_type == GB_HUC3) {
|
||||
gb->last_rtc_second = LE64(bess_huc3.data.last_rtc_second);
|
||||
gb->huc3_minutes = LE16(bess_huc3.data.minutes);
|
||||
gb->huc3_days = LE16(bess_huc3.data.days);
|
||||
gb->huc3_alarm_minutes = LE16(bess_huc3.data.alarm_minutes);
|
||||
gb->huc3_alarm_days = LE16(bess_huc3.data.alarm_days);
|
||||
gb->huc3_alarm_enabled = bess_huc3.data.alarm_enabled;
|
||||
if (gb->cartridge_type->mbc_type != GB_HUC3) break;
|
||||
if (gb->rtc_mode == GB_RTC_MODE_SYNC_TO_HOST) {
|
||||
save.last_rtc_second = MIN(LE64(bess_huc3.data.last_rtc_second), time(NULL));
|
||||
}
|
||||
save.huc3_minutes = LE16(bess_huc3.data.minutes);
|
||||
save.huc3_days = LE16(bess_huc3.data.days);
|
||||
save.huc3_alarm_minutes = LE16(bess_huc3.data.alarm_minutes);
|
||||
save.huc3_alarm_days = LE16(bess_huc3.data.alarm_days);
|
||||
save.huc3_alarm_enabled = bess_huc3.data.alarm_enabled;
|
||||
break;
|
||||
case BE32('TPP1'):
|
||||
if (!found_core) goto parse_error;
|
||||
BESS_TPP1_t bess_tpp1;
|
||||
if (LE32(block.size) != sizeof(bess_tpp1) - sizeof(block)) goto parse_error;
|
||||
if (file->read(file, &bess_tpp1.header + 1, LE32(block.size)) != LE32(block.size)) goto error;
|
||||
if (gb->cartridge_type->mbc_type != GB_TPP1) break;
|
||||
if (gb->rtc_mode == GB_RTC_MODE_SYNC_TO_HOST) {
|
||||
save.last_rtc_second = MIN(LE64(bess_tpp1.last_rtc_second), time(NULL));
|
||||
}
|
||||
unrolled for (unsigned i = 4; i--;) {
|
||||
save.rtc_real.data[i ^ 3] = bess_tpp1.real_rtc_data[i];
|
||||
save.rtc_latched.data[i ^ 3] = bess_tpp1.latched_rtc_data[i];
|
||||
}
|
||||
save.tpp1_mr4 = bess_tpp1.mr4;
|
||||
break;
|
||||
case BE32('SGB '):
|
||||
if (!found_core) goto parse_error;
|
||||
if (!gb->sgb) goto parse_error;
|
||||
if (LE32(block.size) != sizeof(BESS_SGB_t) - sizeof(block)) goto parse_error;
|
||||
file->read(file, &sgb.header + 1, sizeof(BESS_SGB_t) - sizeof(block));
|
||||
if (LE32(block.size) > sizeof(sgb) - sizeof(block)) {
|
||||
if (file->read(file, &sgb.header + 1, sizeof(sgb) - sizeof(block)) != sizeof(sgb) - sizeof(block)) goto error;
|
||||
file->seek(file, LE32(block.size) - (sizeof(sgb) - sizeof(block)), SEEK_CUR);
|
||||
}
|
||||
else {
|
||||
if (file->read(file, &sgb.header + 1, LE32(block.size)) != LE32(block.size)) goto error;
|
||||
}
|
||||
found_sgb = true;
|
||||
break;
|
||||
case BE32('END '):
|
||||
|
|
|
@ -267,7 +267,6 @@ static void command_ready(GB_gameboy_t *gb)
|
|||
#endif
|
||||
uint8_t x = command->x;
|
||||
uint8_t y = command->y;
|
||||
count = MIN(count, 20 * 18);
|
||||
if (x >= 20 || y >= 18) {
|
||||
/* TODO: Verify with the SFC BIOS */
|
||||
break;
|
||||
|
@ -282,7 +281,7 @@ static void command_ready(GB_gameboy_t *gb)
|
|||
x++;
|
||||
y = 0;
|
||||
if (x == 20) {
|
||||
x = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -292,7 +291,7 @@ static void command_ready(GB_gameboy_t *gb)
|
|||
y++;
|
||||
x = 0;
|
||||
if (y == 18) {
|
||||
y = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -283,27 +283,31 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles)
|
|||
}
|
||||
return;
|
||||
}
|
||||
bool running = false;
|
||||
if (gb->cartridge_type->mbc_type == GB_TPP1) {
|
||||
running = gb->tpp1_mr4 & 0x4;
|
||||
}
|
||||
else {
|
||||
running = (gb->rtc_real.high & 0x40) == 0;
|
||||
}
|
||||
|
||||
if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */
|
||||
if (running) { /* is timer running? */
|
||||
while (gb->last_rtc_second + 60 * 60 * 24 < current_time) {
|
||||
gb->last_rtc_second += 60 * 60 * 24;
|
||||
if (++gb->rtc_real.days == 0) {
|
||||
if (gb->cartridge_type->mbc_type == GB_TPP1) {
|
||||
if ((gb->rtc_real.high & 7) >= 6) { /* Bit 8 of days*/
|
||||
gb->rtc_real.high &= 0x40;
|
||||
gb->rtc_real.high |= 0x80; /* Overflow bit */
|
||||
}
|
||||
else {
|
||||
gb->rtc_real.high++;
|
||||
if (gb->cartridge_type->mbc_type == GB_TPP1) {
|
||||
if (++gb->rtc_real.tpp1.weekday == 7) {
|
||||
gb->rtc_real.tpp1.weekday = 0;
|
||||
if (++gb->rtc_real.tpp1.weeks == 0) {
|
||||
gb->tpp1_mr4 |= 8; /* Overflow bit */
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (gb->rtc_real.high & 1) { /* Bit 8 of days*/
|
||||
gb->rtc_real.high |= 0x80; /* Overflow bit */
|
||||
}
|
||||
|
||||
gb->rtc_real.high ^= 1;
|
||||
}
|
||||
else if (++gb->rtc_real.days == 0) {
|
||||
if (gb->rtc_real.high & 1) { /* Bit 8 of days*/
|
||||
gb->rtc_real.high |= 0x80; /* Overflow bit */
|
||||
}
|
||||
|
||||
gb->rtc_real.high ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -315,21 +319,21 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles)
|
|||
if (++gb->rtc_real.minutes != 60) continue;
|
||||
gb->rtc_real.minutes = 0;
|
||||
|
||||
if (++gb->rtc_real.hours != 24) continue;
|
||||
gb->rtc_real.hours = 0;
|
||||
|
||||
if (++gb->rtc_real.days != 0) continue;
|
||||
|
||||
if (gb->cartridge_type->mbc_type == GB_TPP1) {
|
||||
if ((gb->rtc_real.high & 7) >= 6) { /* Bit 8 of days*/
|
||||
gb->rtc_real.high &= 0x40;
|
||||
gb->rtc_real.high |= 0x80; /* Overflow bit */
|
||||
}
|
||||
else {
|
||||
gb->rtc_real.high++;
|
||||
if (++gb->rtc_real.tpp1.hours != 24) continue;
|
||||
gb->rtc_real.tpp1.hours = 0;
|
||||
if (++gb->rtc_real.tpp1.weekday != 7) continue;
|
||||
gb->rtc_real.tpp1.weekday = 0;
|
||||
if (++gb->rtc_real.tpp1.weeks == 0) {
|
||||
gb->tpp1_mr4 |= 8; /* Overflow bit */
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (++gb->rtc_real.hours != 24) continue;
|
||||
gb->rtc_real.hours = 0;
|
||||
|
||||
if (++gb->rtc_real.days != 0) continue;
|
||||
|
||||
if (gb->rtc_real.high & 1) { /* Bit 8 of days*/
|
||||
gb->rtc_real.high |= 0x80; /* Overflow bit */
|
||||
}
|
||||
|
|
19
SDL/main.c
19
SDL/main.c
|
@ -58,7 +58,7 @@ static void start_capturing_logs(void)
|
|||
GB_set_log_callback(&gb, log_capture_callback);
|
||||
}
|
||||
|
||||
static const char *end_capturing_logs(bool show_popup, bool should_exit)
|
||||
static const char *end_capturing_logs(bool show_popup, bool should_exit, uint32_t popup_flags)
|
||||
{
|
||||
GB_set_log_callback(&gb, NULL);
|
||||
if (captured_log[0] == 0) {
|
||||
|
@ -67,7 +67,7 @@ static const char *end_capturing_logs(bool show_popup, bool should_exit)
|
|||
}
|
||||
else {
|
||||
if (show_popup) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", captured_log, window);
|
||||
SDL_ShowSimpleMessageBox(popup_flags, "Error", captured_log, window);
|
||||
}
|
||||
if (should_exit) {
|
||||
exit(1);
|
||||
|
@ -430,20 +430,21 @@ static bool handle_pending_command(void)
|
|||
replace_extension(filename, strlen(filename), save_path, save_extension);
|
||||
|
||||
start_capturing_logs();
|
||||
bool success;
|
||||
if (pending_command == GB_SDL_LOAD_STATE_COMMAND) {
|
||||
GB_load_state(&gb, save_path);
|
||||
success = GB_load_state(&gb, save_path) == 0;
|
||||
}
|
||||
else {
|
||||
GB_save_state(&gb, save_path);
|
||||
success = GB_save_state(&gb, save_path) == 0;
|
||||
}
|
||||
end_capturing_logs(true, false);
|
||||
end_capturing_logs(true, false, success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
case GB_SDL_LOAD_STATE_FROM_FILE_COMMAND:
|
||||
start_capturing_logs();
|
||||
GB_load_state(&gb, dropped_state_file);
|
||||
end_capturing_logs(true, false);
|
||||
bool success = GB_load_state(&gb, dropped_state_file) == 0;
|
||||
end_capturing_logs(true, false, success? SDL_MESSAGEBOX_INFORMATION : SDL_MESSAGEBOX_ERROR);
|
||||
SDL_free(dropped_state_file);
|
||||
return false;
|
||||
|
||||
|
@ -483,7 +484,7 @@ static void load_boot_rom(GB_gameboy_t *gb, GB_boot_rom_t type)
|
|||
if (use_built_in) {
|
||||
start_capturing_logs();
|
||||
GB_load_boot_rom(gb, resource_path(names[type]));
|
||||
end_capturing_logs(true, false);
|
||||
end_capturing_logs(true, false, SDL_MESSAGEBOX_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -556,7 +557,7 @@ restart:
|
|||
else {
|
||||
GB_load_rom(&gb, filename);
|
||||
}
|
||||
end_capturing_logs(true, error);
|
||||
end_capturing_logs(true, error, SDL_MESSAGEBOX_WARNING);
|
||||
|
||||
|
||||
/* Configure battery */
|
||||
|
|
Loading…
Reference in New Issue