diff --git a/.vscode/settings.json b/.vscode/settings.json index 1bc3f03..d2f621c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -87,7 +87,8 @@ "queue": "cpp", "stack": "cpp", "variant": "cpp", - "cassert": "cpp" + "cassert": "cpp", + "__config": "cpp" }, "todo-tree.tree.showBadges": true, "todo-tree.tree.disableCompactFolders": false, diff --git a/platforms/shared/desktop/emu.cpp b/platforms/shared/desktop/emu.cpp index ecae25d..aab909e 100644 --- a/platforms/shared/desktop/emu.cpp +++ b/platforms/shared/desktop/emu.cpp @@ -42,6 +42,7 @@ static void destroy_debug(void); static void update_debug(void); static void update_debug_background(void); static void update_debug_sprites(void); +static void update_savestates_data(void); void emu_init(void) { @@ -57,6 +58,9 @@ void emu_init(void) sound_queue = new SoundQueue(); sound_queue->Start(GG_AUDIO_SAMPLE_RATE, 2, GG_AUDIO_BUFFER_SIZE, GG_AUDIO_BUFFER_COUNT); + for (int i = 0; i < 5; i++) + InitPointer(emu_savestates_screenshots[i].data); + audio_enabled = true; emu_audio_sync = true; emu_savefiles_dir_option = 0; @@ -77,6 +81,9 @@ void emu_destroy(void) SafeDelete(geargrafx); SafeDeleteArray(emu_frame_buffer); destroy_debug(); + + for (int i = 0; i < 5; i++) + SafeDeleteArray(emu_savestates_screenshots[i].data); } void emu_load_rom(const char* file_path) @@ -87,6 +94,8 @@ void emu_load_rom(const char* file_path) save_ram(); geargrafx->LoadROM(file_path); load_ram(); + + update_savestates_data(); } void emu_update(void) @@ -217,44 +226,38 @@ void emu_load_ram(const char* file_path) void emu_save_state_slot(int index) { - // TODO Implement save states - // if (!emu_is_empty()) - // { - // if ((emu_savestates_dir_option == 0) && (strcmp(emu_savestates_path, ""))) - // geargrafx->SaveState(emu_savestates_path, index); - // else - // geargrafx->SaveState(index); - // } - UNUSED(index); + if (!emu_is_empty()) + { + if ((emu_savestates_dir_option == 0) && (strcmp(emu_savestates_path, ""))) + geargrafx->SaveState(emu_savestates_path, index, true); + else + geargrafx->SaveState(NULL, index, true); + + update_savestates_data(); + } } void emu_load_state_slot(int index) { - // TODO Implement save states - // if (!emu_is_empty()) - // { - // if ((emu_savestates_dir_option == 0) && (strcmp(emu_savestates_path, ""))) - // geargrafx->LoadState(emu_savestates_path, index); - // else - // geargrafx->LoadState(index); - // } - UNUSED(index); + if (!emu_is_empty()) + { + if ((emu_savestates_dir_option == 0) && (strcmp(emu_savestates_path, ""))) + geargrafx->LoadState(emu_savestates_path, index); + else + geargrafx->LoadState(NULL, index); + } } void emu_save_state_file(const char* file_path) { - // TODO Implement save states - // if (!emu_is_empty()) - // geargrafx->SaveState(file_path, -1); - UNUSED(file_path); + if (!emu_is_empty()) + geargrafx->SaveState(file_path, -1, true); } void emu_load_state_file(const char* file_path) { - // TODO Implement save states - // if (!emu_is_empty()) - // geargrafx->LoadState(file_path, -1); - UNUSED(file_path); + if (!emu_is_empty()) + geargrafx->LoadState(file_path); } void emu_get_runtime(GG_Runtime_Info& runtime) @@ -582,3 +585,27 @@ static void update_debug_sprites(void) } } } + +static void update_savestates_data(void) +{ + if (emu_is_empty()) + return; + + bool using_path = (emu_savestates_dir_option == 0) && (strcmp(emu_savestates_path, "")); + + for (int i = 0; i < 5; i++) + { + emu_savestates[i].rom_name[0] = 0; + SafeDeleteArray(emu_savestates_screenshots[i].data); + + if (!geargrafx->GetSaveStateHeader(i + 1, using_path ? emu_savestates_path : NULL, &emu_savestates[i])) + continue; + + if (emu_savestates[i].screenshot_size > 0) + { + emu_savestates_screenshots[i].data = new u8[emu_savestates[i].screenshot_size]; + emu_savestates_screenshots[i].size = emu_savestates[i].screenshot_size; + geargrafx->GetSaveStateScreenshot(i + 1, using_path ? emu_savestates_path : NULL, &emu_savestates_screenshots[i]); + } + } +} diff --git a/platforms/shared/desktop/emu.h b/platforms/shared/desktop/emu.h index ddf8d18..3f61c44 100644 --- a/platforms/shared/desktop/emu.h +++ b/platforms/shared/desktop/emu.h @@ -38,6 +38,8 @@ enum Debug_Command EXTERN u8* emu_frame_buffer; +EXTERN GG_SaveState_Header emu_savestates[5]; +EXTERN GG_SaveState_Screenshot emu_savestates_screenshots[5]; EXTERN u8* emu_debug_sprite_buffers[64]; EXTERN u8* emu_debug_background_buffer; EXTERN int emu_debug_background_buffer_width; diff --git a/platforms/shared/desktop/gui_menus.cpp b/platforms/shared/desktop/gui_menus.cpp index 6a75421..d0556e6 100644 --- a/platforms/shared/desktop/gui_menus.cpp +++ b/platforms/shared/desktop/gui_menus.cpp @@ -27,6 +27,7 @@ #include "config.h" #include "application.h" #include "emu.h" +#include "renderer.h" #include "../../../src/geargrafx.h" static char savefiles_path[4096] = ""; @@ -50,6 +51,7 @@ static void menu_about(void); static void file_dialogs(void); static void keyboard_configuration_item(const char* text, SDL_Scancode* key, int player); static void gamepad_configuration_item(const char* text, int* button, int player); +static void draw_savestate_slot_info(int slot); void gui_init_menus(void) { @@ -165,6 +167,10 @@ static void menu_geargrafx(void) ImGui::PushItemWidth(100.0f); ImGui::Combo("##slot", &config_emulator.save_slot, "Slot 1\0Slot 2\0Slot 3\0Slot 4\0Slot 5\0\0"); ImGui::PopItemWidth(); + + ImGui::Separator(); + draw_savestate_slot_info(config_emulator.save_slot); + ImGui::EndMenu(); } @@ -183,6 +189,14 @@ static void menu_geargrafx(void) gui_set_status_message(message.c_str(), 3000); emu_load_state_slot(config_emulator.save_slot + 1); } + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("Slot: %d", config_emulator.save_slot + 1); + ImGui::Separator(); + draw_savestate_slot_info(config_emulator.save_slot); + ImGui::EndTooltip(); + } ImGui::Separator(); @@ -640,3 +654,25 @@ static void gamepad_configuration_item(const char* text, int* button, int player *button = SDL_CONTROLLER_BUTTON_INVALID; } } + +static void draw_savestate_slot_info(int slot) +{ + if (emu_savestates[slot].rom_name[0] != 0) + { + ImGui::Text("%s", emu_savestates[slot].rom_name); + char date[64]; + GetDateTimeString(emu_savestates[slot].timestamp, date, sizeof(date)); + ImGui::Text("%s", date); + + if (IsValidPointer(emu_savestates_screenshots[slot].data)) + { + float width = emu_savestates_screenshots[slot].width; + float height = emu_savestates_screenshots[slot].height; + ImGui::Image((ImTextureID)(intptr_t)renderer_emu_savestates[slot], ImVec2((height / 3.0f) * 4.0f, height), ImVec2(0, 0), ImVec2(width / 512.0f, height / 512.0f)); + } + } + else + { + ImGui::TextColored(ImVec4(0.50f, 0.50f, 0.50f, 1.0f), "Slot %d is empty", slot + 1); + } +} diff --git a/platforms/shared/desktop/renderer.cpp b/platforms/shared/desktop/renderer.cpp index e0dd512..330f8fb 100644 --- a/platforms/shared/desktop/renderer.cpp +++ b/platforms/shared/desktop/renderer.cpp @@ -48,6 +48,7 @@ static u32 scanlines[16] = { static void init_ogl_gui(void); static void init_ogl_emu(void); static void init_ogl_debug(void); +static void init_ogl_savestates(void); static void init_scanlines_texture(void); static void render_gui(void); static void render_emu_normal(void); @@ -56,6 +57,7 @@ static void render_emu_bilinear(void); static void render_quad(void); static void update_system_texture(void); static void update_debug_textures(void); +static void update_savestates_textures(void); static void render_scanlines(void); void renderer_init(void) @@ -77,6 +79,7 @@ void renderer_init(void) init_ogl_gui(); init_ogl_emu(); init_ogl_debug(); + init_ogl_savestates(); first_frame = true; } @@ -89,6 +92,8 @@ void renderer_destroy(void) glDeleteTextures(1, &scanlines_texture); glDeleteTextures(1, &renderer_emu_debug_huc6270_background); glDeleteTextures(64, renderer_emu_debug_huc6270_sprites); + glDeleteTextures(5, renderer_emu_savestates); + ImGui_ImplOpenGL2_Shutdown(); } @@ -106,6 +111,8 @@ void renderer_render(void) update_debug_textures(); } + update_savestates_textures(); + if (config_video.mix_frames) render_emu_mix(); else @@ -188,6 +195,18 @@ static void init_ogl_debug(void) } } +static void init_ogl_savestates(void) +{ + for (int i = 0; i < 5; i++) + { + glGenTextures(1, &renderer_emu_savestates[i]); + glBindTexture(GL_TEXTURE_2D, renderer_emu_savestates[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } +} + static void init_scanlines_texture(void) { glGenTextures(1, &scanlines_texture); @@ -284,6 +303,20 @@ static void update_debug_textures(void) } } +static void update_savestates_textures(void) +{ + for (int i = 0; i < 5; i++) + { + if (IsValidPointer(emu_savestates_screenshots[i].data)) + { + int width = emu_savestates_screenshots[i].width; + int height = emu_savestates_screenshots[i].height; + glBindTexture(GL_TEXTURE_2D, renderer_emu_savestates[i]); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*) emu_savestates_screenshots[i].data); + } + } +} + static void render_emu_bilinear(void) { glBindTexture(GL_TEXTURE_2D, renderer_emu_texture); diff --git a/platforms/shared/desktop/renderer.h b/platforms/shared/desktop/renderer.h index 5ae09aa..0a2dd8f 100644 --- a/platforms/shared/desktop/renderer.h +++ b/platforms/shared/desktop/renderer.h @@ -37,6 +37,7 @@ EXTERN uint32_t renderer_emu_texture; EXTERN uint32_t renderer_emu_debug_huc6270_background; EXTERN uint32_t renderer_emu_debug_huc6270_sprites[64]; +EXTERN uint32_t renderer_emu_savestates[5]; EXTERN const char* renderer_glew_version; EXTERN const char* renderer_opengl_version; diff --git a/src/audio.cpp b/src/audio.cpp index 1db1626..b2d4b2c 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -82,12 +82,16 @@ HuC6280PSG* Audio::GetPSG() return m_psg; } -// void Audio::SaveState(std::ostream& stream) -// { -// stream.write(reinterpret_cast (&m_elapsed_cycles), sizeof(m_ElapsedCycles)); -// } - -// void Audio::LoadState(std::istream& stream) -// { -// stream.read(reinterpret_cast (&m_elapsed_cycles), sizeof(m_ElapsedCycles)); -// } \ No newline at end of file +void Audio::SaveState(std::ostream& stream) +{ + using namespace std; + stream.write(reinterpret_cast (m_psg_buffer), sizeof(s16) * GG_AUDIO_BUFFER_SIZE); + m_psg->SaveState(stream); +} + +void Audio::LoadState(std::istream& stream) +{ + using namespace std; + stream.read(reinterpret_cast (m_psg_buffer), sizeof(s16) * GG_AUDIO_BUFFER_SIZE); + m_psg->LoadState(stream); +} \ No newline at end of file diff --git a/src/audio.h b/src/audio.h index 5d3a880..2779fd6 100644 --- a/src/audio.h +++ b/src/audio.h @@ -20,6 +20,8 @@ #ifndef AUDIO_H #define AUDIO_H +#include +#include #include "types.h" class HuC6280PSG; @@ -36,8 +38,8 @@ class Audio void WritePSG(u32 address, u8 value); void EndFrame(s16* sample_buffer, int* sample_count); HuC6280PSG* GetPSG(); - // void SaveState(std::ostream& stream); - // void LoadState(std::istream& stream); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); private: bool m_mute; diff --git a/src/common.h b/src/common.h index 4e9e6a8..4ccf52e 100644 --- a/src/common.h +++ b/src/common.h @@ -20,6 +20,7 @@ #ifndef COMMON_H #define COMMON_H +#include #include "defines.h" #include "types.h" #include "log.h" @@ -41,4 +42,16 @@ inline unsigned int Pow2Ceil(u16 n) return n; } +inline void GetDateTimeString(time_t timestamp, char* buffer, size_t size) +{ + struct tm* timeinfo = localtime(×tamp); + strftime(buffer, size, "%Y-%m-%d %H:%M:%S", timeinfo); +} + +inline void GetCurrentDateTimeString(char* buffer, size_t size) +{ + time_t timestamp = time(NULL); + GetDateTimeString(timestamp, buffer, size); +} + #endif /* COMMON_H */ \ No newline at end of file diff --git a/src/defines.h b/src/defines.h index 725709c..9bfdc9f 100644 --- a/src/defines.h +++ b/src/defines.h @@ -35,12 +35,13 @@ " \\____|\\___|\\__,_|_| \\__, |_| \\__,_|_| /_/\\_\\\n" \ " |___/ \n" -#define GG_CLOCK_RATE 21477273 - #if defined(DEBUG) #define GG_DEBUG 1 #endif +#define GG_SAVESTATE_VERSION 1 +#define GG_SAVESTATE_MAGIC 0x82190619 + #if !defined(NULL) #define NULL 0 #endif diff --git a/src/geargrafx_core.cpp b/src/geargrafx_core.cpp index 7b854b8..4e4cde5 100644 --- a/src/geargrafx_core.cpp +++ b/src/geargrafx_core.cpp @@ -19,6 +19,9 @@ #include #include +#include +#include +#include #include "geargrafx_core.h" #include "common.h" #include "cartridge.h" @@ -303,255 +306,319 @@ void GeargrafxCore::ResetSound() // // TODO Implement load ram // } -// void GeargrafxCore::SaveState(int index) -// { -// TODO Implement save states -// Log("Creating save state %d...", index); +std::string GeargrafxCore::GetSaveStatePath(const char* path, int index) +{ + using namespace std; + string full_path; -// SaveState(NULL, index); + if (IsValidPointer(path)) + { + full_path = path; + full_path += "/"; + full_path += m_cartridge->GetFileName(); + } + else + full_path = m_cartridge->GetFilePath(); -// Log("Save state %d created", index); -// } + string::size_type dot_index = full_path.rfind('.'); -// void GeargrafxCore::SaveState(const char* szPath, int index) -// { -// Log("Creating save state..."); + if (dot_index != string::npos) + full_path.replace(dot_index + 1, full_path.length() - dot_index - 1, "state"); -// using namespace std; + if (index >= 0) + full_path += to_string(index); -// size_t size; -// SaveState(NULL, size); + return full_path; +} -// u8* buffer = new u8[size]; -// string path = ""; +void GeargrafxCore::SaveState(const char* path, int index, bool screenshot) +{ + using namespace std; + string full_path = GetSaveStatePath(path, index); -// if (IsValidPointer(szPath)) -// { -// path += szPath; -// path += "/"; -// path += m_cartridge->GetFileName(); -// } -// else -// { -// path = m_cartridge->GetFilePath(); -// } + ofstream stream(full_path.c_str(), ios::out | ios::binary); -// string::size_type i = path.rfind('.', path.length()); + size_t size; + SaveState(stream, size, screenshot); +} -// if (i != string::npos) { -// path.replace(i + 1, 3, "state"); -// } +bool GeargrafxCore::SaveState(u8* buffer, size_t& size, bool screenshot) +{ + using namespace std; -// stringstream sstm; + if (!m_cartridge->IsReady()) + { + Log("Cartridge is not ready when trying to save state"); + return false; + } -// if (index < 0) -// sstm << szPath; -// else -// sstm << path << index; + stringstream stream; -// Log("Save state file: %s", sstm.str().c_str()); + if (SaveState(stream, size, screenshot)) + { + if (IsValidPointer(buffer)) + { + Log("Saving state to buffer [%d bytes]...", size); + memcpy(buffer, stream.str().c_str(), size); + } + else + { + Debug("Calculating state size: %d bytes", size); + } + } -// ofstream file(sstm.str().c_str(), ios::out | ios::binary); + return true; +} -// SaveState(file, size); +bool GeargrafxCore::SaveState(std::ostream& stream, size_t& size, bool screenshot) +{ + using namespace std; -// SafeDeleteArray(buffer); + if (!m_cartridge->IsReady()) + { + Log("Cartridge is not ready when trying to save state"); + return false; + } -// file.close(); + Debug("Gathering save state data..."); -// Log("Save state created"); -// } + stream.write(reinterpret_cast (&m_clock), sizeof(m_clock)); + m_memory->SaveState(stream); + m_huc6260->SaveState(stream); + m_huc6270->SaveState(stream); + m_huc6280->SaveState(stream); + m_audio->SaveState(stream); + m_input->SaveState(stream); -// bool GeargrafxCore::SaveState(u8* buffer, size_t& size) -// { -// bool ret = false; - -// if (m_cartridge->IsReady()) -// { -// using namespace std; - -// stringstream stream; - -// if (SaveState(stream, size)) -// ret = true; - -// if (IsValidPointer(buffer)) -// { -// Log("Saving state to buffer [%d bytes]...", size); -// memcpy(buffer, stream.str().c_str(), size); -// ret = true; -// } -// } -// else -// { -// Log("Invalid rom."); -// } - -// return ret; -// } + GG_SaveState_Header header; + header.magic = GG_SAVESTATE_MAGIC; + header.version = GG_SAVESTATE_VERSION; + header.timestamp = time(NULL); + strncpy(header.rom_name, m_cartridge->GetFileName(), sizeof(header.rom_name)); + header.rom_crc = m_cartridge->GetCRC(); -// bool GeargrafxCore::SaveState(std::ostream& stream, size_t& size) -// { -// if (m_cartridge->IsReady()) -// { -// Log("Gathering save state data..."); + if (screenshot) + { + header.screenshot_width = m_huc6260->GetCurrentLineWidth(); + header.screenshot_height = m_huc6260->GetCurrentHeight(); -// m_memory->SaveState(stream); -// m_processor->SaveState(stream); -// m_audio->SaveState(stream); -// m_video->SaveState(stream); -// m_input->SaveState(stream); + int bytes_per_pixel = 2; + if (m_huc6260->GetPixelFormat() == GG_PIXEL_RGBA8888 || m_huc6260->GetPixelFormat() == GG_PIXEL_BGRA8888) + bytes_per_pixel = 4; -// size = static_cast(stream.tellp()); -// size += (sizeof(u32) * 2); + u8* frame_buffer = m_huc6260->GetBuffer(); -// u32 header_magic = GC_SAVESTATE_MAGIC; -// u32 header_size = static_cast(size); + header.screenshot_size = header.screenshot_width * header.screenshot_height * bytes_per_pixel; + stream.write(reinterpret_cast(frame_buffer), header.screenshot_size); + } + else + { + header.screenshot_size = 0; + header.screenshot_width = 0; + header.screenshot_height = 0; + } -// stream.write(reinterpret_cast (&header_magic), sizeof(header_magic)); -// stream.write(reinterpret_cast (&header_size), sizeof(header_size)); + size = static_cast(stream.tellp()); + size += sizeof(header); + header.size = static_cast(size); -// Log("Save state size: %d", static_cast(stream.tellp())); + stream.write(reinterpret_cast(&header), sizeof(header)); -// return true; -// } + Log("Save state size: %d", static_cast(stream.tellp())); -// Log("Invalid rom."); + return true; +} -// return false; -// } +void GeargrafxCore::LoadState(const char* path, int index) +{ + using namespace std; + string full_path = GetSaveStatePath(path, index); -// void GeargrafxCore::LoadState(int index) -// { -// Log("Loading save state %d...", index); + ifstream stream; + stream.open(full_path.c_str(), ios::in | ios::binary); -// LoadState(NULL, index); + if (!stream.fail()) + { + LoadState(stream); + } + else + { + Log("Load state file doesn't exist"); + } -// Log("State %d file loaded", index); -// } + stream.close(); +} -// void GeargrafxCore::LoadState(const char* szPath, int index) -// { -// Log("Loading save state..."); +bool GeargrafxCore::LoadState(const u8* buffer, size_t size) +{ + using namespace std; -// using namespace std; + if (!m_cartridge->IsReady()) + { + Log("Cartridge is not ready when trying to load state"); + return false; + } -// string sav_path = ""; + if (!IsValidPointer(buffer) || (size == 0)) + { + Log("Invalid load state buffer"); + return false; + } -// if (IsValidPointer(szPath)) -// { -// sav_path += szPath; -// sav_path += "/"; -// sav_path += m_cartridge->GetFileName(); -// } -// else -// { -// sav_path = m_cartridge->GetFilePath(); -// } + stringstream stream; + stream.write(reinterpret_cast (buffer), size); -// string rom_path = sav_path; + return LoadState(stream); +} -// string::size_type i = sav_path.rfind('.', sav_path.length()); +bool GeargrafxCore::LoadState(std::istream& stream) +{ + using namespace std; -// if (i != string::npos) { -// sav_path.replace(i + 1, 3, "state"); -// } + if (!m_cartridge->IsReady()) + { + Log("Cartridge is not ready when trying to load state"); + return false; + } -// std::stringstream sstm; + GG_SaveState_Header header; -// if (index < 0) -// sstm << szPath; -// else -// sstm << sav_path << index; + stream.seekg(0, ios::end); + size_t size = static_cast(stream.tellg()); + stream.seekg(0, ios::beg); -// Log("Opening save file: %s", sstm.str().c_str()); + stream.seekg(size - sizeof(header), ios::beg); + stream.read(reinterpret_cast (&header), sizeof(header)); + stream.seekg(0, ios::beg); -// ifstream file; + Debug("Load state stream size: %d", size); + Debug("Load state header magic: 0x%08x", header.magic); + Debug("Load state header version: %d", header.version); + Debug("Load state header size: %d", header.size); + Debug("Load state header timestamp: %d", header.timestamp); + Debug("Load state header rom crc: 0x%08x", header.rom_crc); + Debug("Load state header screenshot size: %d", header.screenshot_size); -// file.open(sstm.str().c_str(), ios::in | ios::binary); + if ((header.magic != GG_SAVESTATE_MAGIC)) + { + Log("Invalid save state"); + return false; + } -// if (!file.fail()) -// { -// if (LoadState(file)) -// { -// Log("Save state loaded"); -// } -// } -// else -// { -// Log("Save state file doesn't exist"); -// } + if (header.version != GG_SAVESTATE_VERSION) + { + Log("Invalid save state version"); + return false; + } -// file.close(); -// } + if (header.size != size) + { + Log("Invalid save state size"); + return false; + } -// bool GeargrafxCore::LoadState(const u8* buffer, size_t size) -// { -// if (m_cartridge->IsReady() && (size > 0) && IsValidPointer(buffer)) -// { -// Log("Gathering load state data [%d bytes]...", size); + if (header.rom_crc != m_cartridge->GetCRC()) + { + Log("Invalid save state rom crc"); + return false; + } -// using namespace std; + Log("Loading state..."); -// stringstream stream; + stream.read(reinterpret_cast (&m_clock), sizeof(m_clock)); + m_memory->LoadState(stream); + m_huc6260->LoadState(stream); + m_huc6270->LoadState(stream); + m_huc6280->LoadState(stream); + m_audio->LoadState(stream); + m_input->LoadState(stream); -// stream.write(reinterpret_cast (buffer), size); + return true; +} -// return LoadState(stream); -// } +bool GeargrafxCore::GetSaveStateHeader(int index, const char* path, GG_SaveState_Header* header) +{ + using namespace std; -// Log("Invalid rom or memory."); + string full_path = GetSaveStatePath(path, index); -// return false; -// } + ifstream stream; + stream.open(full_path.c_str(), ios::in | ios::binary); -// bool GeargrafxCore::LoadState(std::istream& stream) -// { -// if (m_cartridge->IsReady()) -// { -// using namespace std; - -// u32 header_magic = 0; -// u32 header_size = 0; - -// stream.seekg(0, ios::end); -// size_t size = static_cast(stream.tellg()); -// stream.seekg(0, ios::beg); - -// Log("Load state stream size: %d", size); - -// stream.seekg(size - (2 * sizeof(u32)), ios::beg); -// stream.read(reinterpret_cast (&header_magic), sizeof(header_magic)); -// stream.read(reinterpret_cast (&header_size), sizeof(header_size)); -// stream.seekg(0, ios::beg); - -// Log("Load state magic: 0x%08x", header_magic); -// Log("Load state size: %d", header_size); - -// if ((header_size == size) && (header_magic == GC_SAVESTATE_MAGIC)) -// { -// Log("Loading state..."); - -// m_memory->LoadState(stream); -// m_processor->LoadState(stream); -// m_audio->LoadState(stream); -// m_video->LoadState(stream); -// m_input->LoadState(stream); - -// return true; -// } -// else -// { -// Log("Invalid save state size or header"); -// } -// } -// else -// { -// Log("Invalid rom"); -// } - -// return false; -// } + if (stream.fail()) + { + Log("Savestate file doesn't exist"); + stream.close(); + return false; + } + + stream.seekg(0, ios::end); + size_t savestate_size = static_cast(stream.tellg()); + stream.seekg(0, ios::beg); + + stream.seekg(savestate_size - sizeof(GG_SaveState_Header), ios::beg); + stream.read(reinterpret_cast (header), sizeof(GG_SaveState_Header)); + stream.seekg(0, ios::beg); + + return true; +} + +bool GeargrafxCore::GetSaveStateScreenshot(int index, const char* path, GG_SaveState_Screenshot* screenshot) +{ + using namespace std; + + if (!IsValidPointer(screenshot->data) || (screenshot->size == 0)) + { + Log("Invalid save state screenshot buffer"); + return false; + } + + string full_path = GetSaveStatePath(path, index); + + ifstream stream; + stream.open(full_path.c_str(), ios::in | ios::binary); + + if (stream.fail()) + { + Log("Screenshot file doesn't exist"); + stream.close(); + return false; + } + + GG_SaveState_Header header; + + stream.seekg(0, ios::end); + size_t savestate_size = static_cast(stream.tellg()); + stream.seekg(0, ios::beg); + + stream.seekg(savestate_size - sizeof(header), ios::beg); + stream.read(reinterpret_cast (&header), sizeof(header)); + stream.seekg(0, ios::beg); + + if (header.screenshot_size == 0) + { + Log("No screenshot data"); + stream.close(); + return false; + } + + if (screenshot->size < header.screenshot_size) + { + Log("Invalid screenshot buffer size"); + stream.close(); + return false; + } + + screenshot->size = header.screenshot_size; + screenshot->width = header.screenshot_width; + screenshot->height = header.screenshot_height; + + stream.seekg(savestate_size - sizeof(header) - screenshot->size, ios::beg); + stream.read(reinterpret_cast (screenshot->data), screenshot->size); + stream.close(); + + return true; +} void GeargrafxCore::Reset() { diff --git a/src/geargrafx_core.h b/src/geargrafx_core.h index f3e6b70..3cdcba8 100644 --- a/src/geargrafx_core.h +++ b/src/geargrafx_core.h @@ -20,6 +20,8 @@ #ifndef GEARGRAFX_CORE_H #define GEARGRAFX_CORE_H +#include +#include #include "types.h" class Audio; @@ -71,14 +73,12 @@ class GeargrafxCore // void SaveRam(const char* path, bool full_path = false); // void LoadRam(); // void LoadRam(const char* path, bool full_path = false); - // void SaveState(int index); - // void SaveState(const char* path, int index); - // bool SaveState(u8* buffer, size_t& size); - // bool SaveState(std::ostream& stream, size_t& size); - // void LoadState(int index); - // void LoadState(const char* path, int index); - // bool LoadState(const u8* buffer, size_t size); - // bool LoadState(std::istream& stream); + void SaveState(const char* path = NULL, int index = -1, bool screenshot = false); + bool SaveState(u8* buffer, size_t& size, bool screenshot = false); + void LoadState(const char* path = NULL, int index = -1); + bool LoadState(const u8* buffer, size_t size); + bool GetSaveStateHeader(int index, const char* path, GG_SaveState_Header* header); + bool GetSaveStateScreenshot(int index, const char* path, GG_SaveState_Screenshot* screenshot); void ResetSound(); bool GetRuntimeInfo(GG_Runtime_Info& runtime_info); Memory* GetMemory(); @@ -92,6 +92,9 @@ class GeargrafxCore private: void Reset(); + bool SaveState(std::ostream& stream, size_t& size, bool screenshot); + bool LoadState(std::istream& stream); + std::string GetSaveStatePath(const char* path, int index); private: Memory* m_memory; @@ -102,7 +105,7 @@ class GeargrafxCore Input* m_input; Cartridge* m_cartridge; bool m_paused; - int m_clock; + u64 m_clock; GG_Debug_Callback m_debug_callback; }; diff --git a/src/huc6260.cpp b/src/huc6260.cpp index 5772052..15ff6a3 100644 --- a/src/huc6260.cpp +++ b/src/huc6260.cpp @@ -97,7 +97,7 @@ void HuC6260::Reset() m_hsync = false; m_vsync = false; m_blur = 0; - m_black_and_white = false; + m_black_and_white = 0; for (int i = 0; i < 512; i++) { @@ -204,6 +204,11 @@ void HuC6260::SetBuffer(u8* frame_buffer) m_frame_buffer = frame_buffer; } +u8* HuC6260::GetBuffer() +{ + return m_frame_buffer; +} + int HuC6260::GetCurrentLineWidth() { #if defined(HUC6260_DEBUG) @@ -237,6 +242,11 @@ void HuC6260::SetOverscan(bool overscan) m_overscan = overscan ? 1 : 0; } +GG_Pixel_Format HuC6260::GetPixelFormat() +{ + return m_pixel_format; +} + void HuC6260::WritePixel(u16 pixel) { assert(pixel < 512); @@ -318,4 +328,42 @@ void HuC6260::WritePixel(u16 pixel) } m_pixel_index++; +} + +void HuC6260::SaveState(std::ostream& stream) +{ + using namespace std; + stream.write(reinterpret_cast (&m_control_register), sizeof(m_control_register)); + stream.write(reinterpret_cast (&m_color_table_address), sizeof(m_color_table_address)); + stream.write(reinterpret_cast (&m_speed), sizeof(m_speed)); + stream.write(reinterpret_cast (&m_clock_divider), sizeof(m_clock_divider)); + stream.write(reinterpret_cast (m_color_table), sizeof(u16) * 512); + stream.write(reinterpret_cast (&m_hpos), sizeof(m_hpos)); + stream.write(reinterpret_cast (&m_vpos), sizeof(m_vpos)); + stream.write(reinterpret_cast (&m_pixel_index), sizeof(m_pixel_index)); + stream.write(reinterpret_cast (&m_pixel_clock), sizeof(m_pixel_clock)); + stream.write(reinterpret_cast (&m_pixel_x), sizeof(m_pixel_x)); + stream.write(reinterpret_cast (&m_hsync), sizeof(m_hsync)); + stream.write(reinterpret_cast (&m_vsync), sizeof(m_vsync)); + stream.write(reinterpret_cast (&m_blur), sizeof(m_blur)); + stream.write(reinterpret_cast (&m_black_and_white), sizeof(m_black_and_white)); +} + +void HuC6260::LoadState(std::istream& stream) +{ + using namespace std; + stream.read(reinterpret_cast (&m_control_register), sizeof(m_control_register)); + stream.read(reinterpret_cast (&m_color_table_address), sizeof(m_color_table_address)); + stream.read(reinterpret_cast (&m_speed), sizeof(m_speed)); + stream.read(reinterpret_cast (&m_clock_divider), sizeof(m_clock_divider)); + stream.read(reinterpret_cast (m_color_table), sizeof(u16) * 512); + stream.read(reinterpret_cast (&m_hpos), sizeof(m_hpos)); + stream.read(reinterpret_cast (&m_vpos), sizeof(m_vpos)); + stream.read(reinterpret_cast (&m_pixel_index), sizeof(m_pixel_index)); + stream.read(reinterpret_cast (&m_pixel_clock), sizeof(m_pixel_clock)); + stream.read(reinterpret_cast (&m_pixel_x), sizeof(m_pixel_x)); + stream.read(reinterpret_cast (&m_hsync), sizeof(m_hsync)); + stream.read(reinterpret_cast (&m_vsync), sizeof(m_vsync)); + stream.read(reinterpret_cast (&m_blur), sizeof(m_blur)); + stream.read(reinterpret_cast (&m_black_and_white), sizeof(m_black_and_white)); } \ No newline at end of file diff --git a/src/huc6260.h b/src/huc6260.h index b9f8658..fb2ebee 100644 --- a/src/huc6260.h +++ b/src/huc6260.h @@ -20,6 +20,8 @@ #ifndef HUC6260_H #define HUC6260_H +#include +#include #include "common.h" #define HUC6260_LINE_LENGTH 1365 @@ -40,9 +42,9 @@ class HuC6260 { u8* CR; u16* CTA; - int* HPOS; - int* VPOS; - int* PIXEL_INDEX; + s32* HPOS; + s32* VPOS; + s32* PIXEL_INDEX; bool* HSYNC; bool* VSYNC; }; @@ -68,11 +70,15 @@ class HuC6260 int GetClockDivider(); u16* GetColorTable(); void SetBuffer(u8* frame_buffer); + u8* GetBuffer(); int GetCurrentLineWidth(); int GetCurrentHeight(); void SetScanlineStart(int scanline_start); void SetScanlineEnd(int scanline_end); void SetOverscan(bool overscan); + GG_Pixel_Format GetPixelFormat(); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); private: void WritePixel(u16 pixel); @@ -83,19 +89,19 @@ class HuC6260 HuC6260_State m_state; u8 m_control_register; u16 m_color_table_address; - int m_speed; - int m_clock_divider; + s32 m_speed; + s32 m_clock_divider; u16* m_color_table; u8* m_frame_buffer; - int m_hpos; - int m_vpos; - int m_pixel_index; - int m_pixel_clock; - int m_pixel_x; + s32 m_hpos; + s32 m_vpos; + s32 m_pixel_index; + s32 m_pixel_clock; + s32 m_pixel_x; bool m_hsync; bool m_vsync; - int m_blur; - bool m_black_and_white; + s32 m_blur; + u32 m_black_and_white; int m_overscan; int m_scanline_start; int m_scanline_end; diff --git a/src/huc6260_inline.h b/src/huc6260_inline.h index 5fa34de..e0e7d80 100644 --- a/src/huc6260_inline.h +++ b/src/huc6260_inline.h @@ -64,7 +64,7 @@ inline void HuC6260::WriteRegister(u16 address, u8 value) m_control_register = value; m_speed = m_control_register & 0x03; m_blur = (m_control_register >> 2) & 0x01; - m_black_and_white = IsSetBit(m_control_register, 7); + m_black_and_white = (m_control_register >> 7) & 0x01; switch (m_speed) { diff --git a/src/huc6270.cpp b/src/huc6270.cpp index 083dfb3..37e9b2e 100644 --- a/src/huc6270.cpp +++ b/src/huc6270.cpp @@ -88,7 +88,6 @@ void HuC6270::Reset() m_no_sprite_limit = false; m_sprite_count = 0; - for (int i = 0; i < HUC6270_VRAM_SIZE; i++) { m_vram[i] = 0; @@ -523,3 +522,105 @@ void HuC6270::FetchSprites() } } } + +void HuC6270::SaveState(std::ostream& stream) +{ + using namespace std; + stream.write(reinterpret_cast (m_vram), sizeof(u16) * HUC6270_VRAM_SIZE); + stream.write(reinterpret_cast (&m_address_register), sizeof(m_address_register)); + stream.write(reinterpret_cast (&m_status_register), sizeof(m_status_register)); + stream.write(reinterpret_cast (m_register), sizeof(m_register)); + stream.write(reinterpret_cast (m_sat), sizeof(u16) * HUC6270_SAT_SIZE); + stream.write(reinterpret_cast (&m_read_buffer), sizeof(m_read_buffer)); + stream.write(reinterpret_cast (&m_trigger_sat_transfer), sizeof(m_trigger_sat_transfer)); + stream.write(reinterpret_cast (&m_auto_sat_transfer), sizeof(m_auto_sat_transfer)); + stream.write(reinterpret_cast (&m_sat_transfer_pending), sizeof(m_sat_transfer_pending)); + stream.write(reinterpret_cast (&m_hpos), sizeof(m_hpos)); + stream.write(reinterpret_cast (&m_vpos), sizeof(m_vpos)); + stream.write(reinterpret_cast (&m_bg_offset_y), sizeof(m_bg_offset_y)); + stream.write(reinterpret_cast (&m_bg_counter_y), sizeof(m_bg_counter_y)); + stream.write(reinterpret_cast (&m_increment_bg_counter_y), sizeof(m_increment_bg_counter_y)); + stream.write(reinterpret_cast (&m_raster_line), sizeof(m_raster_line)); + stream.write(reinterpret_cast (&m_latched_bxr), sizeof(m_latched_bxr)); + stream.write(reinterpret_cast (&m_latched_hds), sizeof(m_latched_hds)); + stream.write(reinterpret_cast (&m_latched_hdw), sizeof(m_latched_hdw)); + stream.write(reinterpret_cast (&m_latched_hde), sizeof(m_latched_hde)); + stream.write(reinterpret_cast (&m_latched_hsw), sizeof(m_latched_hsw)); + stream.write(reinterpret_cast (&m_latched_vds), sizeof(m_latched_vds)); + stream.write(reinterpret_cast (&m_latched_vdw), sizeof(m_latched_vdw)); + stream.write(reinterpret_cast (&m_latched_vcr), sizeof(m_latched_vcr)); + stream.write(reinterpret_cast (&m_latched_vsw), sizeof(m_latched_vsw)); + stream.write(reinterpret_cast (&m_latched_mwr), sizeof(m_latched_mwr)); + stream.write(reinterpret_cast (&m_latched_cr), sizeof(m_latched_cr)); + stream.write(reinterpret_cast (&m_v_state), sizeof(m_v_state)); + stream.write(reinterpret_cast (&m_h_state), sizeof(m_h_state)); + stream.write(reinterpret_cast (&m_lines_to_next_v_state), sizeof(m_lines_to_next_v_state)); + stream.write(reinterpret_cast (&m_clocks_to_next_h_state), sizeof(m_clocks_to_next_h_state)); + stream.write(reinterpret_cast (&m_vblank_triggered), sizeof(m_vblank_triggered)); + stream.write(reinterpret_cast (&m_active_line), sizeof(m_active_line)); + stream.write(reinterpret_cast (m_line_buffer), sizeof(u16) * 1024); + stream.write(reinterpret_cast (m_line_buffer_sprites), sizeof(u16) * 1024); + stream.write(reinterpret_cast (&m_line_buffer_index), sizeof(m_line_buffer_index)); + stream.write(reinterpret_cast (&m_no_sprite_limit), sizeof(m_no_sprite_limit)); + stream.write(reinterpret_cast (&m_sprite_count), sizeof(m_sprite_count)); + + for (int i = 0; i < 128; i++) + { + stream.write(reinterpret_cast (&m_sprites[i].index), sizeof(m_sprites[i].index)); + stream.write(reinterpret_cast (&m_sprites[i].x), sizeof(m_sprites[i].x)); + stream.write(reinterpret_cast (&m_sprites[i].flags), sizeof(m_sprites[i].flags)); + stream.write(reinterpret_cast (&m_sprites[i].palette), sizeof(m_sprites[i].palette)); + stream.write(reinterpret_cast (m_sprites[i].data), sizeof(m_sprites[i].data)); + } +} + +void HuC6270::LoadState(std::istream& stream) +{ + using namespace std; + stream.read(reinterpret_cast (m_vram), sizeof(u16) * HUC6270_VRAM_SIZE); + stream.read(reinterpret_cast (&m_address_register), sizeof(m_address_register)); + stream.read(reinterpret_cast (&m_status_register), sizeof(m_status_register)); + stream.read(reinterpret_cast (m_register), sizeof(m_register)); + stream.read(reinterpret_cast (m_sat), sizeof(u16) * HUC6270_SAT_SIZE); + stream.read(reinterpret_cast (&m_read_buffer), sizeof(m_read_buffer)); + stream.read(reinterpret_cast (&m_trigger_sat_transfer), sizeof(m_trigger_sat_transfer)); + stream.read(reinterpret_cast (&m_auto_sat_transfer), sizeof(m_auto_sat_transfer)); + stream.read(reinterpret_cast (&m_sat_transfer_pending), sizeof(m_sat_transfer_pending)); + stream.read(reinterpret_cast (&m_hpos), sizeof(m_hpos)); + stream.read(reinterpret_cast (&m_vpos), sizeof(m_vpos)); + stream.read(reinterpret_cast (&m_bg_offset_y), sizeof(m_bg_offset_y)); + stream.read(reinterpret_cast (&m_bg_counter_y), sizeof(m_bg_counter_y)); + stream.read(reinterpret_cast (&m_increment_bg_counter_y), sizeof(m_increment_bg_counter_y)); + stream.read(reinterpret_cast (&m_raster_line), sizeof(m_raster_line)); + stream.read(reinterpret_cast (&m_latched_bxr), sizeof(m_latched_bxr)); + stream.read(reinterpret_cast (&m_latched_hds), sizeof(m_latched_hds)); + stream.read(reinterpret_cast (&m_latched_hdw), sizeof(m_latched_hdw)); + stream.read(reinterpret_cast (&m_latched_hde), sizeof(m_latched_hde)); + stream.read(reinterpret_cast (&m_latched_hsw), sizeof(m_latched_hsw)); + stream.read(reinterpret_cast (&m_latched_vds), sizeof(m_latched_vds)); + stream.read(reinterpret_cast (&m_latched_vdw), sizeof(m_latched_vdw)); + stream.read(reinterpret_cast (&m_latched_vcr), sizeof(m_latched_vcr)); + stream.read(reinterpret_cast (&m_latched_vsw), sizeof(m_latched_vsw)); + stream.read(reinterpret_cast (&m_latched_mwr), sizeof(m_latched_mwr)); + stream.read(reinterpret_cast (&m_latched_cr), sizeof(m_latched_cr)); + stream.read(reinterpret_cast (&m_v_state), sizeof(m_v_state)); + stream.read(reinterpret_cast (&m_h_state), sizeof(m_h_state)); + stream.read(reinterpret_cast (&m_lines_to_next_v_state), sizeof(m_lines_to_next_v_state)); + stream.read(reinterpret_cast (&m_clocks_to_next_h_state), sizeof(m_clocks_to_next_h_state)); + stream.read(reinterpret_cast (&m_vblank_triggered), sizeof(m_vblank_triggered)); + stream.read(reinterpret_cast (&m_active_line), sizeof(m_active_line)); + stream.read(reinterpret_cast (m_line_buffer), sizeof(u16) * 1024); + stream.read(reinterpret_cast (m_line_buffer_sprites), sizeof(u16) * 1024); + stream.read(reinterpret_cast (&m_line_buffer_index), sizeof(m_line_buffer_index)); + stream.read(reinterpret_cast (&m_no_sprite_limit), sizeof(m_no_sprite_limit)); + stream.read(reinterpret_cast (&m_sprite_count), sizeof(m_sprite_count)); + + for (int i = 0; i < 128; i++) + { + stream.read(reinterpret_cast (&m_sprites[i].index), sizeof(m_sprites[i].index)); + stream.read(reinterpret_cast (&m_sprites[i].x), sizeof(m_sprites[i].x)); + stream.read(reinterpret_cast (&m_sprites[i].flags), sizeof(m_sprites[i].flags)); + stream.read(reinterpret_cast (&m_sprites[i].palette), sizeof(m_sprites[i].palette)); + stream.read(reinterpret_cast (m_sprites[i].data), sizeof(m_sprites[i].data)); + } +} diff --git a/src/huc6270.h b/src/huc6270.h index 22b3e7a..60db0d7 100644 --- a/src/huc6270.h +++ b/src/huc6270.h @@ -20,6 +20,8 @@ #ifndef HUC6270_H #define HUC6270_H +#include +#include #include "huc6270_defines.h" #include "common.h" @@ -55,10 +57,10 @@ class HuC6270 u16* SR; u16* R; u16* READ_BUFFER; - int* HPOS; - int* VPOS; - int* V_STATE; - int* H_STATE; + s32* HPOS; + s32* VPOS; + s32* V_STATE; + s32* H_STATE; }; public: @@ -75,12 +77,14 @@ class HuC6270 u16* GetVRAM(); u16* GetSAT(); void SetNoSpriteLimit(bool no_sprite_limit); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); private: struct HuC6270_Sprite_Data { - int index; + s32 index; u16 x; u16 flags; u8 palette; @@ -98,13 +102,13 @@ class HuC6270 u16 m_read_buffer; bool m_trigger_sat_transfer; bool m_auto_sat_transfer; - int m_sat_transfer_pending; - int m_hpos; - int m_vpos; - int m_bg_offset_y; - int m_bg_counter_y; + s32 m_sat_transfer_pending; + s32 m_hpos; + s32 m_vpos; + s32 m_bg_offset_y; + s32 m_bg_counter_y; bool m_increment_bg_counter_y; - int m_raster_line; + s32 m_raster_line; u16 m_latched_bxr; u16 m_latched_hds; u16 m_latched_hdw; @@ -116,17 +120,17 @@ class HuC6270 u16 m_latched_vsw; u16 m_latched_mwr; u16 m_latched_cr; - int m_v_state; - int m_h_state; - int m_lines_to_next_v_state; - int m_clocks_to_next_h_state; + s32 m_v_state; + s32 m_h_state; + s32 m_lines_to_next_v_state; + s32 m_clocks_to_next_h_state; bool m_vblank_triggered; bool m_active_line; u16 m_line_buffer[1024]; u16 m_line_buffer_sprites[1024]; - int m_line_buffer_index; + s32 m_line_buffer_index; bool m_no_sprite_limit; - int m_sprite_count; + s32 m_sprite_count; HuC6270_Sprite_Data m_sprites[128]; private: diff --git a/src/huc6280.cpp b/src/huc6280.cpp index a89e397..1104bfc 100644 --- a/src/huc6280.cpp +++ b/src/huc6280.cpp @@ -534,10 +534,10 @@ void HuC6280::PushCallStack(u16 src, u16 dest, u16 back) entry.back = back; if (m_disassembler_call_stack.size() < 256) m_disassembler_call_stack.push(entry); - else - { - //Debug("** HuC6280 --> Disassembler Call Stack Overflow"); - } + // else + // { + // //Debug("** HuC6280 --> Disassembler Call Stack Overflow"); + // } #endif } @@ -560,4 +560,56 @@ void HuC6280::CreateZNFlagsTable() if (i & 0x80) m_zn_flags_lut[i] |= FLAG_NEGATIVE; } +} + +void HuC6280::SaveState(std::ostream& stream) +{ + m_PC.SaveState(stream); + m_A.SaveState(stream); + m_X.SaveState(stream); + m_Y.SaveState(stream); + m_S.SaveState(stream); + m_P.SaveState(stream); + + stream.write(reinterpret_cast (&m_cycles), sizeof(m_cycles)); + stream.write(reinterpret_cast (&m_clock), sizeof(m_clock)); + stream.write(reinterpret_cast (&m_clock_cycles), sizeof(m_clock_cycles)); + stream.write(reinterpret_cast (&m_last_instruction_cycles), sizeof(m_last_instruction_cycles)); + stream.write(reinterpret_cast (&m_irq_pending), sizeof(m_irq_pending)); + stream.write(reinterpret_cast (&m_speed), sizeof(m_speed)); + stream.write(reinterpret_cast (&m_transfer), sizeof(m_transfer)); + stream.write(reinterpret_cast (&m_timer_enabled), sizeof(m_timer_enabled)); + stream.write(reinterpret_cast (&m_timer_cycles), sizeof(m_timer_cycles)); + stream.write(reinterpret_cast (&m_timer_counter), sizeof(m_timer_counter)); + stream.write(reinterpret_cast (&m_timer_reload), sizeof(m_timer_reload)); + stream.write(reinterpret_cast (&m_interrupt_disable_register), sizeof(m_interrupt_disable_register)); + stream.write(reinterpret_cast (&m_interrupt_request_register), sizeof(m_interrupt_request_register)); + stream.write(reinterpret_cast (&m_skip_flag_transfer_clear), sizeof(m_skip_flag_transfer_clear)); + stream.write(reinterpret_cast (&m_debug_next_irq), sizeof(m_debug_next_irq)); +} + +void HuC6280::LoadState(std::istream& stream) +{ + m_PC.LoadState(stream); + m_A.LoadState(stream); + m_X.LoadState(stream); + m_Y.LoadState(stream); + m_S.LoadState(stream); + m_P.LoadState(stream); + + stream.read(reinterpret_cast (&m_cycles), sizeof(m_cycles)); + stream.read(reinterpret_cast (&m_clock), sizeof(m_clock)); + stream.read(reinterpret_cast (&m_clock_cycles), sizeof(m_clock_cycles)); + stream.read(reinterpret_cast (&m_last_instruction_cycles), sizeof(m_last_instruction_cycles)); + stream.read(reinterpret_cast (&m_irq_pending), sizeof(m_irq_pending)); + stream.read(reinterpret_cast (&m_speed), sizeof(m_speed)); + stream.read(reinterpret_cast (&m_transfer), sizeof(m_transfer)); + stream.read(reinterpret_cast (&m_timer_enabled), sizeof(m_timer_enabled)); + stream.read(reinterpret_cast (&m_timer_cycles), sizeof(m_timer_cycles)); + stream.read(reinterpret_cast (&m_timer_counter), sizeof(m_timer_counter)); + stream.read(reinterpret_cast (&m_timer_reload), sizeof(m_timer_reload)); + stream.read(reinterpret_cast (&m_interrupt_disable_register), sizeof(m_interrupt_disable_register)); + stream.read(reinterpret_cast (&m_interrupt_request_register), sizeof(m_interrupt_request_register)); + stream.read(reinterpret_cast (&m_skip_flag_transfer_clear), sizeof(m_skip_flag_transfer_clear)); + stream.read(reinterpret_cast (&m_debug_next_irq), sizeof(m_debug_next_irq)); } \ No newline at end of file diff --git a/src/huc6280.h b/src/huc6280.h index bfc95ec..b89258f 100644 --- a/src/huc6280.h +++ b/src/huc6280.h @@ -22,6 +22,8 @@ #include #include +#include +#include #include "common.h" #include "huc6280_registers.h" @@ -56,13 +58,13 @@ class HuC6280 EightBitRegister* Y; EightBitRegister* S; EightBitRegister* P; - int* SPEED; + s32* SPEED; bool* TIMER; u8* TIMER_COUNTER; u8* TIMER_RELOAD; u8* IDR; u8* IRR; - unsigned int* CYCLES; + u32* CYCLES; }; enum GG_Breakpoint_Type @@ -122,6 +124,8 @@ class HuC6280 void ClearDisassemblerCallStack(); std::stack* GetDisassemblerCallStack(); void CheckMemoryBreakpoints(int type, u16 address, bool read); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); private: typedef void (HuC6280::*opcodeptr) (void); @@ -133,24 +137,24 @@ class HuC6280 EightBitRegister m_S; EightBitRegister m_P; u8 m_zn_flags_lut[256]; - unsigned int m_cycles; - unsigned int m_clock; - int m_clock_cycles; - unsigned int m_last_instruction_cycles; - int m_irq_pending; - int m_speed; + u32 m_cycles; + u32 m_clock; + s32 m_clock_cycles; + u32 m_last_instruction_cycles; + s32 m_irq_pending; + s32 m_speed; bool m_transfer; Memory* m_memory; HuC6270* m_huc6270; HuC6280_State m_processor_state; bool m_timer_enabled; - unsigned int m_timer_cycles; + u32 m_timer_cycles; u8 m_timer_counter; u8 m_timer_reload; u8 m_interrupt_disable_register; u8 m_interrupt_request_register; bool m_skip_flag_transfer_clear; - int m_debug_next_irq; + s32 m_debug_next_irq; bool m_breakpoints_enabled; bool m_breakpoints_irq_enabled; bool m_cpu_breakpoint_hit; @@ -162,8 +166,8 @@ class HuC6280 std::stack m_disassembler_call_stack; private: - unsigned int TickOPCode(); - unsigned int TickIRQ(); + u32 TickOPCode(); + u32 TickIRQ(); void CheckIRQs(); void ClockTimer(); diff --git a/src/huc6280_inline.h b/src/huc6280_inline.h index 12c8be7..b4c45c1 100644 --- a/src/huc6280_inline.h +++ b/src/huc6280_inline.h @@ -57,7 +57,7 @@ inline bool HuC6280::Clock() return instruction_completed; } -inline unsigned int HuC6280::TickOPCode() +inline u32 HuC6280::TickOPCode() { m_transfer = false; m_memory_breakpoint_hit = false; @@ -83,7 +83,7 @@ inline unsigned int HuC6280::TickOPCode() return m_cycles; } -inline unsigned int HuC6280::TickIRQ() +inline u32 HuC6280::TickIRQ() { assert(m_irq_pending != 0); diff --git a/src/huc6280_psg.cpp b/src/huc6280_psg.cpp index 156918e..dff5e42 100644 --- a/src/huc6280_psg.cpp +++ b/src/huc6280_psg.cpp @@ -57,7 +57,6 @@ void HuC6280PSG::Reset() { m_elapsed_cycles = 0; m_buffer_index = 0; - m_cycles_per_sample = GG_AUDIO_CYCLES_PER_SAMPLE; m_sample_cycle_counter = 0; m_frame_samples = 0; @@ -76,7 +75,6 @@ void HuC6280PSG::Reset() m_channels[i].wave = 0; m_channels[i].wave_index = 0; m_channels[i].noise_control = 0; - m_channels[i].noise_frequency = 0; m_channels[i].noise_seed = 1; m_channels[i].noise_counter = 0; m_channels[i].counter = 0; @@ -147,13 +145,13 @@ void HuC6280PSG::Sync() u8 right_vol = ch->amplitude & 0x0F; u8 channel_vol = (ch->control >> 1) & 0x0F; - int temp_left_vol = std::min(0x0F, (0x0F - main_left_vol) + (0x0F - left_vol) + (0x0F - channel_vol)); - int temp_right_vol = std::min(0x0F, (0x0F - main_right_vol) + (0x0F - right_vol) + (0x0F - channel_vol)); + u8 temp_left_vol = std::min(0x0F, (0x0F - main_left_vol) + (0x0F - left_vol) + (0x0F - channel_vol)); + u8 temp_right_vol = std::min(0x0F, (0x0F - main_right_vol) + (0x0F - right_vol) + (0x0F - channel_vol)); - int final_left_vol = m_volume_lut[(temp_left_vol << 1) | (~ch->control & 0x01)]; - int final_right_vol = m_volume_lut[(temp_right_vol << 1) | (~ch->control & 0x01)]; + u16 final_left_vol = m_volume_lut[(temp_left_vol << 1) | (~ch->control & 0x01)]; + u16 final_right_vol = m_volume_lut[(temp_right_vol << 1) | (~ch->control & 0x01)]; - int data = 0; + s8 data = 0; // Noise if ((i >=4) && (ch->noise_control & 0x80)) @@ -165,7 +163,7 @@ void HuC6280PSG::Sync() if (ch->noise_counter <= 0) { ch->noise_counter = freq << 6; - const u32 seed = ch->noise_seed; + u32 seed = ch->noise_seed; ch->noise_seed = (seed >> 1) | ((IsSetBit(seed, 0) ^ IsSetBit(seed, 1) ^ IsSetBit(seed, 11) ^ IsSetBit(seed, 12) ^ IsSetBit(seed, 17)) << 17); } } @@ -220,7 +218,7 @@ void HuC6280PSG::Sync() // No LFO else { - int freq = ch->frequency ? ch->frequency : 0x1000; + u16 freq = ch->frequency ? ch->frequency : 0x1000; if (freq > 7) data = ch->wave_data[ch->wave_index]; @@ -243,9 +241,9 @@ void HuC6280PSG::Sync() m_sample_cycle_counter++; - if (m_sample_cycle_counter >= m_cycles_per_sample) + if (m_sample_cycle_counter >= GG_AUDIO_CYCLES_PER_SAMPLE) { - m_sample_cycle_counter -= m_cycles_per_sample; + m_sample_cycle_counter -= GG_AUDIO_CYCLES_PER_SAMPLE; for (int i = 0; i < 6; i++) { @@ -279,4 +277,64 @@ void HuC6280PSG::ComputeVolumeLUT() m_volume_lut[30] = 0; m_volume_lut[31] = 0; +} + +void HuC6280PSG::SaveState(std::ostream& stream) +{ + stream.write(reinterpret_cast (&m_channel_select), sizeof(m_channel_select)); + stream.write(reinterpret_cast (&m_main_amplitude), sizeof(m_main_amplitude)); + stream.write(reinterpret_cast (&m_lfo_frequency), sizeof(m_lfo_frequency)); + stream.write(reinterpret_cast (&m_lfo_control), sizeof(m_lfo_control)); + stream.write(reinterpret_cast (&m_elapsed_cycles), sizeof(m_elapsed_cycles)); + stream.write(reinterpret_cast (&m_sample_cycle_counter), sizeof(m_sample_cycle_counter)); + stream.write(reinterpret_cast (&m_frame_samples), sizeof(m_frame_samples)); + stream.write(reinterpret_cast (&m_buffer_index), sizeof(m_buffer_index)); + + for (int i = 0; i < 6; i++) + { + stream.write(reinterpret_cast (&m_channels[i].frequency), sizeof(m_channels[i].frequency)); + stream.write(reinterpret_cast (&m_channels[i].control), sizeof(m_channels[i].control)); + stream.write(reinterpret_cast (&m_channels[i].amplitude), sizeof(m_channels[i].amplitude)); + stream.write(reinterpret_cast (&m_channels[i].wave), sizeof(m_channels[i].wave)); + stream.write(reinterpret_cast (&m_channels[i].wave_index), sizeof(m_channels[i].wave_index)); + stream.write(reinterpret_cast (m_channels[i].wave_data), sizeof(m_channels[i].wave_data)); + stream.write(reinterpret_cast (&m_channels[i].noise_control), sizeof(m_channels[i].noise_control)); + stream.write(reinterpret_cast (&m_channels[i].noise_seed), sizeof(m_channels[i].noise_seed)); + stream.write(reinterpret_cast (&m_channels[i].noise_counter), sizeof(m_channels[i].noise_counter)); + stream.write(reinterpret_cast (&m_channels[i].counter), sizeof(m_channels[i].counter)); + stream.write(reinterpret_cast (&m_channels[i].dda), sizeof(m_channels[i].dda)); + stream.write(reinterpret_cast (m_channels[i].output), sizeof(m_channels[i].output)); + stream.write(reinterpret_cast (&m_channels[i].left_sample), sizeof(m_channels[i].left_sample)); + stream.write(reinterpret_cast (&m_channels[i].right_sample), sizeof(m_channels[i].right_sample)); + } +} + +void HuC6280PSG::LoadState(std::istream& stream) +{ + stream.read(reinterpret_cast (&m_channel_select), sizeof(m_channel_select)); + stream.read(reinterpret_cast (&m_main_amplitude), sizeof(m_main_amplitude)); + stream.read(reinterpret_cast (&m_lfo_frequency), sizeof(m_lfo_frequency)); + stream.read(reinterpret_cast (&m_lfo_control), sizeof(m_lfo_control)); + stream.read(reinterpret_cast (&m_elapsed_cycles), sizeof(m_elapsed_cycles)); + stream.read(reinterpret_cast (&m_sample_cycle_counter), sizeof(m_sample_cycle_counter)); + stream.read(reinterpret_cast (&m_frame_samples), sizeof(m_frame_samples)); + stream.read(reinterpret_cast (&m_buffer_index), sizeof(m_buffer_index)); + + for (int i = 0; i < 6; i++) + { + stream.read(reinterpret_cast (&m_channels[i].frequency), sizeof(m_channels[i].frequency)); + stream.read(reinterpret_cast (&m_channels[i].control), sizeof(m_channels[i].control)); + stream.read(reinterpret_cast (&m_channels[i].amplitude), sizeof(m_channels[i].amplitude)); + stream.read(reinterpret_cast (&m_channels[i].wave), sizeof(m_channels[i].wave)); + stream.read(reinterpret_cast (&m_channels[i].wave_index), sizeof(m_channels[i].wave_index)); + stream.read(reinterpret_cast (m_channels[i].wave_data), sizeof(m_channels[i].wave_data)); + stream.read(reinterpret_cast (&m_channels[i].noise_control), sizeof(m_channels[i].noise_control)); + stream.read(reinterpret_cast (&m_channels[i].noise_seed), sizeof(m_channels[i].noise_seed)); + stream.read(reinterpret_cast (&m_channels[i].noise_counter), sizeof(m_channels[i].noise_counter)); + stream.read(reinterpret_cast (&m_channels[i].counter), sizeof(m_channels[i].counter)); + stream.read(reinterpret_cast (&m_channels[i].dda), sizeof(m_channels[i].dda)); + stream.read(reinterpret_cast (m_channels[i].output), sizeof(m_channels[i].output)); + stream.read(reinterpret_cast (&m_channels[i].left_sample), sizeof(m_channels[i].left_sample)); + stream.read(reinterpret_cast (&m_channels[i].right_sample), sizeof(m_channels[i].right_sample)); + } } \ No newline at end of file diff --git a/src/huc6280_psg.h b/src/huc6280_psg.h index ef4a760..6d74e59 100644 --- a/src/huc6280_psg.h +++ b/src/huc6280_psg.h @@ -20,12 +20,15 @@ #ifndef HUC6280_PSG_H #define HUC6280_PSG_H +#include +#include #include "common.h" +#define GG_MASTER_CLOCK_RATE 21477273 #define GG_AUDIO_SAMPLE_RATE 44100 #define GG_AUDIO_BUFFER_SIZE 2048 #define GG_AUDIO_BUFFER_COUNT 3 -#define GG_AUDIO_CLOCK_RATE (GG_CLOCK_RATE / 6) +#define GG_AUDIO_CLOCK_RATE (GG_MASTER_CLOCK_RATE / 6) #define GG_AUDIO_CYCLES_PER_SAMPLE (GG_AUDIO_CLOCK_RATE / GG_AUDIO_SAMPLE_RATE) class HuC6280PSG @@ -40,10 +43,9 @@ class HuC6280PSG u8 wave_index; u8 wave_data[32]; u8 noise_control; - u32 noise_frequency; u32 noise_seed; - int noise_counter; - int counter; + s32 noise_counter; + s32 counter; s16 dda; s16 output[GG_AUDIO_BUFFER_SIZE]; s16 left_sample; @@ -58,8 +60,8 @@ class HuC6280PSG u8* MAIN_AMPLITUDE; u8* LFO_FREQUENCY; u8* LFO_CONTROL; - int* BUFFER_INDEX; - int* FRAME_SAMPLES; + s32* BUFFER_INDEX; + s32* FRAME_SAMPLES; }; public: @@ -71,6 +73,8 @@ class HuC6280PSG void Write(u16 address, u8 value); int EndFrame(s16* sample_buffer); HuC6280PSG_State* GetState(); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); private: void Sync(); @@ -84,11 +88,10 @@ class HuC6280PSG u8 m_main_amplitude; u8 m_lfo_frequency; u8 m_lfo_control; - int m_elapsed_cycles; - int m_sample_cycle_counter; - int m_frame_samples; - int m_cycles_per_sample; - int m_buffer_index; + s32 m_elapsed_cycles; + s32 m_sample_cycle_counter; + s32 m_frame_samples; + s32 m_buffer_index; u16 m_volume_lut[32]; }; diff --git a/src/huc6280_registers.h b/src/huc6280_registers.h index ee4acae..d5ecb2c 100644 --- a/src/huc6280_registers.h +++ b/src/huc6280_registers.h @@ -32,6 +32,8 @@ class EightBitRegister void Increment(u8 value); void Decrement(); void Decrement(u8 value); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); private: u8 m_value; @@ -67,6 +69,16 @@ inline void EightBitRegister::Decrement(u8 value) m_value -= value; } +inline void EightBitRegister::SaveState(std::ostream& stream) +{ + stream.write(reinterpret_cast (&m_value), sizeof(m_value)); +} + +inline void EightBitRegister::LoadState(std::istream& stream) +{ + stream.read(reinterpret_cast (&m_value), sizeof(m_value)); +} + ////////////////////////////////////////////////////////////////////////// class SixteenBitRegister @@ -83,6 +95,8 @@ class SixteenBitRegister void Increment(u16 value); void Decrement(); void Decrement(u16 value); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); private: u16 m_value; @@ -138,4 +152,14 @@ inline void SixteenBitRegister::Decrement(u16 value) m_value -= value; } +inline void SixteenBitRegister::SaveState(std::ostream& stream) +{ + stream.write(reinterpret_cast (&m_value), sizeof(m_value)); +} + +inline void SixteenBitRegister::LoadState(std::istream& stream) +{ + stream.read(reinterpret_cast (&m_value), sizeof(m_value)); +} + #endif /* HUC6280_REGISTERS_H */ \ No newline at end of file diff --git a/src/input.cpp b/src/input.cpp index 42e4cec..2a7295c 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -58,10 +58,24 @@ void Input::KeyReleased(GG_Controllers controller, GG_Keys key) m_joypads[controller] |= key; } -// void Input::SaveState(std::ostream& stream) -// { -// } +void Input::SaveState(std::ostream& stream) +{ + using namespace std; + stream.write(reinterpret_cast (&m_clr), sizeof(m_clr)); + stream.write(reinterpret_cast (&m_sel), sizeof(m_sel)); + stream.write(reinterpret_cast (m_joypads), sizeof(m_joypads)); + stream.write(reinterpret_cast (&m_register), sizeof(m_register)); + stream.write(reinterpret_cast (&m_pce_jap), sizeof(m_pce_jap)); + stream.write(reinterpret_cast (&m_cdrom), sizeof(m_cdrom)); +} -// void Input::LoadState(std::istream& stream) -// { -// } \ No newline at end of file +void Input::LoadState(std::istream& stream) +{ + using namespace std; + stream.read(reinterpret_cast (&m_clr), sizeof(m_clr)); + stream.read(reinterpret_cast (&m_sel), sizeof(m_sel)); + stream.read(reinterpret_cast (m_joypads), sizeof(m_joypads)); + stream.read(reinterpret_cast (&m_register), sizeof(m_register)); + stream.read(reinterpret_cast (&m_pce_jap), sizeof(m_pce_jap)); + stream.read(reinterpret_cast (&m_cdrom), sizeof(m_cdrom)); +} \ No newline at end of file diff --git a/src/input.h b/src/input.h index f468e3e..ca2cf46 100644 --- a/src/input.h +++ b/src/input.h @@ -20,6 +20,8 @@ #ifndef INPUT_H #define INPUT_H +#include +#include #include "common.h" class Input @@ -35,8 +37,8 @@ class Input u8 GetIORegister(); bool GetSel(); bool GetClr(); - // void SaveState(std::ostream& stream); - // void LoadState(std::istream& stream); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); private: bool m_clr; diff --git a/src/mapper.cpp b/src/mapper.cpp index 692264a..5ef0b6c 100644 --- a/src/mapper.cpp +++ b/src/mapper.cpp @@ -18,6 +18,7 @@ */ #include "mapper.h" +#include "log.h" Mapper::Mapper(Cartridge* cartridge) { @@ -27,3 +28,13 @@ Mapper::Mapper(Cartridge* cartridge) Mapper::~Mapper() { } + +void Mapper::SaveState(std::ostream&) +{ + Debug("Mapper::SaveState not implemented"); +} + +void Mapper::LoadState(std::istream&) +{ + Debug("Mapper::LoadState not implemented"); +} diff --git a/src/mapper.h b/src/mapper.h index d933db8..16d53fd 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -20,6 +20,8 @@ #ifndef MAPPER_H #define MAPPER_H +#include +#include #include "types.h" class Cartridge; @@ -32,6 +34,8 @@ class Mapper virtual u8 Read(u8 bank, u16 address) = 0; virtual void Write(u8 bank, u16 address, u8 value) = 0; virtual void Reset() = 0; + virtual void SaveState(std::ostream& stream); + virtual void LoadState(std::istream& stream); protected: Cartridge* m_cartridge; diff --git a/src/memory.cpp b/src/memory.cpp index e9b09d3..33ea5b4 100644 --- a/src/memory.cpp +++ b/src/memory.cpp @@ -162,4 +162,26 @@ u8* Memory::GetWram() Memory::GG_Disassembler_Record** Memory::GetAllDisassemblerRecords() { return m_disassembler; +} + +void Memory::SaveState(std::ostream& stream) +{ + using namespace std; + stream.write(reinterpret_cast (m_mpr), sizeof(m_mpr)); + stream.write(reinterpret_cast (m_wram), sizeof(u8) * 0x2000); + stream.write(reinterpret_cast (&m_io_buffer), sizeof(m_io_buffer)); + stream.write(reinterpret_cast (&m_mpr_buffer), sizeof(m_mpr_buffer)); + if (IsValidPointer(m_current_mapper)) + m_current_mapper->SaveState(stream); +} + +void Memory::LoadState(std::istream& stream) +{ + using namespace std; + stream.read(reinterpret_cast (m_mpr), sizeof(m_mpr)); + stream.read(reinterpret_cast (m_wram), sizeof(u8) * 0x2000); + stream.read(reinterpret_cast (&m_io_buffer), sizeof(m_io_buffer)); + stream.read(reinterpret_cast (&m_mpr_buffer), sizeof(m_mpr_buffer)); + if (IsValidPointer(m_current_mapper)) + m_current_mapper->LoadState(stream); } \ No newline at end of file diff --git a/src/memory.h b/src/memory.h index 91d52dc..3b4a4f3 100644 --- a/src/memory.h +++ b/src/memory.h @@ -20,6 +20,8 @@ #ifndef MEMORY_H #define MEMORY_H +#include +#include #include "common.h" class Cartridge; @@ -68,6 +70,8 @@ class Memory void ResetDisassemblerRecords(); u8* GetWram(); GG_Disassembler_Record** GetAllDisassemblerRecords(); + void SaveState(std::ostream& stream); + void LoadState(std::istream& stream); private: HuC6260* m_huc6260; diff --git a/src/sf2_mapper.cpp b/src/sf2_mapper.cpp index ec39939..6e50ed7 100644 --- a/src/sf2_mapper.cpp +++ b/src/sf2_mapper.cpp @@ -62,4 +62,19 @@ void SF2Mapper::Reset() { m_bank = 0; m_bank_address = 0; +} + +void SF2Mapper::SaveState(std::ostream& stream) +{ + using namespace std; + stream.write(reinterpret_cast (&m_bank), sizeof(m_bank)); + stream.write(reinterpret_cast (&m_bank_address), sizeof(m_bank_address)); + +} + +void SF2Mapper::LoadState(std::istream& stream) +{ + using namespace std; + stream.read(reinterpret_cast (&m_bank), sizeof(m_bank)); + stream.read(reinterpret_cast (&m_bank_address), sizeof(m_bank_address)); } \ No newline at end of file diff --git a/src/sf2_mapper.h b/src/sf2_mapper.h index 8eaf66f..e3a84f5 100644 --- a/src/sf2_mapper.h +++ b/src/sf2_mapper.h @@ -33,6 +33,8 @@ class SF2Mapper : public Mapper virtual u8 Read(u8 bank, u16 address); virtual void Write(u8 bank, u16 address, u8 value); virtual void Reset(); + virtual void SaveState(std::ostream& stream); + virtual void LoadState(std::istream& stream); private: int m_bank; diff --git a/src/types.h b/src/types.h index dadb97a..ee89cc3 100644 --- a/src/types.h +++ b/src/types.h @@ -72,4 +72,25 @@ enum GG_Controllers GG_CONTROLLER_2 = 1 }; +struct GG_SaveState_Header +{ + u32 magic; + u32 version; + u32 size; + s64 timestamp; + char rom_name[128]; + u32 rom_crc; + u32 screenshot_size; + u16 screenshot_width; + u16 screenshot_height; +}; + +struct GG_SaveState_Screenshot +{ + u32 width; + u32 height; + u32 size; + u8* data; +}; + #endif /* TYPES_H */ \ No newline at end of file