sameboy: it's alive!
This commit is contained in:
parent
34e68c589f
commit
f7bb894753
|
@ -59,8 +59,8 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
|||
|
||||
_cgb = (rom[0x143] & 0xc0) == 0xc0;
|
||||
Console.WriteLine("Automaticly detected CGB to " + _cgb);
|
||||
var bios = Util.DecompressGzipFile(new MemoryStream(
|
||||
_cgb ? Resources.SameboyCgbBoot : Resources.SameboyDmgBoot));
|
||||
var bios = Util.DecompressGzipFile(new MemoryStream(_cgb ? Resources.SameboyCgbBoot : Resources.SameboyDmgBoot));
|
||||
// var bios = comm.CoreFileProvider.GetFirmware(_cgb ? "GBC" : "GB", "World", true);
|
||||
|
||||
_exe.AddReadonlyFile(rom, "game.rom");
|
||||
_exe.AddReadonlyFile(bios, "boot.rom");
|
||||
|
|
|
@ -6,12 +6,11 @@ FLAGS:=-Wall -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=imp
|
|||
-fomit-frame-pointer -fvisibility=hidden \
|
||||
-O0 -g
|
||||
|
||||
CCFLAGS:=$(FLAGS) -Ilib \
|
||||
-I../emulibc \
|
||||
CCFLAGS:=$(FLAGS) \
|
||||
-std=gnu99 \
|
||||
-DLSB_FIRST -D_GNU_SOURCE -DGB_INTERNAL
|
||||
|
||||
CPPFLAGS:=$(FLAGS) -DSPC_NO_COPY_STATE_FUNCS -std=c++0x
|
||||
CPPFLAGS:=$(FLAGS) -DSPC_NO_COPY_STATE_FUNCS -std=c++0x -D_GNU_SOURCE -DGB_INTERNAL
|
||||
|
||||
TARGET = sameboy.wbx
|
||||
|
||||
|
|
|
@ -65,6 +65,9 @@ ECL_EXPORT bool Init(bool cgb)
|
|||
GB_set_infrared_callback(&GB, InfraredCallback);
|
||||
GB_set_rumble_callback(&GB, RumbleCallback);
|
||||
|
||||
GB_set_sample_rate(&GB, 44100);
|
||||
GB_set_audio_quality(&GB, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -76,9 +79,12 @@ struct MyFrameInfo : public FrameInfo
|
|||
ECL_EXPORT void FrameAdvance(MyFrameInfo &f)
|
||||
{
|
||||
GB_set_pixels_output(&GB, f.VideoBuffer);
|
||||
// void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed);
|
||||
GB_run_frame(&GB);
|
||||
f.Samples = 735;
|
||||
for (int i = 0; i < (int)GB_KEY_MAX; i++)
|
||||
GB_set_key_state(&GB, (GB_key_t)i, false);
|
||||
|
||||
f.Cycles = GB_run_cycles(&GB, 35112);
|
||||
f.Samples = GB_apu_get_current_buffer_length(&GB);
|
||||
GB_apu_copy_buffer(&GB, (GB_sample_t*)f.SoundBuffer, f.Samples);
|
||||
f.Width = 160;
|
||||
f.Height = 144;
|
||||
}
|
||||
|
@ -104,7 +110,7 @@ ECL_EXPORT void GetMemoryAreas(MemoryArea *m)
|
|||
SetMemoryArea(m + 6, GB_DIRECT_ACCESS_IO, "IO", MEMORYAREA_FLAGS_WORDSIZE1);
|
||||
SetMemoryArea(m + 7, GB_DIRECT_ACCESS_BOOTROM, "BOOTROM", MEMORYAREA_FLAGS_WORDSIZE1);
|
||||
SetMemoryArea(m + 8, GB_DIRECT_ACCESS_BGP, "BGP", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE);
|
||||
SetMemoryArea(m + 8, GB_DIRECT_ACCESS_OBP, "OBP", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE);
|
||||
SetMemoryArea(m + 9, GB_DIRECT_ACCESS_OBP, "OBP", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE);
|
||||
}
|
||||
|
||||
ECL_EXPORT void SetInputCallback(void (*callback)())
|
||||
|
|
|
@ -125,7 +125,7 @@ void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
addr &= 0x7F;
|
||||
if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) {
|
||||
value &= 0x7;
|
||||
noise_seed = rand();
|
||||
noise_seed = 42; // rand();
|
||||
if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && gb->camera_update_request_callback) {
|
||||
/* If no callback is set, ignore the write as if the camera is instantly done */
|
||||
gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] |= 1;
|
||||
|
|
|
@ -197,12 +197,6 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y)
|
|||
|
||||
static void display_vblank(GB_gameboy_t *gb)
|
||||
{
|
||||
if (gb->turbo) {
|
||||
if (GB_timing_sync_turbo(gb)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
|
||||
/* LCD is off, set screen to white */
|
||||
uint32_t white = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
|
||||
|
@ -212,7 +206,6 @@ static void display_vblank(GB_gameboy_t *gb)
|
|||
}
|
||||
|
||||
gb->vblank_callback(gb);
|
||||
GB_timing_sync(gb);
|
||||
|
||||
gb->vblank_just_occured = true;
|
||||
}
|
||||
|
|
|
@ -133,18 +133,6 @@ void GB_free(GB_gameboy_t *gb)
|
|||
if (gb->breakpoints) {
|
||||
free(gb->breakpoints);
|
||||
}
|
||||
for (int i = 0x200; i--;) {
|
||||
if (gb->bank_symbols[i]) {
|
||||
GB_map_free(gb->bank_symbols[i]);
|
||||
}
|
||||
}
|
||||
for (int i = 0x400; i--;) {
|
||||
if (gb->reversed_symbol_map.buckets[i]) {
|
||||
GB_symbol_t *next = gb->reversed_symbol_map.buckets[i]->next;
|
||||
free(gb->reversed_symbol_map.buckets[i]);
|
||||
gb->reversed_symbol_map.buckets[i] = next;
|
||||
}
|
||||
}
|
||||
memset(gb, 0, sizeof(*gb));
|
||||
}
|
||||
|
||||
|
@ -267,24 +255,20 @@ void GB_run(GB_gameboy_t *gb)
|
|||
}
|
||||
}
|
||||
|
||||
uint64_t GB_run_frame(GB_gameboy_t *gb)
|
||||
uint64_t GB_run_cycles(GB_gameboy_t *gb, uint32_t cycles)
|
||||
{
|
||||
/* Configure turbo temporarily, the user wants to handle FPS capping manually. */
|
||||
bool old_turbo = gb->turbo;
|
||||
bool old_dont_skip = gb->turbo_dont_skip;
|
||||
gb->turbo = true;
|
||||
gb->turbo_dont_skip = true;
|
||||
|
||||
gb->cycles_since_last_sync = 0;
|
||||
while (true) {
|
||||
uint64_t start = gb->cycles_since_epoch;
|
||||
uint64_t target = start + cycles;
|
||||
while (gb->cycles_since_epoch < target) {
|
||||
GB_run(gb);
|
||||
if (gb->vblank_just_occured) {
|
||||
// TODO: fix these up
|
||||
GB_update_joyp(gb);
|
||||
GB_rtc_run(gb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
gb->turbo = old_turbo;
|
||||
gb->turbo_dont_skip = old_dont_skip;
|
||||
return gb->cycles_since_last_sync * FRAME_LENGTH * LCDC_PERIOD;
|
||||
return gb->cycles_since_epoch - start;
|
||||
}
|
||||
|
||||
void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output)
|
||||
|
@ -416,12 +400,6 @@ bool GB_is_cgb(GB_gameboy_t *gb)
|
|||
return gb->is_cgb;
|
||||
}
|
||||
|
||||
void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip)
|
||||
{
|
||||
gb->turbo = on;
|
||||
gb->turbo_dont_skip = no_frame_skip;
|
||||
}
|
||||
|
||||
void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled)
|
||||
{
|
||||
gb->disable_rendering = disabled;
|
||||
|
@ -441,7 +419,7 @@ void GB_reset(GB_gameboy_t *gb)
|
|||
{
|
||||
uint32_t mbc_ram_size = gb->mbc_ram_size;
|
||||
bool cgb = gb->is_cgb;
|
||||
memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved));
|
||||
// memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved));
|
||||
gb->version = GB_STRUCT_VERSION;
|
||||
|
||||
gb->mbc_rom_bank = 1;
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "gb_struct_def.h"
|
||||
#include "save_state.h"
|
||||
|
||||
#include "apu.h"
|
||||
#include "camera.h"
|
||||
|
@ -17,7 +17,6 @@
|
|||
#include "printer.h"
|
||||
#include "timing.h"
|
||||
#include "z80_cpu.h"
|
||||
#include "symbol_hash.h"
|
||||
|
||||
#define GB_STRUCT_VERSION 11
|
||||
|
||||
|
@ -193,7 +192,6 @@ struct GB_gameboy_s {
|
|||
#else
|
||||
struct GB_gameboy_internal_s {
|
||||
#endif
|
||||
GB_SECTION(header,
|
||||
/* The magic makes sure a state file is:
|
||||
- Indeed a SameBoy state file.
|
||||
- Has the same endianess has the current platform. */
|
||||
|
@ -201,9 +199,7 @@ struct GB_gameboy_internal_s {
|
|||
/* The version field makes sure we don't load save state files with a completely different structure.
|
||||
This happens when struct fields are removed/resized in an backward incompatible manner. */
|
||||
uint32_t version;
|
||||
);
|
||||
|
||||
GB_SECTION(core_state,
|
||||
|
||||
/* Registers */
|
||||
uint16_t pc;
|
||||
union {
|
||||
|
@ -249,10 +245,8 @@ struct GB_gameboy_internal_s {
|
|||
/* Misc state */
|
||||
bool infrared_input;
|
||||
GB_printer_t printer;
|
||||
);
|
||||
|
||||
/* DMA and HDMA */
|
||||
GB_SECTION(dma,
|
||||
bool hdma_on;
|
||||
bool hdma_on_hblank;
|
||||
uint8_t hdma_steps_left;
|
||||
|
@ -264,10 +258,8 @@ struct GB_gameboy_internal_s {
|
|||
uint16_t dma_current_src;
|
||||
int16_t dma_cycles;
|
||||
bool is_dma_restarting;
|
||||
);
|
||||
|
||||
/* MBC */
|
||||
GB_SECTION(mbc,
|
||||
uint16_t mbc_rom_bank;
|
||||
uint8_t mbc_ram_bank;
|
||||
uint32_t mbc_ram_size;
|
||||
|
@ -311,32 +303,23 @@ struct GB_gameboy_internal_s {
|
|||
bool camera_registers_mapped;
|
||||
uint8_t camera_registers[0x36];
|
||||
bool rumble_state;
|
||||
);
|
||||
|
||||
|
||||
/* HRAM and HW Registers */
|
||||
GB_SECTION(hram,
|
||||
uint8_t hram[0xFFFF - 0xFF80];
|
||||
uint8_t io_registers[0x80];
|
||||
);
|
||||
|
||||
/* Timing */
|
||||
GB_SECTION(timing,
|
||||
uint32_t display_cycles;
|
||||
uint32_t div_cycles;
|
||||
uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */
|
||||
GB_PADDING(uint16_t, serial_cycles);
|
||||
uint16_t serial_cycles; /* This field changed its meaning in v0.10 */
|
||||
uint16_t serial_length;
|
||||
);
|
||||
|
||||
/* APU */
|
||||
GB_SECTION(apu,
|
||||
GB_apu_t apu;
|
||||
);
|
||||
|
||||
/* RTC */
|
||||
GB_SECTION(rtc,
|
||||
union {
|
||||
struct {
|
||||
uint8_t seconds;
|
||||
|
@ -349,10 +332,8 @@ struct GB_gameboy_internal_s {
|
|||
} rtc_real, rtc_latched;
|
||||
time_t last_rtc_second;
|
||||
bool rtc_latch;
|
||||
);
|
||||
|
||||
/* Video Display */
|
||||
GB_SECTION(video,
|
||||
uint32_t vram_size; // Different between CGB and DMG
|
||||
uint8_t cgb_vram_bank;
|
||||
uint8_t oam[0xA0];
|
||||
|
@ -379,11 +360,9 @@ struct GB_gameboy_internal_s {
|
|||
bool vram_read_blocked;
|
||||
bool oam_write_blocked;
|
||||
bool vram_write_blocked;
|
||||
);
|
||||
|
||||
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
|
||||
/* This data is reserved on reset and must come last in the struct */
|
||||
GB_SECTION(unsaved,
|
||||
/* ROM */
|
||||
uint8_t *rom;
|
||||
uint32_t rom_size;
|
||||
|
@ -404,8 +383,7 @@ struct GB_gameboy_internal_s {
|
|||
bool keys[GB_KEY_MAX];
|
||||
|
||||
/* Timing */
|
||||
uint64_t last_sync;
|
||||
uint64_t cycles_since_last_sync;
|
||||
uint64_t cycles_since_epoch;
|
||||
|
||||
/* Audio */
|
||||
unsigned buffer_size;
|
||||
|
@ -459,21 +437,11 @@ struct GB_gameboy_internal_s {
|
|||
uint16_t addr;
|
||||
} backtrace_returns[0x200];
|
||||
|
||||
/* Symbol tables */
|
||||
GB_symbol_map_t *bank_symbols[0x200];
|
||||
GB_reversed_symbol_map_t reversed_symbol_map;
|
||||
|
||||
/* Ticks command */
|
||||
unsigned long debugger_ticks;
|
||||
|
||||
/* Misc */
|
||||
bool turbo;
|
||||
bool turbo_dont_skip;
|
||||
bool disable_rendering;
|
||||
uint32_t ram_size; // Different between CGB and DMG
|
||||
uint8_t boot_rom[0x900];
|
||||
bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
|
||||
);
|
||||
};
|
||||
|
||||
#ifndef GB_INTERNAL
|
||||
|
@ -497,8 +465,7 @@ void GB_free(GB_gameboy_t *gb);
|
|||
void GB_reset(GB_gameboy_t *gb);
|
||||
void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb);
|
||||
void GB_run(GB_gameboy_t *gb);
|
||||
/* Returns the time passed since the last frame, in nanoseconds */
|
||||
uint64_t GB_run_frame(GB_gameboy_t *gb);
|
||||
uint64_t GB_run_cycles(GB_gameboy_t *gb, uint32_t cycles);
|
||||
|
||||
typedef enum {
|
||||
GB_DIRECT_ACCESS_ROM,
|
||||
|
@ -526,7 +493,6 @@ int GB_load_rom(GB_gameboy_t *gb, const char *path);
|
|||
int GB_save_battery(GB_gameboy_t *gb, const char *path);
|
||||
void GB_load_battery(GB_gameboy_t *gb, const char *path);
|
||||
|
||||
void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip);
|
||||
void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled);
|
||||
|
||||
void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3);
|
||||
|
|
|
@ -22,13 +22,6 @@ void GB_update_joyp(GB_gameboy_t *gb)
|
|||
for (uint8_t i = 0; i < 4; i++) {
|
||||
gb->io_registers[GB_IO_JOYP] |= (!gb->keys[i]) << i;
|
||||
}
|
||||
/* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */
|
||||
if (!(gb->io_registers[GB_IO_JOYP] & 1)) {
|
||||
gb->io_registers[GB_IO_JOYP] |= 2;
|
||||
}
|
||||
if (!(gb->io_registers[GB_IO_JOYP] & 4)) {
|
||||
gb->io_registers[GB_IO_JOYP] |= 8;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
|
|
|
@ -456,10 +456,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
|||
gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON;
|
||||
}
|
||||
}
|
||||
else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) {
|
||||
/* Sync after turning off LCD */
|
||||
GB_timing_sync(gb);
|
||||
}
|
||||
gb->io_registers[GB_IO_LCDC] = value;
|
||||
return;
|
||||
|
||||
|
|
|
@ -1,304 +0,0 @@
|
|||
#include "gb.h"
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
static bool dump_section(FILE *f, const void *src, uint32_t size)
|
||||
{
|
||||
if (fwrite(&size, 1, sizeof(size), f) != sizeof(size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fwrite(src, 1, size, f) != size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define DUMP_SECTION(gb, f, section) dump_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
|
||||
|
||||
/* Todo: we need a sane and protable save state format. */
|
||||
int GB_save_state(GB_gameboy_t *gb, const char *path)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) {
|
||||
GB_log(gb, "Could not open save state: %s.\n", strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
|
||||
if (fwrite(GB_GET_SECTION(gb, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
|
||||
if (!DUMP_SECTION(gb, f, core_state)) goto error;
|
||||
if (!DUMP_SECTION(gb, f, dma )) goto error;
|
||||
if (!DUMP_SECTION(gb, f, mbc )) goto error;
|
||||
if (!DUMP_SECTION(gb, f, hram )) goto error;
|
||||
if (!DUMP_SECTION(gb, f, timing )) goto error;
|
||||
if (!DUMP_SECTION(gb, f, apu )) goto error;
|
||||
if (!DUMP_SECTION(gb, f, rtc )) goto error;
|
||||
if (!DUMP_SECTION(gb, f, video )) goto error;
|
||||
|
||||
|
||||
if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
error:
|
||||
fclose(f);
|
||||
return errno;
|
||||
}
|
||||
|
||||
#undef DUMP_SECTION
|
||||
|
||||
size_t GB_get_save_state_size(GB_gameboy_t *gb)
|
||||
{
|
||||
return GB_SECTION_SIZE(header)
|
||||
+ GB_SECTION_SIZE(core_state) + sizeof(uint32_t)
|
||||
+ GB_SECTION_SIZE(dma ) + sizeof(uint32_t)
|
||||
+ GB_SECTION_SIZE(mbc ) + sizeof(uint32_t)
|
||||
+ GB_SECTION_SIZE(hram ) + sizeof(uint32_t)
|
||||
+ GB_SECTION_SIZE(timing ) + sizeof(uint32_t)
|
||||
+ GB_SECTION_SIZE(apu ) + sizeof(uint32_t)
|
||||
+ GB_SECTION_SIZE(rtc ) + sizeof(uint32_t)
|
||||
+ GB_SECTION_SIZE(video ) + sizeof(uint32_t)
|
||||
+ gb->mbc_ram_size
|
||||
+ gb->ram_size
|
||||
+ gb->vram_size;
|
||||
}
|
||||
|
||||
/* A write-line function for memory copying */
|
||||
static void buffer_write(const void *src, size_t size, uint8_t **dest)
|
||||
{
|
||||
memcpy(*dest, src, size);
|
||||
*dest += size;
|
||||
}
|
||||
|
||||
static void buffer_dump_section(uint8_t **buffer, const void *src, uint32_t size)
|
||||
{
|
||||
buffer_write(&size, sizeof(size), buffer);
|
||||
buffer_write(src, size, buffer);
|
||||
}
|
||||
|
||||
#define DUMP_SECTION(gb, buffer, section) buffer_dump_section(&buffer, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
|
||||
void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
|
||||
{
|
||||
buffer_write(GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header), &buffer);
|
||||
DUMP_SECTION(gb, buffer, core_state);
|
||||
DUMP_SECTION(gb, buffer, dma );
|
||||
DUMP_SECTION(gb, buffer, mbc );
|
||||
DUMP_SECTION(gb, buffer, hram );
|
||||
DUMP_SECTION(gb, buffer, timing );
|
||||
DUMP_SECTION(gb, buffer, apu );
|
||||
DUMP_SECTION(gb, buffer, rtc );
|
||||
DUMP_SECTION(gb, buffer, video );
|
||||
|
||||
|
||||
buffer_write(gb->mbc_ram, gb->mbc_ram_size, &buffer);
|
||||
buffer_write(gb->ram, gb->ram_size, &buffer);
|
||||
buffer_write(gb->vram, gb->vram_size, &buffer);
|
||||
}
|
||||
|
||||
/* Best-effort read function for maximum future compatibility. */
|
||||
static bool read_section(FILE *f, void *dest, uint32_t size)
|
||||
{
|
||||
uint32_t saved_size = 0;
|
||||
if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (saved_size <= size) {
|
||||
if (fread(dest, 1, saved_size, f) != saved_size) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (fread(dest, 1, size, f) != size) {
|
||||
return false;
|
||||
}
|
||||
fseek(f, saved_size - size, SEEK_CUR);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#undef DUMP_SECTION
|
||||
|
||||
static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
|
||||
{
|
||||
if (gb->magic != save->magic) {
|
||||
GB_log(gb, "File is not a save state, or is from an incompatible operating system.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gb->version != save->version) {
|
||||
GB_log(gb, "Save state is for a different version of SameBoy.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gb->mbc_ram_size < save->mbc_ram_size) {
|
||||
GB_log(gb, "Save state has non-matching MBC RAM size.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gb->ram_size != save->ram_size) {
|
||||
GB_log(gb, "Save state has non-matching RAM size. Try changing emulated model.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gb->vram_size != save->vram_size) {
|
||||
GB_log(gb, "Save state has non-matching VRAM size. Try changing emulated model.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
|
||||
|
||||
int GB_load_state(GB_gameboy_t *gb, const char *path)
|
||||
{
|
||||
GB_gameboy_t save;
|
||||
|
||||
/* Every unread value should be kept the same. */
|
||||
memcpy(&save, gb, sizeof(save));
|
||||
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) {
|
||||
GB_log(gb, "Could not open save state: %s.\n", strerror(errno));
|
||||
return errno;
|
||||
}
|
||||
|
||||
if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
|
||||
if (!READ_SECTION(&save, f, core_state)) goto error;
|
||||
if (!READ_SECTION(&save, f, dma )) goto error;
|
||||
if (!READ_SECTION(&save, f, mbc )) goto error;
|
||||
if (!READ_SECTION(&save, f, hram )) goto error;
|
||||
if (!READ_SECTION(&save, f, timing )) goto error;
|
||||
if (!READ_SECTION(&save, f, apu )) goto error;
|
||||
if (!READ_SECTION(&save, f, rtc )) goto error;
|
||||
if (!READ_SECTION(&save, f, video )) goto error;
|
||||
|
||||
if (!verify_state_compatibility(gb, &save)) {
|
||||
errno = -1;
|
||||
goto error;
|
||||
}
|
||||
|
||||
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
|
||||
if (fread(gb->mbc_ram, 1, save.mbc_ram_size, f) != save.mbc_ram_size) {
|
||||
fclose(f);
|
||||
return EIO;
|
||||
}
|
||||
|
||||
if (fread(gb->ram, 1, gb->ram_size, f) != gb->ram_size) {
|
||||
fclose(f);
|
||||
return EIO;
|
||||
}
|
||||
|
||||
if (fread(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
|
||||
fclose(f);
|
||||
return EIO;
|
||||
}
|
||||
|
||||
memcpy(gb, &save, sizeof(save));
|
||||
errno = 0;
|
||||
|
||||
if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
|
||||
gb->rumble_callback(gb, gb->rumble_state);
|
||||
}
|
||||
|
||||
error:
|
||||
fclose(f);
|
||||
return errno;
|
||||
}
|
||||
|
||||
#undef READ_SECTION
|
||||
|
||||
/* An read-like function for buffer-copying */
|
||||
static size_t buffer_read(void *dest, size_t length, const uint8_t **buffer, size_t *buffer_length)
|
||||
{
|
||||
if (length > *buffer_length) {
|
||||
length = *buffer_length;
|
||||
}
|
||||
|
||||
memcpy(dest, *buffer, length);
|
||||
*buffer += length;
|
||||
*buffer_length -= length;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size)
|
||||
{
|
||||
uint32_t saved_size = 0;
|
||||
if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (saved_size <= size) {
|
||||
if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (buffer_read(dest, size, buffer, buffer_length) != size) {
|
||||
return false;
|
||||
}
|
||||
*buffer += saved_size - size;
|
||||
*buffer_length -= saved_size - size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
|
||||
int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length)
|
||||
{
|
||||
GB_gameboy_t save;
|
||||
|
||||
/* Every unread value should be kept the same. */
|
||||
memcpy(&save, gb, sizeof(save));
|
||||
|
||||
if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, core_state)) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, dma )) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, mbc )) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, hram )) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, timing )) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, apu )) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, rtc )) return -1;
|
||||
if (!READ_SECTION(&save, buffer, length, video )) return -1;
|
||||
|
||||
if (!verify_state_compatibility(gb, &save)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
|
||||
if (buffer_read(gb->mbc_ram, save.mbc_ram_size, &buffer, &length) != save.mbc_ram_size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buffer_read(gb->ram, gb->ram_size, &buffer, &length) != gb->ram_size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buffer_read(gb->vram,gb->vram_size, &buffer, &length) != gb->vram_size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(gb, &save, sizeof(save));
|
||||
|
||||
if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
|
||||
gb->rumble_callback(gb, gb->rumble_state);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef READ_SECTION
|
|
@ -1,24 +0,0 @@
|
|||
/* Macros to make the GB_gameboy_t struct more future compatible when state saving */
|
||||
#ifndef save_state_h
|
||||
#define save_state_h
|
||||
#include <stddef.h>
|
||||
|
||||
#define GB_PADDING(type, old_usage) type old_usage##__do_not_use
|
||||
|
||||
#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end
|
||||
#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start))
|
||||
#define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start))
|
||||
#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start))
|
||||
|
||||
#define GB_aligned_double __attribute__ ((aligned (8))) double
|
||||
|
||||
|
||||
/* Public calls related to save states */
|
||||
int GB_save_state(GB_gameboy_t *gb, const char *path);
|
||||
size_t GB_get_save_state_size(GB_gameboy_t *gb);
|
||||
/* Assumes buffer is big enough to contain the save state. Use with GB_get_save_state_size(). */
|
||||
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);
|
||||
#endif /* save_state_h */
|
|
@ -1,106 +0,0 @@
|
|||
#include "gb.h"
|
||||
|
||||
static size_t GB_map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr)
|
||||
{
|
||||
if (!map->symbols) {
|
||||
return 0;
|
||||
}
|
||||
ssize_t min = 0;
|
||||
ssize_t max = map->n_symbols;
|
||||
while (min < max) {
|
||||
size_t pivot = (min + max) / 2;
|
||||
if (map->symbols[pivot].addr == addr) return pivot;
|
||||
if (map->symbols[pivot].addr > addr) {
|
||||
max = pivot;
|
||||
}
|
||||
else {
|
||||
min = pivot + 1;
|
||||
}
|
||||
}
|
||||
return (size_t) min;
|
||||
}
|
||||
|
||||
GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name)
|
||||
{
|
||||
size_t index = GB_map_find_symbol_index(map, addr);
|
||||
|
||||
if (index < map->n_symbols && map->symbols[index].addr == addr) return NULL;
|
||||
|
||||
map->symbols = realloc(map->symbols, (map->n_symbols + 1) * sizeof(map->symbols[0]));
|
||||
memmove(&map->symbols[index + 1], &map->symbols[index], (map->n_symbols - index) * sizeof(map->symbols[0]));
|
||||
map->symbols[index].addr = addr;
|
||||
map->symbols[index].name = strdup(name);
|
||||
map->n_symbols++;
|
||||
return &map->symbols[index];
|
||||
}
|
||||
|
||||
const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr)
|
||||
{
|
||||
if (!map) return NULL;
|
||||
size_t index = GB_map_find_symbol_index(map, addr);
|
||||
if (index < map->n_symbols && map->symbols[index].addr != addr) {
|
||||
index--;
|
||||
}
|
||||
if (index < map->n_symbols) {
|
||||
return &map->symbols[index];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GB_symbol_map_t *GB_map_alloc(void)
|
||||
{
|
||||
GB_symbol_map_t *map = malloc(sizeof(*map));
|
||||
memset(map, 0, sizeof(*map));
|
||||
return map;
|
||||
}
|
||||
|
||||
void GB_map_free(GB_symbol_map_t *map)
|
||||
{
|
||||
for (unsigned i = 0; i < map->n_symbols; i++) {
|
||||
free(map->symbols[i].name);
|
||||
}
|
||||
|
||||
if (map->symbols) {
|
||||
free(map->symbols);
|
||||
}
|
||||
|
||||
free(map);
|
||||
}
|
||||
|
||||
static int hash_name(const char *name)
|
||||
{
|
||||
int r = 0;
|
||||
while (*name) {
|
||||
r <<= 1;
|
||||
if (r & 0x400) {
|
||||
r ^= 0x401;
|
||||
}
|
||||
r += (unsigned char)*(name++);
|
||||
}
|
||||
|
||||
return r & 0x3FF;
|
||||
}
|
||||
|
||||
void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *bank_symbol)
|
||||
{
|
||||
int hash = hash_name(bank_symbol->name);
|
||||
GB_symbol_t *symbol = malloc(sizeof(*symbol));
|
||||
symbol->name = bank_symbol->name;
|
||||
symbol->addr = bank_symbol->addr;
|
||||
symbol->bank = bank;
|
||||
symbol->next = map->buckets[hash];
|
||||
map->buckets[hash] = symbol;
|
||||
}
|
||||
|
||||
const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name)
|
||||
{
|
||||
int hash = hash_name(name);
|
||||
GB_symbol_t *symbol = map->buckets[hash];
|
||||
|
||||
while (symbol) {
|
||||
if (strcmp(symbol->name, name) == 0) return symbol;
|
||||
symbol = symbol->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
#ifndef symbol_hash_h
|
||||
#define symbol_hash_h
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
uint16_t addr;
|
||||
} GB_bank_symbol_t;
|
||||
|
||||
typedef struct GB_symbol_s {
|
||||
struct GB_symbol_s *next;
|
||||
const char *name;
|
||||
uint16_t bank;
|
||||
uint16_t addr;
|
||||
} GB_symbol_t;
|
||||
|
||||
typedef struct {
|
||||
GB_bank_symbol_t *symbols;
|
||||
size_t n_symbols;
|
||||
} GB_symbol_map_t;
|
||||
|
||||
typedef struct {
|
||||
GB_symbol_t *buckets[0x400];
|
||||
} GB_reversed_symbol_map_t;
|
||||
|
||||
#ifdef GB_INTERNAL
|
||||
void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *symbol);
|
||||
const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name);
|
||||
GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name);
|
||||
const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr);
|
||||
GB_symbol_map_t *GB_map_alloc(void);
|
||||
void GB_map_free(GB_symbol_map_t *map);
|
||||
#endif
|
||||
|
||||
#endif /* symbol_hash_h */
|
|
@ -6,69 +6,6 @@
|
|||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
static int64_t get_nanoseconds(void)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
return (now.tv_usec) * 1000 + now.tv_sec * 1000000000L;
|
||||
#else
|
||||
FILETIME time;
|
||||
GetSystemTimeAsFileTime(&time);
|
||||
return (((int64_t)time.dwHighDateTime << 32) | time.dwLowDateTime) * 100L;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void nsleep(uint64_t nanoseconds)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
struct timespec sleep = {0, nanoseconds};
|
||||
nanosleep(&sleep, NULL);
|
||||
#else
|
||||
HANDLE timer;
|
||||
LARGE_INTEGER time;
|
||||
timer = CreateWaitableTimer(NULL, true, NULL);
|
||||
time.QuadPart = -(nanoseconds / 100L);
|
||||
SetWaitableTimer(timer, &time, 0, NULL, NULL, false);
|
||||
WaitForSingleObject(timer, INFINITE);
|
||||
CloseHandle(timer);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GB_timing_sync_turbo(GB_gameboy_t *gb)
|
||||
{
|
||||
if (!gb->turbo_dont_skip) {
|
||||
int64_t nanoseconds = get_nanoseconds();
|
||||
if (nanoseconds <= gb->last_sync + FRAME_LENGTH) {
|
||||
return true;
|
||||
}
|
||||
gb->last_sync = nanoseconds;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GB_timing_sync(GB_gameboy_t *gb)
|
||||
{
|
||||
if (gb->turbo) {
|
||||
gb->cycles_since_last_sync = 0;
|
||||
return;
|
||||
}
|
||||
/* Prevent syncing if not enough time has passed.*/
|
||||
if (gb->cycles_since_last_sync < LCDC_PERIOD / 4) return;
|
||||
|
||||
uint64_t target_nanoseconds = gb->cycles_since_last_sync * FRAME_LENGTH / LCDC_PERIOD;
|
||||
int64_t nanoseconds = get_nanoseconds();
|
||||
if (labs((signed long)(nanoseconds - gb->last_sync)) < target_nanoseconds ) {
|
||||
nsleep(target_nanoseconds + gb->last_sync - nanoseconds);
|
||||
gb->last_sync += target_nanoseconds;
|
||||
}
|
||||
else {
|
||||
gb->last_sync = nanoseconds;
|
||||
}
|
||||
|
||||
gb->cycles_since_last_sync = 0;
|
||||
}
|
||||
|
||||
static void GB_ir_run(GB_gameboy_t *gb)
|
||||
{
|
||||
if (gb->ir_queue_length == 0) return;
|
||||
|
@ -136,7 +73,7 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
|||
gb->apu.apu_cycles += cycles;
|
||||
gb->cycles_since_ir_change += cycles;
|
||||
gb->cycles_since_input_ir_change += cycles;
|
||||
gb->cycles_since_last_sync += cycles;
|
||||
gb->cycles_since_epoch += cycles >> 1;
|
||||
GB_dma_run(gb);
|
||||
GB_hdma_run(gb);
|
||||
GB_apu_run(gb);
|
||||
|
|
|
@ -7,9 +7,6 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles);
|
|||
void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value);
|
||||
void GB_rtc_run(GB_gameboy_t *gb);
|
||||
void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac);
|
||||
bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */
|
||||
void GB_timing_sync(GB_gameboy_t *gb);
|
||||
|
||||
|
||||
enum {
|
||||
GB_TIMA_RUNNING = 0,
|
||||
|
|
|
@ -283,7 +283,7 @@ static void jr_r8(GB_gameboy_t *gb, uint8_t opcode)
|
|||
{
|
||||
/* Todo: Verify cycles are not 8 and 4 instead */
|
||||
GB_advance_cycles(gb, 4);
|
||||
gb->pc += (int8_t) GB_read_memory(gb, gb->pc++);
|
||||
gb->pc += (int8_t)GB_read_memory(gb, gb->pc) + 1;
|
||||
GB_advance_cycles(gb, 8);
|
||||
}
|
||||
|
||||
|
@ -307,7 +307,7 @@ static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode)
|
|||
{
|
||||
if (condition_code(gb, opcode)) {
|
||||
GB_advance_cycles(gb, 4);
|
||||
gb->pc += (int8_t)GB_read_memory(gb, gb->pc++);
|
||||
gb->pc += (int8_t)GB_read_memory(gb, gb->pc) + 1;
|
||||
GB_advance_cycles(gb, 8);
|
||||
}
|
||||
else {
|
||||
|
|
Loading…
Reference in New Issue