Merge branch 'bess' into gbs

This commit is contained in:
Lior Halphon 2021-04-14 16:44:51 +03:00
commit ba6e22dfc0
22 changed files with 526 additions and 150 deletions

90
BESS.md
View File

@ -28,13 +28,13 @@ BESS uses a block format where each block contains the following header:
| 0 | A four-letter ASCII identifier |
| 4 | Length of the block, excluding header |
Every block is followed by another blocked, until the END block is reached.
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).
#### 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.
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 length of the CORE block is 0xCF bytes, but implementations are expected to ignore any excess bytes. Following the BESS block header, the structure is as follows:
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 |
|--------|---------------------------------------|
@ -54,10 +54,10 @@ BESS uses a four-character string to identify Game Boy models:
* The third letter represents a specific CPU revision within a model, and is optional (If an implementation does not distinguish between revisions, a space character may be used). The allowed values for model GD (DMG) are `'0'` and `'A'`, through `'C'`; the allowed values for model CC (CGB) are `'0'` and `'A'`, through `'E'`; the allowed values for model CA (AGB, AGS, GBP) are `'0'`, `'A'` and `'B'`; and for every other model this value must be a space character.
* The last character is used for padding and must be a space character.
For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `CCE ` represent a CGB using CPU revision E.
For example; `'GD '` represents a DMG of an unspecified revision, `'S '` represents some model of the SGB family, and `'CCE '` represent a CGB using CPU revision E.
| Offset | Content |
|--------|-------------------------------------------------------|
|--------|--------------------------------------------------------|
| 0x08 | The value of the PC register |
| 0x0A | The value of the AF register |
| 0x0C | The value of the BC register |
@ -67,13 +67,16 @@ For example; `'GD '` represents a DMG of an unspecified revision, `'S '` repr
| 0x14 | The value of IME (0 or 1) |
| 0x15 | The value of the IE register |
| 0x16 | Execution state (0 = running; 1 = halted; 2 = stopped) |
| 0x17 | The values of every memory-mapped register (128 bytes) |
| 0x17 | Reserved, must be 0 |
| 0x18 | The values of every memory-mapped register (128 bytes) |
The values of memory-mapped registers should be written 'as-is' to memory as if the actual ROM wrote them, with the following exceptions and note:
* Unused registers have Don't-Care values which should be ignored
* Unused register bits have Don't-Care values which should be ignored
* The value of KEY0 (FF4C) must be valid as it determines CGB mode
* If the model is CGB or newer, the value of KEY0 (FF4C) must be valid as it determines DMG mode
* Bit 2 determines DMG mode. A value of 0x04 usually denotes DMG mode, while a value of `0x80` usually denotes CGB mode.
* Sprite priority is derived from KEY0 (FF4C) instead of OPRI (FF6C) because OPRI can be modified after booting, but only the value of OPRI during boot ROM execution takes effect
* If a register doesn't exist on the emulated model (For example, KEY0 (FF4C) on a DMG), its value should be ignored.
* BANK (FF50) should be 0 if the boot ROM is still mapped, and 1 otherwise, and must be valid.
* Implementations should not start a serial transfer when writing the value of SB
* Similarly, no value of NRx4 should trigger a sound pulse on save state load
@ -81,25 +84,26 @@ The values of memory-mapped registers should be written 'as-is' to memory as if
* The value store for DIV will be used to set the internal divisor to `DIV << 8`
* Implementation should apply care when ordering the write operations (For example, writes to NR52 must come before writes to the other APU registers)
|Offset | Content |
|-------|--------------------------------------------------------------------|
| 0x97 | The size of RAM (32-bit integer) |
| 0x9B | The offset of RAM from file start (32-bit integer) |
| 0x9F | The size of VRAM (32-bit integer) |
| 0xA3 | The offset of VRAM from file start (32-bit integer) |
| 0xA7 | The size of MBC RAM (32-bit integer) |
| 0xAB | The offset of MBC RAM from file start (32-bit integer) |
| 0xAF | The size of OAM (=0xA0, 32-bit integer) |
| 0xB3 | The offset of OAM from file start (32-bit integer) |
| 0xB7 | The size of HRAM (=0x7F, 32-bit integer) |
| 0xBB | The offset of HRAM from file start (32-bit integer) |
| 0xBF | The size of background palettes (=0x40 or 0, 32-bit integer) |
| 0xC3 | The offset of background palettes from file start (32-bit integer) |
| 0xC7 | The size of object palettes (=0x40 or 0, 32-bit integer) |
| 0xCB | The offset of object palettes from file start (32-bit integer) |
| Offset | Content |
|--------|--------------------------------------------------------------------|
| 0x98 | The size of RAM (32-bit integer) |
| 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) |
| 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) |
| 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) |
| 0xC8 | The size of object palettes (=0x40 or 0, 32-bit integer) |
| 0xCC | The offset of object palettes from file start (32-bit integer) |
The contents of large buffers are stored outside of BESS structure so data from an implementation's native save state format can be reused. The offsets are absolute offsets from the save state file's start. Background and object palette sizes must be 0 for models prior to Game Boy Color.
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
@ -129,14 +133,14 @@ This block contains an MBC-specific number of 3-byte-long pairs that represent t
| 0x9 | The value 0x4000 as a 16-bit integer |
| 0xB | The current RAM bank |
An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` range are not allowed.
An implementation should parse this block as a series of writes to be made. Values outside the `0x0000-0x7FFF` and `0xA000-0xBFFF` ranges are not allowed. Implementations must perform the writes in order (i.e. not reverse, sorted, or any other transformation on their order)
#### RTC block
The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB.
The RTC block uses the `'RTC '` identifier, and is an optional block that is used while emulating an MBC3 with an RTC. The contents of this block are identical to 64-bit RTC saves from VBA, which are also used by SameBoy and different emulators such as BGB.
The length of this block is 0x30 bytes long and it follows the following structure:
|Offset | Content |
| Offset | Content |
|--------|------------------------------------------------------------------------|
| 0x00 | Current seconds (1 byte), followed by 3 bytes of padding |
| 0x04 | Current minutes (1 byte), followed by 3 bytes of padding |
@ -150,14 +154,28 @@ The length of this block is 0x30 bytes long and it follows the following structu
| 0x24 | Latched high/overflow/running (1 byte), followed by 3 bytes of padding |
| 0x28 | UNIX timestamp at the time of the save state (64-bit) |
#### HUC3 block
The HUC3 block uses the `'HUC3'` identifier, and is an optional block that is used while emulating an HuC3 cartridge to store RTC and alarm information. The contents of this block are identical to HuC3 RTC saves from SameBoy.
The length of this block is 0x11 bytes long and it follows the following structure:
| Offset | Content |
|--------|-------------------------------------------------------|
| 0x00 | UNIX timestamp at the time of the save state (64-bit) |
| 0x08 | RTC minutes (16-bit) |
| 0x0A | RTC days (16-bit) |
| 0x0C | Scheduled alarm time minutes (16-bit) |
| 0x0E | Scheduled alarm time days (16-bit) |
| 0x10 | Alarm enabled flag (8-bits, either 0 or 1) |
#### SGB block
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:
|Offset | Content |
|--------|--------------------------------------------------------------------------------------------------|
| Offset | Content |
|--------|--------------------------------------------------------------------------------------------------------------------------|
| 0x00 | The size of the border tile data (=0x2000, 32-bit integer) |
| 0x04 | The offset of the border tile data (SNES tile format, 32-bit integer) |
| 0x08 | The size of the border tilemap (=0x800, 32-bit integer) |
@ -170,12 +188,22 @@ The length of this block is 0x39 bytes and it follows the following structure:
| 0x24 | The offset of the RAM colorization palettes (LE 16-bit sequences, 32-bit integer) |
| 0x28 | The size of the attribute map (=0x168, 32-bit integer) |
| 0x2C | The offset of the attribute map (32-bit integer) |
| 0x30 | The size of the attribute files (=0xfe0, 32-bit integer) |
| 0x30 | The size of the attribute files (=0xfd2, 32-bit integer) |
| 0x34 | The offset of the attribute files (32-bit integer) |
| 0x38 | Multiplayer status (1 byte); high nibble is player count, low nibble is current player (0-based) |
| 0x38 | Multiplayer status (1 byte); high nibble is player count (1, 2 or 4), low nibble is current player (Where Player 1 is 0) |
If only some of the size-offset pairs are available (for example, partial HLE SGB implementation), missing fields are allowed to have 0 as their size, and implementations are expected to fall back to a sane default.
#### END block
The END block uses the `'END '` identifier, and is a required block that marks the end of BESS data. Naturally, it must be the last block.
The END block uses the `'END '` identifier, and is a required block that marks the end of BESS data. Naturally, it must be the last block. The length of the END block must be 0.
## Validation and Failures
Other than previously specified required fail conditions, an implementation is free to decide what format errors should abort the loading of a save file. Structural errors (e.g. a block with an invalid length, a file offset that is outside the file's range, or a missing END block) should be considered as irrecoverable errors. Other errors that are considered fatal by SameBoy's implementation:
* Duplicate CORE block
* A known block, other than NAME, appearing before CORE
* An invalid length for the XOAM, RTC, SGB or HUC3 blocks
* An invalid length of MBC (not a multiple of 3)
* A write outside the $0000-$7FFF and $A000-$BFFF ranges in the MBC block
* An SGB block on a save state targeting another model
* An END block with non-zero length

View File

@ -54,5 +54,6 @@
-(void) writeMemory:(uint16_t) addr value:(uint8_t)value;
-(void) performAtomicBlock: (void (^)())block;
-(void) connectLinkCable:(NSMenuItem *)sender;
- (bool)loadStateFile:(const char *)path;
@end

View File

@ -1263,12 +1263,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
}
}
- (IBAction)loadState:(id)sender
- (bool)loadStateFile:(const char *)path
{
bool __block success = false;
NSString *error =
[self captureOutputForBlock:^{
success = GB_load_state(&gb, [[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String) == 0;
success = GB_load_state(&gb, path) == 0;
}];
if (!success) {
@ -1277,6 +1277,12 @@ static unsigned *multiplication_table_for_frequency(unsigned frequency)
if (error) {
[GBWarningPopover popoverWithContents:error onWindow:self.mainWindow];
}
return success;
}
- (IBAction)loadState:(id)sender
{
[self loadStateFile:[[self.fileURL URLByDeletingPathExtension] URLByAppendingPathExtension:[NSString stringWithFormat:@"s%ld", (long)[sender tag] ]].path.UTF8String];
}
- (IBAction)clearConsole:(id)sender

View File

@ -142,6 +142,8 @@ static const uint8_t workboy_vk_to_key[] = {
- (void) _init
{
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil];
tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){}
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect
@ -626,4 +628,29 @@ static const uint8_t workboy_vk_to_key[] = {
return image_buffers[(current_buffer + 2) % self.numberOfBuffers];
}
-(NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSURLPboardType] ) {
NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
if (GB_is_stave_state(fileURL.fileSystemRepresentation)) {
return NSDragOperationGeneric;
}
}
return NSDragOperationNone;
}
-(BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSURLPboardType] ) {
NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
return [_document loadStateFile:fileURL.fileSystemRepresentation];
}
return false;
}
@end

View File

@ -1523,7 +1523,9 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
const GB_cartridge_t *cartridge = gb->cartridge_type;
if (cartridge->has_ram) {
GB_log(gb, "Cartridge includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size);
bool has_battery = gb->cartridge_type->has_battery &&
(gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 8));
GB_log(gb, "Cartridge includes%s RAM: $%x bytes\n", has_battery? " battery-backed": "", gb->mbc_ram_size);
}
else {
GB_log(gb, "No cartridge RAM\n");
@ -1565,7 +1567,8 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
GB_log(gb, "No MBC\n");
}
if (cartridge->has_rumble) {
if (gb->cartridge_type->has_rumble &&
(gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 1))) {
GB_log(gb, "Cart contains a Rumble Pak\n");
}
@ -1899,7 +1902,7 @@ static bool undo(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
return true;
}
uint16_t pc = gb->pc;
GB_load_state_from_buffer(gb, gb->undo_state, GB_get_save_state_size(gb));
GB_load_state_from_buffer(gb, gb->undo_state, GB_get_save_state_size_no_bess(gb));
GB_log(gb, "Reverted a \"%s\" command.\n", gb->undo_label);
if (pc != gb->pc) {
GB_cpu_disassemble(gb, gb->pc, 5);
@ -2205,8 +2208,8 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
const debugger_command_t *command = find_command(command_string);
if (command) {
uint8_t *old_state = malloc(GB_get_save_state_size(gb));
GB_save_state_to_buffer(gb, old_state);
uint8_t *old_state = malloc(GB_get_save_state_size_no_bess(gb));
GB_save_state_to_buffer_no_bess(gb, old_state);
bool ret = command->implementation(gb, arguments, modifiers, command);
if (!ret) { // Command continues, save state in any case
free(gb->undo_state);
@ -2214,9 +2217,9 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
gb->undo_label = command->command;
}
else {
uint8_t *new_state = malloc(GB_get_save_state_size(gb));
GB_save_state_to_buffer(gb, new_state);
if (memcmp(new_state, old_state, GB_get_save_state_size(gb)) != 0) {
uint8_t *new_state = malloc(GB_get_save_state_size_no_bess(gb));
GB_save_state_to_buffer_no_bess(gb, new_state);
if (memcmp(new_state, old_state, GB_get_save_state_size_no_bess(gb)) != 0) {
// State changed, save the old state as the new undo state
free(gb->undo_state);
gb->undo_state = old_state;
@ -2306,8 +2309,8 @@ void GB_debugger_run(GB_gameboy_t *gb)
if (gb->debug_disable) return;
if (!gb->undo_state) {
gb->undo_state = malloc(GB_get_save_state_size(gb));
GB_save_state_to_buffer(gb, gb->undo_state);
gb->undo_state = malloc(GB_get_save_state_size_no_bess(gb));
GB_save_state_to_buffer_no_bess(gb, gb->undo_state);
}
char *input = NULL;
@ -2355,9 +2358,9 @@ next_command:
}
else if (jump_to_result == JUMP_TO_NONTRIVIAL) {
if (!gb->nontrivial_jump_state) {
gb->nontrivial_jump_state = malloc(GB_get_save_state_size(gb));
gb->nontrivial_jump_state = malloc(GB_get_save_state_size_no_bess(gb));
}
GB_save_state_to_buffer(gb, gb->nontrivial_jump_state);
GB_save_state_to_buffer_no_bess(gb, gb->nontrivial_jump_state);
gb->non_trivial_jump_breakpoint_occured = false;
should_delete_state = false;
}

View File

@ -579,7 +579,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
icd_pixel = pixel;
//gb->icd_pixel_callback(gb, pixel);
}
}
else if (gb->cgb_palettes_ppu_blocked) {

View File

@ -19,12 +19,6 @@
#endif
static inline uint32_t state_magic(void)
{
if (sizeof(bool) == 1) return 'SAME';
return 'S4ME';
}
void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args)
{
char *string = NULL;
@ -701,14 +695,6 @@ typedef struct {
uint8_t padding5[3];
} GB_vba_rtc_time_t;
typedef struct __attribute__((packed)) {
uint64_t last_rtc_second;
uint16_t minutes;
uint16_t days;
uint16_t alarm_minutes, alarm_days;
uint8_t alarm_enabled;
} GB_huc3_rtc_time_t;
typedef union {
struct __attribute__((packed)) {
GB_rtc_time_t rtc_real;
@ -729,6 +715,8 @@ typedef union {
int GB_save_battery_size(GB_gameboy_t *gb)
{
if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save.
if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
if (gb->cartridge_type->mbc_type == GB_HUC3) {
@ -741,6 +729,7 @@ int GB_save_battery_size(GB_gameboy_t *gb)
int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size)
{
if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save.
if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
if (size < GB_save_battery_size(gb)) return EIO;
@ -798,6 +787,7 @@ int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size)
int GB_save_battery(GB_gameboy_t *gb, const char *path)
{
if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
if (gb->cartridge_type->mbc_type == GB_TPP1 && !(gb->rom[0x153] & 8)) return 0; // Nothing to save.
if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
FILE *f = fopen(path, "wb");
if (!f) {

View File

@ -101,6 +101,14 @@ typedef union {
uint8_t data[5];
} GB_rtc_time_t;
typedef struct __attribute__((packed)) {
uint64_t last_rtc_second;
uint16_t minutes;
uint16_t days;
uint16_t alarm_minutes, alarm_days;
uint8_t alarm_enabled;
} GB_huc3_rtc_time_t;
typedef enum {
// GB_MODEL_DMG_0 = 0x000,
// GB_MODEL_DMG_A = 0x001,
@ -483,10 +491,10 @@ struct GB_gameboy_internal_s {
uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */
bool camera_registers_mapped;
uint8_t camera_registers[0x36];
bool rumble_state;
uint8_t rumble_strength;
bool cart_ir;
// TODO: move to huc3/mbc3 struct when breaking save compat
// TODO: move to huc3/mbc3/tpp1 struct when breaking save compat
uint8_t huc3_mode;
uint8_t huc3_access_index;
uint16_t huc3_minutes, huc3_days;
@ -495,6 +503,9 @@ struct GB_gameboy_internal_s {
uint8_t huc3_read;
uint8_t huc3_access_flags;
bool mbc3_rtc_mapped;
uint16_t tpp1_rom_bank;
uint8_t tpp1_ram_bank;
uint8_t tpp1_mode;
);

View File

@ -111,12 +111,24 @@ void GB_update_mbc_mappings(GB_gameboy_t *gb)
gb->mbc_rom_bank = gb->huc3.rom_bank;
gb->mbc_ram_bank = gb->huc3.ram_bank;
break;
case GB_TPP1:
gb->mbc_rom_bank = gb->tpp1_rom_bank;
gb->mbc_ram_bank = gb->tpp1_ram_bank;
gb->mbc_ram_enable = (gb->tpp1_mode == 2) || (gb->tpp1_mode == 3);
break;
}
}
void GB_configure_cart(GB_gameboy_t *gb)
{
gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]];
if (gb->rom[0x147] == 0xbc &&
gb->rom[0x149] == 0xc1 &&
gb->rom[0x14a] == 0x65) {
static const GB_cartridge_t tpp1 = {GB_TPP1, GB_STANDARD_MBC, true, true, true, true};
gb->cartridge_type = &tpp1;
gb->tpp1_rom_bank = 1;
}
if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) {
GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n");
@ -135,6 +147,11 @@ void GB_configure_cart(GB_gameboy_t *gb)
if (gb->cartridge_type->mbc_type == GB_MBC2) {
gb->mbc_ram_size = 0x200;
}
else if (gb->cartridge_type->mbc_type == GB_TPP1) {
if (gb->rom[0x152] >= 1 && gb->rom[0x152] <= 9) {
gb->mbc_ram_size = 0x2000 << (gb->rom[0x152] - 1);
}
}
else {
static const unsigned ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];

View File

@ -12,6 +12,7 @@ typedef struct {
GB_MBC5,
GB_HUC1,
GB_HUC3,
GB_TPP1,
} mbc_type;
enum {
GB_STANDARD_MBC,

View File

@ -178,7 +178,42 @@ static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
}
}
if ((!gb->mbc_ram_enable) &&
if (gb->cartridge_type->mbc_type == GB_TPP1) {
switch (gb->tpp1_mode) {
case 0:
switch (addr & 3) {
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 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;
}
default:
return 0xFF;
}
}
else if ((!gb->mbc_ram_enable) &&
gb->cartridge_type->mbc_subtype != GB_CAMERA &&
gb->cartridge_type->mbc_type != GB_HUC1 &&
gb->cartridge_type->mbc_type != GB_HUC3) {
@ -335,9 +370,7 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
}
if (addr < 0xFF00) {
return 0;
}
if (addr < 0xFF80) {
@ -539,8 +572,8 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
case 0x3000: gb->mbc5.rom_bank_high = value; break;
case 0x4000: case 0x5000:
if (gb->cartridge_type->has_rumble) {
if (!!(value & 8) != gb->rumble_state) {
gb->rumble_state = !gb->rumble_state;
if (!!(value & 8) != !!gb->rumble_strength) {
gb->rumble_strength = gb->rumble_strength? 0 : 3;
}
value &= 7;
}
@ -567,6 +600,56 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break;
}
break;
case GB_TPP1:
switch (addr & 3) {
case 0:
gb->tpp1_rom_bank &= 0xFF00;
gb->tpp1_rom_bank |= value;
break;
case 1:
gb->tpp1_rom_bank &= 0xFF;
gb->tpp1_rom_bank |= value << 8;
break;
case 2:
gb->tpp1_ram_bank = value;
break;
case 3:
switch (value) {
case 0:
case 2:
case 3:
case 5:
gb->tpp1_mode = value;
break;
case 0x10:
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;
break;
case 0x18:
gb->rtc_real.high |= 0x40;
break;
case 0x19:
gb->rtc_real.high &= ~0x40;
break;
case 0x20:
case 0x21:
case 0x22:
case 0x23:
gb->rumble_strength = value & 3;
break;
}
}
break;
}
GB_update_mbc_mappings(gb);
}
@ -688,6 +771,43 @@ static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
}
if (gb->cartridge_type->mbc_type == GB_TPP1) {
switch (gb->tpp1_mode) {
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;
}
return;
default:
return;
}
}
if ((!gb->mbc_ram_enable)
&& gb->cartridge_type->mbc_type != GB_HUC1) return;

View File

@ -108,7 +108,7 @@ static void state_decompress(const uint8_t *prev, uint8_t *data, uint8_t *dest,
void GB_rewind_push(GB_gameboy_t *gb)
{
const size_t save_size = GB_get_save_state_size(gb);
const size_t save_size = GB_get_save_state_size_no_bess(gb);
if (!gb->rewind_sequences) {
if (gb->rewind_buffer_length) {
gb->rewind_sequences = malloc(sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length);
@ -140,11 +140,11 @@ void GB_rewind_push(GB_gameboy_t *gb)
if (!gb->rewind_sequences[gb->rewind_pos].key_state) {
gb->rewind_sequences[gb->rewind_pos].key_state = malloc(save_size);
GB_save_state_to_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state);
GB_save_state_to_buffer_no_bess(gb, gb->rewind_sequences[gb->rewind_pos].key_state);
}
else {
uint8_t *save_state = malloc(save_size);
GB_save_state_to_buffer(gb, save_state);
GB_save_state_to_buffer_no_bess(gb, save_state);
gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos++] =
state_compress(gb->rewind_sequences[gb->rewind_pos].key_state, save_state, save_size);
free(save_state);
@ -158,7 +158,7 @@ bool GB_rewind_pop(GB_gameboy_t *gb)
return false;
}
const size_t save_size = GB_get_save_state_size(gb);
const size_t save_size = GB_get_save_state_size_no_bess(gb);
if (gb->rewind_sequences[gb->rewind_pos].pos == 0) {
GB_load_state_from_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state, save_size);
free(gb->rewind_sequences[gb->rewind_pos].key_state);

View File

@ -15,7 +15,8 @@ void GB_handle_rumble(GB_gameboy_t *gb)
if (gb->rumble_mode == GB_RUMBLE_DISABLED) {
return;
}
if (gb->cartridge_type->has_rumble) {
if (gb->cartridge_type->has_rumble &&
(gb->cartridge_type->mbc_type != GB_TPP1 || (gb->rom[0x153] & 1))) {
if (gb->rumble_on_cycles + gb->rumble_off_cycles) {
gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles));
gb->rumble_on_cycles = gb->rumble_off_cycles = 0;

View File

@ -50,6 +50,7 @@ typedef struct __attribute__((packed)) {
uint8_t ime;
uint8_t ie;
uint8_t execution_mode; // 0 = running; 1 = halted; 2 = stopped
uint8_t _padding;
uint8_t io_registers[0x80];
@ -99,6 +100,12 @@ typedef struct __attribute__((packed)){
uint64_t last_rtc_second;
} BESS_RTC_t;
/* Same HuC3 RTC format as used by SameBoy and BGB in battery saves */
typedef struct __attribute__((packed)){
BESS_block_t header;
GB_huc3_rtc_time_t data;
} BESS_HUC3_t;
typedef struct __attribute__((packed)) {
uint16_t address;
uint8_t value;
@ -207,11 +214,13 @@ static size_t bess_size_for_cartridge(const GB_cartridge_t *cart)
case GB_HUC1:
return sizeof(BESS_block_t) + 4 * sizeof(BESS_MBC_pair_t);
case GB_HUC3:
return sizeof(BESS_block_t) + 3 * sizeof(BESS_MBC_pair_t);
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);
}
}
size_t GB_get_save_state_size(GB_gameboy_t *gb)
size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb)
{
return GB_SECTION_SIZE(header)
+ GB_SECTION_SIZE(core_state) + sizeof(uint32_t)
@ -225,14 +234,19 @@ size_t GB_get_save_state_size(GB_gameboy_t *gb)
+ (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0)
+ gb->mbc_ram_size
+ gb->ram_size
+ gb->vram_size
+ gb->vram_size;
}
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_XOAM_t)
+ (gb->sgb? sizeof(BESS_SGB_t) : 0)
+ bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC block
+ bess_size_for_cartridge(gb->cartridge_type) // MBC & RTC/HUC3 block
+ sizeof(BESS_block_t) // END block
+ sizeof(BESS_footer_t);
}
@ -431,6 +445,14 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file)
pairs[2] = (BESS_MBC_pair_t){LE16(0x4000), gb->huc3.ram_bank};
mbc_block.size = 3 * sizeof(pairs[0]);
break;
case GB_TPP1:
pairs[0] = (BESS_MBC_pair_t){LE16(0x0000), gb->tpp1_rom_bank};
pairs[1] = (BESS_MBC_pair_t){LE16(0x0001), gb->tpp1_rom_bank >> 8};
pairs[2] = (BESS_MBC_pair_t){LE16(0x0002), gb->tpp1_rom_bank};
pairs[3] = (BESS_MBC_pair_t){LE16(0x0003), gb->tpp1_mode};
mbc_block.size = 4 * sizeof(pairs[0]);
break;
}
mbc_block.size = LE32(mbc_block.size);
@ -446,7 +468,7 @@ static int save_bess_mbc_block(GB_gameboy_t *gb, virtual_file_t *file)
return 0;
}
static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file)
static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file, bool append_bess)
{
if (file->write(file, GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header)) != GB_SECTION_SIZE(header)) goto error;
if (!DUMP_SECTION(gb, file, core_state)) goto error;
@ -468,6 +490,7 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file)
if (!dump_section(file, gb->sgb, sizeof(*gb->sgb))) goto error;
}
BESS_CORE_t bess_core = {0,};
bess_core.mbc_ram.offset = LE32(file->tell(file));
@ -488,6 +511,8 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file)
goto error;
}
if (!append_bess) return 0;
BESS_footer_t bess_footer = {
.start_offset = LE32(file->tell(file)),
.magic = BE32('BESS'),
@ -581,7 +606,8 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file)
}
save_bess_mbc_block(gb, file);
if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type == GB_MBC3) {
if (gb->cartridge_type->has_rtc) {
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;
@ -599,6 +625,23 @@ static int save_state_internal(GB_gameboy_t *gb, virtual_file_t *file)
goto error;
}
}
else {
BESS_HUC3_t bess_huc3 = {0,};
bess_huc3.header = (BESS_block_t){BE32('HUC3'), LE32(sizeof(bess_huc3) - sizeof(bess_huc3.header))};
bess_huc3.data = (GB_huc3_rtc_time_t) {
LE64(gb->last_rtc_second),
LE16(gb->huc3_minutes),
LE16(gb->huc3_days),
LE16(gb->huc3_alarm_minutes),
LE16(gb->huc3_alarm_days),
gb->huc3_alarm_enabled,
};
if (file->write(file, &bess_huc3, sizeof(bess_huc3)) != sizeof(bess_huc3)) {
goto error;
}
}
}
bool needs_sgb_padding = false;
if (gb->sgb) {
@ -669,7 +712,7 @@ int GB_save_state(GB_gameboy_t *gb, const char *path)
.tell = file_tell,
.file = f,
};
int ret = save_state_internal(gb, &file);
int ret = save_state_internal(gb, &file, true);
fclose(f);
return ret;
}
@ -684,10 +727,23 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
.position = 0,
};
save_state_internal(gb, &file);
save_state_internal(gb, &file, true);
assert(file.position == GB_get_save_state_size(gb));
}
void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer)
{
virtual_file_t file = {
.write = buffer_write,
.seek = buffer_seek,
.tell = buffer_tell,
.buffer = (uint8_t *)buffer,
.position = 0,
};
save_state_internal(gb, &file, false);
assert(file.position == GB_get_save_state_size_no_bess(gb));
}
static bool read_section(virtual_file_t *file, void *dest, uint32_t size, bool fix_broken_windows_saves)
{
@ -725,6 +781,10 @@ static void read_bess_buffer(const BESS_buffer_t *buffer, virtual_file_t *file,
file->seek(file, LE32(buffer->offset), SEEK_SET);
file->read(file, dest, MIN(LE32(buffer->size), max_size));
file->seek(file, pos, SEEK_SET);
if (LE32(buffer->size) < max_size) {
memset(dest + LE32(buffer->size), 0, max_size - LE32(buffer->size));
}
}
static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_sameboy)
@ -887,7 +947,8 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
for (unsigned i = LE32(block.size); i > 0; i -= 3) {
BESS_MBC_pair_t pair;
file->read(file, &pair, sizeof(pair));
if (LE16(pair.address) >= 0x8000) goto parse_error;
if (LE16(pair.address) >= 0x8000 && LE16(pair.address) < 0xA000) goto parse_error;
if (LE16(pair.address) >= 0xC000) goto parse_error;
GB_write_memory(&save, LE16(pair.address), pair.value);
}
break;
@ -895,8 +956,8 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
if (!found_core) goto parse_error;
BESS_RTC_t bess_rtc;
if (LE32(block.size) != sizeof(bess_rtc) - sizeof(block)) goto parse_error;
if (gb->cartridge_type->has_rtc && gb->cartridge_type->mbc_type == GB_MBC3) {
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;
@ -910,8 +971,23 @@ static int load_bess_save(GB_gameboy_t *gb, virtual_file_t *file, bool is_samebo
gb->last_rtc_second = LE64(bess_rtc.last_rtc_second);
}
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;
}
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));
found_sgb = true;
@ -957,7 +1033,7 @@ done:
gb->sgb->player_count = sgb.multiplayer_state >> 4;
gb->sgb->current_player = sgb.multiplayer_state & 0xF;
if (gb->sgb->player_count > 4 || gb->sgb->player_count == 3) {
if (gb->sgb->player_count > 4 || gb->sgb->player_count == 3 || gb->sgb->player_count == 0) {
gb->sgb->player_count = 1;
gb->sgb->current_player = 0;
}
@ -1094,3 +1170,36 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
return load_state_internal(gb, &file);
}
bool GB_is_stave_state(const char *path)
{
bool ret = false;
FILE *f = fopen(path, "rb");
if (!f) return false;
uint32_t magic = 0;
fread(&magic, sizeof(magic), 1, f);
if (magic == state_magic()) {
ret = true;
goto exit;
}
// Legacy corrupted Windows save state
if (magic == 0) {
fread(&magic, sizeof(magic), 1, f);
if (magic == state_magic()) {
ret = true;
goto exit;
}
}
fseek(f, -sizeof(magic), SEEK_END);
fread(&magic, sizeof(magic), 1, f);
if (magic == BE32('BESS')) {
ret = true;
}
exit:
fclose(f);
return ret;
}

View File

@ -27,4 +27,17 @@ void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer);
int GB_load_state(GB_gameboy_t *gb, const char *path);
int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length);
bool GB_is_stave_state(const char *path);
#ifdef GB_INTERNAL
static inline uint32_t state_magic(void)
{
if (sizeof(bool) == 1) return 'SAME';
return 'S4ME';
}
/* For internal in-memory save states (rewind, debugger) that do not need BESS */
size_t GB_get_save_state_size_no_bess(GB_gameboy_t *gb);
void GB_save_state_to_buffer_no_bess(GB_gameboy_t *gb, uint8_t *buffer);
#endif
#endif /* save_state_h */

View File

@ -267,7 +267,8 @@ static void command_ready(GB_gameboy_t *gb)
#endif
uint8_t x = command->x;
uint8_t y = command->y;
if (x >= 20 || y >= 18 || (count + 3) / 4 > sizeof(gb->sgb->command) - sizeof(*command) - 1) {
count = MIN(count, 20 * 18);
if (x >= 20 || y >= 18) {
/* TODO: Verify with the SFC BIOS */
break;
}

View File

@ -51,7 +51,8 @@ struct GB_sgb_s {
uint16_t effective_palettes[4 * 4];
uint16_t ram_palettes[4 * 512];
uint8_t attribute_map[20 * 18];
uint8_t attribute_files[0xFE0];
uint8_t attribute_files[0xFD2];
uint8_t attribute_files_padding[0xFE0 - 0xFD2];
/* Intro */
int16_t intro_animation;

View File

@ -288,12 +288,24 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles)
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++;
}
}
else {
if (gb->rtc_real.high & 1) { /* Bit 8 of days*/
gb->rtc_real.high |= 0x80; /* Overflow bit */
}
gb->rtc_real.high ^= 1;
}
}
}
while (gb->last_rtc_second < current_time) {
gb->last_rtc_second++;
@ -308,6 +320,16 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles)
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++;
}
}
else {
if (gb->rtc_real.high & 1) { /* Bit 8 of days*/
gb->rtc_real.high |= 0x80; /* Overflow bit */
}
@ -315,6 +337,7 @@ static void GB_rtc_run(GB_gameboy_t *gb, uint8_t cycles)
gb->rtc_real.high ^= 1;
}
}
}
}
@ -344,12 +367,8 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
gb->cycles_since_last_sync += cycles;
gb->cycles_since_run += cycles;
if (gb->rumble_state) {
gb->rumble_on_cycles++;
}
else {
gb->rumble_off_cycles++;
}
gb->rumble_on_cycles += gb->rumble_strength & 3;
gb->rumble_off_cycles += (gb->rumble_strength & 3) ^ 3;
if (!gb->stopped) { // TODO: Verify what happens in STOP mode
GB_dma_run(gb);

View File

@ -166,6 +166,7 @@ typedef union {
double _sentRumbleAmp;
unsigned _rumbleCounter;
bool _deviceCantSendReports;
dispatch_queue_t _rumbleQueue;
}
- (instancetype)initWithDevice:(IOHIDDeviceRef) device hacks:(NSDictionary *)hacks
@ -490,9 +491,11 @@ typedef union {
{.timeEnabled = 0, .dutyLength = 0, .enabled = 0, .dutyOff = 0, .dutyOn = 0},
}
};
}
_rumbleQueue = dispatch_queue_create([NSString stringWithFormat:@"Rumble Queue for %@", self.deviceName].UTF8String,
NULL);
return self;
}
@ -564,7 +567,9 @@ typedef union {
}
}
}
dispatch_async(_rumbleQueue, ^{
[self updateRumble];
});
}
- (void)elementChanged:(IOHIDElementRef)element
@ -699,7 +704,9 @@ typedef union {
_physicallyConnected = false;
[exposedControllers removeObject:self];
[self setRumbleAmplitude:0];
dispatch_sync(_rumbleQueue, ^{
[self updateRumble];
});
_device = nil;
}

View File

@ -18,6 +18,7 @@ SDL_Texture *texture = NULL;
SDL_PixelFormat *pixel_format = NULL;
enum pending_command pending_command;
unsigned command_parameter;
char *dropped_state_file = NULL;
#ifdef __APPLE__
#define MODIFIER_NAME " " CMD_STRING
@ -1300,10 +1301,16 @@ void run_gui(bool is_running)
break;
}
case SDL_DROPFILE: {
if (GB_is_stave_state(event.drop.file)) {
dropped_state_file = event.drop.file;
pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND;
}
else {
set_filename(event.drop.file, SDL_free);
pending_command = GB_SDL_NEW_FILE_COMMAND;
return;
}
}
case SDL_JOYBUTTONDOWN:
{
if (gui_state == WAITING_FOR_JBUTTON && joypad_configuration_progress != JOYPAD_BUTTONS_MAX) {

View File

@ -39,12 +39,14 @@ enum pending_command {
GB_SDL_RESET_COMMAND,
GB_SDL_NEW_FILE_COMMAND,
GB_SDL_QUIT_COMMAND,
GB_SDL_LOAD_STATE_FROM_FILE_COMMAND,
};
#define GB_SDL_DEFAULT_SCALE_MAX 8
extern enum pending_command pending_command;
extern unsigned command_parameter;
extern char *dropped_state_file;
typedef enum {
JOYPAD_BUTTON_LEFT,

View File

@ -142,8 +142,14 @@ static void handle_events(GB_gameboy_t *gb)
break;
case SDL_DROPFILE: {
if (GB_is_stave_state(event.drop.file)) {
dropped_state_file = event.drop.file;
pending_command = GB_SDL_LOAD_STATE_FROM_FILE_COMMAND;
}
else {
set_filename(event.drop.file, SDL_free);
pending_command = GB_SDL_NEW_FILE_COMMAND;
}
break;
}
@ -434,6 +440,13 @@ static bool handle_pending_command(void)
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);
SDL_free(dropped_state_file);
return false;
case GB_SDL_NO_COMMAND:
return false;