From 9107e8ac4b792946e32c2e423d4115d1929f37aa Mon Sep 17 00:00:00 2001 From: ATEMVEGETA Date: Fri, 28 Aug 2026 10:29:13 +0300 Subject: [PATCH] Add virtual RTC persistence and emulator save states --- core/pokewalker/peripherals/bma150/bma150.cpp | 44 +++ core/pokewalker/peripherals/bma150/bma150.h | 4 + core/pokewalker/peripherals/m95512/m95512.cpp | 31 ++ core/pokewalker/peripherals/m95512/m95512.h | 4 + .../peripherals/ssd1854/ssd1854.cpp | 32 ++ core/pokewalker/peripherals/ssd1854/ssd1854.h | 7 +- core/pokewalker/pocketwalker.cpp | 305 ++++++++++++++++++ core/pokewalker/pocketwalker.h | 8 + core/soc/rtc/rtc.cpp | 246 +++++++++++--- core/soc/rtc/rtc.h | 18 ++ core/soc/sci3/sci3.cpp | 42 +++ core/soc/sci3/sci3.h | 6 +- core/soc/ssu/ssu.cpp | 13 +- core/soc/ssu/ssu.h | 4 + core/soc/timer/timerb1.cpp | 11 + core/soc/timer/timerb1.h | 4 + core/soc/timer/timerw.cpp | 11 + core/soc/timer/timerw.h | 5 + desktop/src/main.cpp | 5 + desktop/src/qt/application_args.h | 1 + desktop/src/qt/emulator/emulator_context.cpp | 7 + desktop/src/qt/emulator/emulator_context.h | 2 +- desktop/src/qt/window/qt_window_system.cpp | 53 ++- desktop/src/qt/window/qt_window_system.h | 2 + 24 files changed, 805 insertions(+), 60 deletions(-) diff --git a/core/pokewalker/peripherals/bma150/bma150.cpp b/core/pokewalker/peripherals/bma150/bma150.cpp index d499a53..7b1f0fd 100644 --- a/core/pokewalker/peripherals/bma150/bma150.cpp +++ b/core/pokewalker/peripherals/bma150/bma150.cpp @@ -63,3 +63,47 @@ void BMA150::Cycle(uint32_t cycles) OnOutputPin({BMA150_PIN_INT, true}); OnOutputPin({BMA150_PIN_INT, false}); } + +void BMA150::SaveEmulatorState(std::ostream& stream) +{ + for (uint16_t i = 0; i < 0x80; i++) + { + const uint8_t value = mem.Read8(i); + stream.write(reinterpret_cast(&value), sizeof(value)); + } + + const uint8_t state_value = static_cast(state); + stream.write(reinterpret_cast(&state_value), sizeof(state_value)); + stream.write(reinterpret_cast(&is_reading), sizeof(is_reading)); + stream.write(reinterpret_cast(®ister_index), sizeof(register_index)); + stream.write(reinterpret_cast(&cycle_count), sizeof(cycle_count)); + stream.write(reinterpret_cast(&offset), sizeof(offset)); +} + +bool BMA150::LoadEmulatorState(std::istream& stream) +{ + for (uint16_t i = 0; i < 0x80; i++) + { + uint8_t value = 0; + stream.read(reinterpret_cast(&value), sizeof(value)); + if (!stream) + return false; + mem.Write8(i, value); + } + + uint8_t state_value = 0; + stream.read(reinterpret_cast(&state_value), sizeof(state_value)); + stream.read(reinterpret_cast(&is_reading), sizeof(is_reading)); + stream.read(reinterpret_cast(®ister_index), sizeof(register_index)); + stream.read(reinterpret_cast(&cycle_count), sizeof(cycle_count)); + stream.read(reinterpret_cast(&offset), sizeof(offset)); + if (!stream) + return false; + + if (state_value > static_cast(BMA150State::MEMORY)) + state_value = static_cast(BMA150State::IDLE); + + state = static_cast(state_value); + register_index &= 0x7F; + return true; +} diff --git a/core/pokewalker/peripherals/bma150/bma150.h b/core/pokewalker/peripherals/bma150/bma150.h index 138018f..e2b065e 100644 --- a/core/pokewalker/peripherals/bma150/bma150.h +++ b/core/pokewalker/peripherals/bma150/bma150.h @@ -1,5 +1,7 @@ #pragma once +#include #include +#include #include "core/memory/memory.h" #include "core/soc/ssu/peripheral.h" @@ -35,6 +37,8 @@ class BMA150 : public Peripheral uint8_t Transmit() override; void Reset() override; void Cycle(uint32_t cycles) override; + void SaveEmulatorState(std::ostream& stream); + bool LoadEmulatorState(std::istream& stream); void SetSampleProvider(const std::shared_ptr& provider) { diff --git a/core/pokewalker/peripherals/m95512/m95512.cpp b/core/pokewalker/peripherals/m95512/m95512.cpp index 1308177..fe1a9fb 100644 --- a/core/pokewalker/peripherals/m95512/m95512.cpp +++ b/core/pokewalker/peripherals/m95512/m95512.cpp @@ -78,3 +78,34 @@ void M95512::Reset() state = M95512State::IDLE; offset = 0; } + +void M95512::SaveEmulatorState(std::ostream& stream) const +{ + const uint8_t state_value = static_cast(state); + stream.write(reinterpret_cast(&state_value), sizeof(state_value)); + stream.write(reinterpret_cast(&is_reading), sizeof(is_reading)); + stream.write(reinterpret_cast(&status), sizeof(status)); + stream.write(reinterpret_cast(&high_addr), sizeof(high_addr)); + stream.write(reinterpret_cast(&low_addr), sizeof(low_addr)); + stream.write(reinterpret_cast(&offset), sizeof(offset)); +} + +bool M95512::LoadEmulatorState(std::istream& stream) +{ + uint8_t state_value = 0; + stream.read(reinterpret_cast(&state_value), sizeof(state_value)); + stream.read(reinterpret_cast(&is_reading), sizeof(is_reading)); + stream.read(reinterpret_cast(&status), sizeof(status)); + stream.read(reinterpret_cast(&high_addr), sizeof(high_addr)); + stream.read(reinterpret_cast(&low_addr), sizeof(low_addr)); + stream.read(reinterpret_cast(&offset), sizeof(offset)); + if (!stream) + return false; + + if (state_value > static_cast(M95512State::STATUS)) + state_value = static_cast(M95512State::IDLE); + + state = static_cast(state_value); + offset %= 128; + return true; +} diff --git a/core/pokewalker/peripherals/m95512/m95512.h b/core/pokewalker/peripherals/m95512/m95512.h index efb9d1a..3e2c205 100644 --- a/core/pokewalker/peripherals/m95512/m95512.h +++ b/core/pokewalker/peripherals/m95512/m95512.h @@ -1,5 +1,7 @@ #pragma once #include +#include +#include #include "core/soc/ssu/peripheral.h" @@ -30,6 +32,8 @@ class M95512 : public Peripheral void Receive(uint8_t data) override; uint8_t Transmit() override; void Reset() override; + void SaveEmulatorState(std::ostream& stream) const; + bool LoadEmulatorState(std::istream& stream); EepromBuffer eeprom = {}; diff --git a/core/pokewalker/peripherals/ssd1854/ssd1854.cpp b/core/pokewalker/peripherals/ssd1854/ssd1854.cpp index bf0c0fc..343dacf 100644 --- a/core/pokewalker/peripherals/ssd1854/ssd1854.cpp +++ b/core/pokewalker/peripherals/ssd1854/ssd1854.cpp @@ -1,5 +1,6 @@ #include "ssd1854.h" +#include #include #include "core/utils/logger.h" @@ -107,3 +108,34 @@ void SSD1854::HandleCommand(uint8_t data) state = SSD1854State::IDLE; } } + +void SSD1854::SaveEmulatorState(std::ostream& stream) const +{ + const uint8_t state_value = static_cast(state); + stream.write(reinterpret_cast(&state_value), sizeof(state_value)); + stream.write(reinterpret_cast(&column), sizeof(column)); + stream.write(reinterpret_cast(&offset), sizeof(offset)); + stream.write(reinterpret_cast(&page), sizeof(page)); + stream.write(reinterpret_cast(&is_data_mode), sizeof(is_data_mode)); +} + +bool SSD1854::LoadEmulatorState(std::istream& stream) +{ + uint8_t state_value = 0; + stream.read(reinterpret_cast(&state_value), sizeof(state_value)); + stream.read(reinterpret_cast(&column), sizeof(column)); + stream.read(reinterpret_cast(&offset), sizeof(offset)); + stream.read(reinterpret_cast(&page), sizeof(page)); + stream.read(reinterpret_cast(&is_data_mode), sizeof(is_data_mode)); + if (!stream) + return false; + + if (state_value > static_cast(SSD1854State::SET_PAGE_OFFSET)) + state_value = static_cast(SSD1854State::IDLE); + + state = static_cast(state_value); + column = std::min(column, SSD1854_TOTAL_COLUMNS); + offset %= SSD1854_COLUMN_SIZE; + page %= SSD1854_TOTAL_PAGES; + return true; +} diff --git a/core/pokewalker/peripherals/ssd1854/ssd1854.h b/core/pokewalker/peripherals/ssd1854/ssd1854.h index aef3963..75e8afd 100644 --- a/core/pokewalker/peripherals/ssd1854/ssd1854.h +++ b/core/pokewalker/peripherals/ssd1854/ssd1854.h @@ -1,4 +1,7 @@ #pragma once +#include +#include + #include "core/memory/memory.h" #include "core/soc/ssu/peripheral.h" @@ -50,6 +53,8 @@ class SSD1854 : public Peripheral void Receive(uint8_t data) override; uint8_t Transmit() override; + void SaveEmulatorState(std::ostream& stream) const; + bool LoadEmulatorState(std::istream& stream); SSD1854DrawInfo draw_info = {}; @@ -63,4 +68,4 @@ class SSD1854 : public Peripheral uint8_t page = 0; bool is_data_mode = false; -}; \ No newline at end of file +}; diff --git a/core/pokewalker/pocketwalker.cpp b/core/pokewalker/pocketwalker.cpp index 5702e44..224b65d 100644 --- a/core/pokewalker/pocketwalker.cpp +++ b/core/pokewalker/pocketwalker.cpp @@ -1,9 +1,112 @@ #include "pocketwalker.h" +#include #include +#include +#include #include #include "core/soc/defines.h" +#include "core/soc/memory/regions/io.h" +#include "core/soc/memory/regions/ram.h" + +namespace +{ +constexpr std::array STATE_MAGIC = {'P', 'W', 'S', 'T', 'A', 'T', '0', '4'}; +constexpr uint32_t MAX_REASONABLE_STEPS = 9999999; + +template +void WriteValue(std::ostream& stream, const T& value) +{ + stream.write(reinterpret_cast(&value), sizeof(T)); +} + +template +bool ReadValue(std::istream& stream, T& value) +{ + stream.read(reinterpret_cast(&value), sizeof(T)); + return static_cast(stream); +} + +void WriteMemoryRange(std::ostream& stream, const std::shared_ptr& memory, const uint16_t start, const size_t size) +{ + for (size_t i = 0; i < size; i++) + { + auto ptr = memory->Ptr8(start + static_cast(i)); + const uint8_t value = ptr.ptr ? *ptr : 0xFF; + WriteValue(stream, value); + } +} + +bool ReadMemoryRange(std::istream& stream, const std::shared_ptr& memory, const uint16_t start, const size_t size) +{ + for (size_t i = 0; i < size; i++) + { + uint8_t value = 0; + if (!ReadValue(stream, value)) + return false; + + auto ptr = memory->Ptr8(start + static_cast(i)); + if (ptr.ptr) + ptr = value; + } + + return true; +} + +void WriteCpuState(std::ostream& stream, const std::shared_ptr& cpu) +{ + WriteValue(stream, cpu->reg.PC); + WriteValue(stream, cpu->reg.flags.CCR); + for (uint8_t i = 0; i < 8; i++) + WriteValue(stream, *cpu->reg.Reg32(i)); + WriteValue(stream, cpu->sleep); +} + +bool ReadCpuState(std::istream& stream, const std::shared_ptr& cpu) +{ + if (!ReadValue(stream, cpu->reg.PC)) + return false; + + if (!ReadValue(stream, cpu->reg.flags.CCR)) + return false; + + for (uint8_t i = 0; i < 8; i++) + { + uint32_t value = 0; + if (!ReadValue(stream, value)) + return false; + *cpu->reg.Reg32(i) = value; + } + + return ReadValue(stream, cpu->sleep); +} + +void WriteDisplayState(std::ostream& stream, const std::shared_ptr& display) +{ + for (uint16_t i = 0; i < SSD1854_MEM_SIZE; i++) + WriteValue(stream, display->draw_info.vram.Read8(i)); + + WriteValue(stream, display->draw_info.page_offset); + WriteValue(stream, display->draw_info.contrast); + WriteValue(stream, display->draw_info.power_save_mode); +} + +bool ReadDisplayState(std::istream& stream, const std::shared_ptr& display) +{ + for (uint16_t i = 0; i < SSD1854_MEM_SIZE; i++) + { + uint8_t value = 0; + if (!ReadValue(stream, value)) + return false; + display->draw_info.vram.Write8(i, value); + } + + return ReadValue(stream, display->draw_info.page_offset) && + ReadValue(stream, display->draw_info.contrast) && + ReadValue(stream, display->draw_info.power_save_mode); +} +} PocketWalker::PocketWalker(RomBuffer rom_buffer) { @@ -147,6 +250,208 @@ void PocketWalker::SetEepromBuffer(const EepromBuffer& buffer) const m95512->eeprom = buffer; } +uint32_t PocketWalker::GetVolatileStepCount() const +{ + const uint32_t session_steps = this->soc->memory->Read32(PW_ADDR_SESSION_STEPS); + const uint32_t total_steps = this->soc->memory->Read32(PW_ADDR_TOTAL_STEPS); + + if (session_steps > 0 && session_steps <= MAX_REASONABLE_STEPS) + return session_steps; + + if (total_steps > 0 && total_steps <= MAX_REASONABLE_STEPS) + return total_steps; + + return 0; +} + +uint16_t PocketWalker::GetVolatileWatts() const +{ + return this->soc->memory->Read16(PW_ADDR_WATTS); +} + +void PocketWalker::RestoreVolatileCounters(uint32_t steps, uint16_t watts) const +{ + if (steps > 0 && steps <= MAX_REASONABLE_STEPS) + { + this->soc->memory->Write32(PW_ADDR_SESSION_STEPS, steps); + this->soc->memory->Write32(PW_ADDR_TOTAL_STEPS, steps); + } + + if (watts > 0) + this->soc->memory->Write16(PW_ADDR_WATTS, watts); +} + +void PocketWalker::LoadRtcState(const std::string& path) const +{ + soc->rtc->LoadState(path); +} + +void PocketWalker::SaveRtcState(const std::string& path) const +{ + soc->rtc->SaveState(path); +} + +bool PocketWalker::LoadEmulatorState(const std::string& path) const +{ + std::ifstream f(path, std::ios::binary); + if (!f) + return false; + + std::array magic = {}; + f.read(magic.data(), magic.size()); + if (!f || magic != STATE_MAGIC) + return false; + + if (!ReadCpuState(f, soc->cpu)) + return false; + + f.read(reinterpret_cast(m95512->eeprom.data()), m95512->eeprom.size()); + if (!f) + return false; + + if (!m95512->LoadEmulatorState(f)) + return false; + + if (!bma150->LoadEmulatorState(f)) + return false; + + if (!ReadMemoryRange(f, soc->memory, RAM_START, RAM_SIZE)) + return false; + + if (!ReadMemoryRange(f, soc->memory, IO_LOW_START, IO_LOW_SIZE)) + return false; + + if (!ReadMemoryRange(f, soc->memory, IO_HIGH_START, IO_HIGH_SIZE)) + return false; + + if (!ReadValue(f, soc->CKSTPR1.VALUE) || !ReadValue(f, soc->CKSTPR2.VALUE)) + return false; + + if (!ReadValue(f, soc->interrupts->IENR1.VALUE) || !ReadValue(f, soc->interrupts->IENR2.VALUE) || + !ReadValue(f, soc->interrupts->IRR1.VALUE) || !ReadValue(f, soc->interrupts->IRR2.VALUE) || + !ReadValue(f, soc->interrupts->RTCFLG.VALUE) || !ReadValue(f, soc->interrupts->RTCCR2.VALUE) || + !ReadValue(f, soc->interrupts->TIERW.VALUE) || !ReadValue(f, soc->interrupts->TSRW.VALUE)) + return false; + + if (!ReadValue(f, soc->ssu->PDRB.VALUE) || !ReadValue(f, soc->ssu->PMRB.VALUE) || + !ReadValue(f, soc->ssu->PFCR.VALUE) || !ReadValue(f, soc->ssu->SSMR.VALUE) || + !ReadValue(f, soc->ssu->SSER.VALUE) || !ReadValue(f, soc->ssu->SSSR.VALUE) || + !ReadValue(f, soc->ssu->SSRDR) || !ReadValue(f, soc->ssu->SSTDR)) + return false; + + if (!ReadValue(f, soc->sci3->SMR.VALUE) || !ReadValue(f, soc->sci3->SSR.VALUE) || + !ReadValue(f, soc->sci3->SCR.VALUE) || !ReadValue(f, soc->sci3->IRCR.VALUE) || + !ReadValue(f, soc->sci3->BRR) || !ReadValue(f, soc->sci3->TDR) || + !ReadValue(f, soc->sci3->RDR)) + return false; + if (!soc->sci3->LoadEmulatorState(f)) + return false; + + if (!ReadValue(f, soc->timer_b1->TMB1.VALUE) || !ReadValue(f, soc->timer_b1->TCB1) || + !ReadValue(f, soc->timer_b1->TLB1)) + return false; + if (!soc->timer_b1->LoadEmulatorState(f)) + return false; + + if (!ReadValue(f, soc->timer_w->TMRW.VALUE) || !ReadValue(f, soc->timer_w->TCRW.VALUE)) + return false; + if (!soc->timer_w->LoadEmulatorState(f)) + return false; + + if (!ReadValue(f, soc->rtc->RTCCR1.VALUE) || !ReadValue(f, soc->rtc->RSECDR) || + !ReadValue(f, soc->rtc->RMINDR) || !ReadValue(f, soc->rtc->RHRDR) || + !ReadValue(f, soc->rtc->RWKDR)) + return false; + + if (!ReadValue(f, soc->adc->ADSR.VALUE) || !ReadValue(f, soc->adc->AMR.VALUE)) + return false; + + if (!ReadDisplayState(f, ssd1854)) + return false; + if (!ssd1854->LoadEmulatorState(f)) + return false; + + if (!soc->ssu->LoadEmulatorState(f)) + return false; + + const uint32_t total_steps = soc->memory->Read32(PW_ADDR_TOTAL_STEPS); + const uint32_t session_steps = soc->memory->Read32(PW_ADDR_SESSION_STEPS); + if (session_steps == 0 && total_steps > 0 && total_steps <= MAX_REASONABLE_STEPS) + soc->memory->Write32(PW_ADDR_SESSION_STEPS, total_steps); + + return true; +} + +void PocketWalker::SaveEmulatorState(const std::string& path) const +{ + std::ofstream f(path, std::ios::binary); + if (!f) + return; + + f.write(STATE_MAGIC.data(), STATE_MAGIC.size()); + + WriteCpuState(f, soc->cpu); + f.write(reinterpret_cast(m95512->eeprom.data()), m95512->eeprom.size()); + m95512->SaveEmulatorState(f); + bma150->SaveEmulatorState(f); + + WriteMemoryRange(f, soc->memory, RAM_START, RAM_SIZE); + WriteMemoryRange(f, soc->memory, IO_LOW_START, IO_LOW_SIZE); + WriteMemoryRange(f, soc->memory, IO_HIGH_START, IO_HIGH_SIZE); + + WriteValue(f, soc->CKSTPR1.VALUE); + WriteValue(f, soc->CKSTPR2.VALUE); + + WriteValue(f, soc->interrupts->IENR1.VALUE); + WriteValue(f, soc->interrupts->IENR2.VALUE); + WriteValue(f, soc->interrupts->IRR1.VALUE); + WriteValue(f, soc->interrupts->IRR2.VALUE); + WriteValue(f, soc->interrupts->RTCFLG.VALUE); + WriteValue(f, soc->interrupts->RTCCR2.VALUE); + WriteValue(f, soc->interrupts->TIERW.VALUE); + WriteValue(f, soc->interrupts->TSRW.VALUE); + + WriteValue(f, soc->ssu->PDRB.VALUE); + WriteValue(f, soc->ssu->PMRB.VALUE); + WriteValue(f, soc->ssu->PFCR.VALUE); + WriteValue(f, soc->ssu->SSMR.VALUE); + WriteValue(f, soc->ssu->SSER.VALUE); + WriteValue(f, soc->ssu->SSSR.VALUE); + WriteValue(f, soc->ssu->SSRDR); + WriteValue(f, soc->ssu->SSTDR); + + WriteValue(f, soc->sci3->SMR.VALUE); + WriteValue(f, soc->sci3->SSR.VALUE); + WriteValue(f, soc->sci3->SCR.VALUE); + WriteValue(f, soc->sci3->IRCR.VALUE); + WriteValue(f, soc->sci3->BRR); + WriteValue(f, soc->sci3->TDR); + WriteValue(f, soc->sci3->RDR); + soc->sci3->SaveEmulatorState(f); + + WriteValue(f, soc->timer_b1->TMB1.VALUE); + WriteValue(f, soc->timer_b1->TCB1); + WriteValue(f, soc->timer_b1->TLB1); + soc->timer_b1->SaveEmulatorState(f); + + WriteValue(f, soc->timer_w->TMRW.VALUE); + WriteValue(f, soc->timer_w->TCRW.VALUE); + soc->timer_w->SaveEmulatorState(f); + + WriteValue(f, soc->rtc->RTCCR1.VALUE); + WriteValue(f, soc->rtc->RSECDR); + WriteValue(f, soc->rtc->RMINDR); + WriteValue(f, soc->rtc->RHRDR); + WriteValue(f, soc->rtc->RWKDR); + + WriteValue(f, soc->adc->ADSR.VALUE); + WriteValue(f, soc->adc->AMR.VALUE); + + WriteDisplayState(f, ssd1854); + ssd1854->SaveEmulatorState(f); + soc->ssu->SaveEmulatorState(f); +} + void PocketWalker::CyclePeripherals(uint8_t cycles) const { this->buzzer->Cycle(cycles); diff --git a/core/pokewalker/pocketwalker.h b/core/pokewalker/pocketwalker.h index bcee710..22810bf 100644 --- a/core/pokewalker/pocketwalker.h +++ b/core/pokewalker/pocketwalker.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "core/soc/h838606.h" #include "core/utils/event_handler.h" @@ -52,6 +53,13 @@ class PocketWalker EepromBuffer GetEepromBuffer() const; void SetEepromBuffer(const EepromBuffer& buffer) const; + uint32_t GetVolatileStepCount() const; + uint16_t GetVolatileWatts() const; + void RestoreVolatileCounters(uint32_t steps, uint16_t watts) const; + void LoadRtcState(const std::string& path) const; + void SaveRtcState(const std::string& path) const; + bool LoadEmulatorState(const std::string& path) const; + void SaveEmulatorState(const std::string& path) const; private: void CyclePeripherals(uint8_t cycles) const; diff --git a/core/soc/rtc/rtc.cpp b/core/soc/rtc/rtc.cpp index 91fc660..ee83511 100644 --- a/core/soc/rtc/rtc.cpp +++ b/core/soc/rtc/rtc.cpp @@ -1,10 +1,14 @@ #include "rtc.h" +#include #include #include +#include #include "core/soc/defines.h" +using Clock = std::chrono::steady_clock; + static uint8_t BCD(const uint8_t number) { const uint8_t tens = std::floor(number / 10); @@ -13,93 +17,243 @@ static uint8_t BCD(const uint8_t number) return tens * 16 + ones; } +static uint8_t FromBCD(const uint8_t value) +{ + return ((value >> 4) * 10) + (value & 0x0F); +} + +static std::tm LocalTime(const time_t value) +{ + std::tm result = {}; +#ifdef _WIN32 + localtime_s(&result, &value); +#else + localtime_r(&value, &result); +#endif + return result; +} + RTC::RTC(const std::shared_ptr& interrupts) { this->interrupts = interrupts; + this->virtual_time = std::time(nullptr); } void RTC::RegisterIOHandlers(const std::shared_ptr& io) { IO_HANDLER_READ_UNION(RTC_ADDR_RTCCR1, RTCCR1); - IO_HANDLER_WRITE_UNION(RTC_ADDR_RTCCR1, RTCCR1); + io->RegisterWriteHandler(RTC_ADDR_RTCCR1, [this](const uint8_t value) { WriteRTCCR1(value); }); IO_HANDLER_READ_VALUE(RTC_ADDR_RSECDR, RSECDR); - IO_HANDLER_WRITE_VALUE(RTC_ADDR_RSECDR, RSECDR); + io->RegisterWriteHandler(RTC_ADDR_RSECDR, [this](const uint8_t value) { WriteSeconds(value); }); IO_HANDLER_READ_VALUE(RTC_ADDR_RMINDR, RMINDR); - IO_HANDLER_WRITE_VALUE(RTC_ADDR_RMINDR, RMINDR); + io->RegisterWriteHandler(RTC_ADDR_RMINDR, [this](const uint8_t value) { WriteMinutes(value); }); IO_HANDLER_READ_VALUE(RTC_ADDR_RHRDR, RHRDR); - IO_HANDLER_WRITE_VALUE(RTC_ADDR_RHRDR, RHRDR); + io->RegisterWriteHandler(RTC_ADDR_RHRDR, [this](const uint8_t value) { WriteHours(value); }); IO_HANDLER_READ_VALUE(RTC_ADDR_RWKDR, RWKDR); - IO_HANDLER_WRITE_VALUE(RTC_ADDR_RWKDR, RWKDR); + io->RegisterWriteHandler(RTC_ADDR_RWKDR, [this](const uint8_t value) { WriteWeekday(value); }); +} + +void RTC::LoadState(const std::string& path) +{ + const auto now_wall = Clock::now(); + ignore_rtc_writes_until = now_wall + std::chrono::seconds(3); + wall_clock_initialized = false; + + std::ifstream f(path, std::ios::binary); + if (!f) + { + virtual_time = std::time(nullptr); + SetRegistersFromVirtualTime(); + last_time = LocalTime(virtual_time); + return; + } + + char magic[8] = {}; + int64_t saved_virtual_time = 0; + int64_t saved_host_time = 0; + f.read(magic, sizeof(magic)); + f.read(reinterpret_cast(&saved_virtual_time), sizeof(saved_virtual_time)); + f.read(reinterpret_cast(&saved_host_time), sizeof(saved_host_time)); + + if (!f || std::string(magic, sizeof(magic)) != "PWRTC001") + { + virtual_time = std::time(nullptr); + } + else + { + const time_t now = std::time(nullptr); + int64_t elapsed = static_cast(now) - saved_host_time; + if (elapsed < 0) + elapsed = 0; + virtual_time = static_cast(saved_virtual_time + elapsed); + } + + SetRegistersFromVirtualTime(); + last_time = LocalTime(virtual_time); +} + +void RTC::SaveState(const std::string& path) +{ + std::ofstream f(path, std::ios::binary); + if (!f) + return; + + const char magic[8] = {'P', 'W', 'R', 'T', 'C', '0', '0', '1'}; + const int64_t saved_virtual_time = static_cast(virtual_time); + const int64_t saved_host_time = static_cast(std::time(nullptr)); + + f.write(magic, sizeof(magic)); + f.write(reinterpret_cast(&saved_virtual_time), sizeof(saved_virtual_time)); + f.write(reinterpret_cast(&saved_host_time), sizeof(saved_host_time)); } void RTC::Cycle(uint8_t cycles) { + (void)cycles; + if (!RTCCR1.RUN) + { + wall_clock_initialized = false; return; + } - rtc_cycles += cycles; - if (rtc_cycles >= PHI_CLK / 4) + const auto now = Clock::now(); + if (!wall_clock_initialized) { - rtc_cycles -= PHI_CLK / 4; + last_wall_tick = now; + wall_clock_initialized = true; + return; + } - const time_t now = std::time(nullptr); - std::tm current_time = {}; -#ifdef _WIN32 - localtime_s(¤t_time, &now); -#else - localtime_r(&now, ¤t_time); -#endif + const auto tick = std::chrono::milliseconds(250); + while (now - last_wall_tick >= tick) + { + last_wall_tick += tick; + TickQuarter(); + } +} + +void RTC::TickQuarter() +{ + quarters++; + + if (quarters % 4 == 0) + virtual_time++; + + const std::tm current_time = LocalTime(virtual_time); + SetRegistersFromVirtualTime(); + + if (!initialized) + { + interrupts->RTCFLG.SEIFG025 = true; + interrupts->RTCFLG.SEIFG05 = true; + interrupts->RTCFLG.SEIFG1 = true; + interrupts->RTCFLG.MNIFG = true; + interrupts->RTCFLG.HRIFG = true; + interrupts->RTCFLG.DYIFG = true; + interrupts->RTCFLG.WKIFG = true; - RSECDR = BCD(current_time.tm_sec); - RMINDR = BCD(current_time.tm_min); - RHRDR = BCD(RTCCR1.HR24 ? current_time.tm_hour : current_time.tm_hour % 12); - RWKDR = BCD(current_time.tm_wday); + last_time = current_time; + initialized = true; + } + + interrupts->RTCFLG.SEIFG025 = true; + + if (quarters % 2 == 0) + interrupts->RTCFLG.SEIFG05 = true; + + if (quarters % 4 == 0) + { + quarters = 0; - if (!initialized) - { - interrupts->RTCFLG.SEIFG025 = true; - interrupts->RTCFLG.SEIFG05 = true; + if (current_time.tm_sec != last_time.tm_sec) interrupts->RTCFLG.SEIFG1 = true; + + if (current_time.tm_min != last_time.tm_min) interrupts->RTCFLG.MNIFG = true; + + if (current_time.tm_hour != last_time.tm_hour) interrupts->RTCFLG.HRIFG = true; + + if (current_time.tm_mday != last_time.tm_mday) [[unlikely]] interrupts->RTCFLG.DYIFG = true; + + if (current_time.tm_wday != last_time.tm_wday) [[unlikely]] interrupts->RTCFLG.WKIFG = true; - last_time = current_time; - initialized = true; - } + last_time = current_time; + } +} - quarters++; +void RTC::SetRegistersFromVirtualTime() +{ + const std::tm current_time = LocalTime(virtual_time); + RSECDR = BCD(current_time.tm_sec); + RMINDR = BCD(current_time.tm_min); + RHRDR = BCD(RTCCR1.HR24 ? current_time.tm_hour : current_time.tm_hour % 12); + RWKDR = BCD(current_time.tm_wday); +} - interrupts->RTCFLG.SEIFG025 = true; +void RTC::SyncVirtualTimeFromRegisters() +{ + std::tm current_time = LocalTime(virtual_time); + current_time.tm_sec = std::min(FromBCD(RSECDR), 59); + current_time.tm_min = std::min(FromBCD(RMINDR), 59); - if (quarters % 2 == 0) - interrupts->RTCFLG.SEIFG05 = true; + uint8_t hour = FromBCD(RHRDR); + if (!RTCCR1.HR24) + { + hour %= 12; + if (RTCCR1.PM) + hour += 12; + } + current_time.tm_hour = std::min(hour, 23); + current_time.tm_isdst = -1; - if (quarters % 4 == 0) - { - quarters = 0; + virtual_time = std::mktime(¤t_time); + last_time = LocalTime(virtual_time); +} - if (current_time.tm_sec != last_time.tm_sec) - interrupts->RTCFLG.SEIFG1 = true; +bool RTC::CanAcceptRtcWrites() const +{ + return Clock::now() >= ignore_rtc_writes_until; +} - if (current_time.tm_min != last_time.tm_min) - interrupts->RTCFLG.MNIFG = true; +void RTC::WriteRTCCR1(const uint8_t value) +{ + RTCCR1.VALUE = value; + if (CanAcceptRtcWrites()) + SyncVirtualTimeFromRegisters(); +} - if (current_time.tm_hour != last_time.tm_hour) - interrupts->RTCFLG.HRIFG = true; +void RTC::WriteSeconds(const uint8_t value) +{ + RSECDR = value; + if (CanAcceptRtcWrites()) + SyncVirtualTimeFromRegisters(); +} - if (current_time.tm_mday != last_time.tm_mday) [[unlikely]] - interrupts->RTCFLG.DYIFG = true; +void RTC::WriteMinutes(const uint8_t value) +{ + RMINDR = value; + if (CanAcceptRtcWrites()) + SyncVirtualTimeFromRegisters(); +} - if (current_time.tm_wday != last_time.tm_wday) [[unlikely]] - interrupts->RTCFLG.WKIFG = true; +void RTC::WriteHours(const uint8_t value) +{ + RHRDR = value; + if (CanAcceptRtcWrites()) + SyncVirtualTimeFromRegisters(); +} - last_time = current_time; - } - } +void RTC::WriteWeekday(const uint8_t value) +{ + RWKDR = value; + if (CanAcceptRtcWrites()) + SyncVirtualTimeFromRegisters(); } diff --git a/core/soc/rtc/rtc.h b/core/soc/rtc/rtc.h index 5c25e5b..c367a0a 100644 --- a/core/soc/rtc/rtc.h +++ b/core/soc/rtc/rtc.h @@ -1,5 +1,7 @@ #pragma once +#include #include +#include #include "core/soc/interrupts/interrupts.h" @@ -32,6 +34,8 @@ class RTC void RegisterIOHandlers(const std::shared_ptr& io); void Cycle(uint8_t cycles); + void LoadState(const std::string& path); + void SaveState(const std::string& path); RTCCR1_t RTCCR1 = {}; uint8_t RSECDR = 0; @@ -45,5 +49,19 @@ class RTC uint32_t rtc_cycles = 0; uint8_t quarters = 0; bool initialized = false; + bool wall_clock_initialized = false; + std::chrono::steady_clock::time_point last_wall_tick = {}; + std::chrono::steady_clock::time_point ignore_rtc_writes_until = {}; std::tm last_time = {}; + time_t virtual_time = 0; + + void SetRegistersFromVirtualTime(); + void SyncVirtualTimeFromRegisters(); + bool CanAcceptRtcWrites() const; + void TickQuarter(); + void WriteRTCCR1(uint8_t value); + void WriteSeconds(uint8_t value); + void WriteMinutes(uint8_t value); + void WriteHours(uint8_t value); + void WriteWeekday(uint8_t value); }; diff --git a/core/soc/sci3/sci3.cpp b/core/soc/sci3/sci3.cpp index 8423af9..e8187c4 100644 --- a/core/soc/sci3/sci3.cpp +++ b/core/soc/sci3/sci3.cpp @@ -122,3 +122,45 @@ void SCI3::ReceiveIR(uint8_t data) std::lock_guard lock(receive_mutex); receive_queue.push(data); } + +void SCI3::SaveEmulatorState(std::ostream& stream) +{ + std::lock_guard lock(receive_mutex); + stream.write(reinterpret_cast(&sci3_cycles), sizeof(sci3_cycles)); + + uint32_t queue_size = static_cast(receive_queue.size()); + stream.write(reinterpret_cast(&queue_size), sizeof(queue_size)); + + auto queue_copy = receive_queue; + while (!queue_copy.empty()) + { + const uint8_t value = queue_copy.front(); + queue_copy.pop(); + stream.write(reinterpret_cast(&value), sizeof(value)); + } +} + +bool SCI3::LoadEmulatorState(std::istream& stream) +{ + std::lock_guard lock(receive_mutex); + stream.read(reinterpret_cast(&sci3_cycles), sizeof(sci3_cycles)); + if (!stream) + return false; + + uint32_t queue_size = 0; + stream.read(reinterpret_cast(&queue_size), sizeof(queue_size)); + if (!stream) + return false; + + receive_queue = {}; + for (uint32_t i = 0; i < queue_size; i++) + { + uint8_t value = 0; + stream.read(reinterpret_cast(&value), sizeof(value)); + if (!stream) + return false; + receive_queue.push(value); + } + + return true; +} diff --git a/core/soc/sci3/sci3.h b/core/soc/sci3/sci3.h index 484e678..62aed70 100644 --- a/core/soc/sci3/sci3.h +++ b/core/soc/sci3/sci3.h @@ -1,7 +1,9 @@ #pragma once +#include #include #include +#include #include #include "core/soc/memory/regions/io.h" @@ -86,6 +88,8 @@ class SCI3 void OnTransmitIR(const EventHandlerCallback& callback); void ReceiveIR(uint8_t data); + void SaveEmulatorState(std::ostream& stream); + bool LoadEmulatorState(std::istream& stream); SMR_t SMR = {}; SSR_t SSR = {}; @@ -104,4 +108,4 @@ class SCI3 std::queue receive_queue; std::mutex receive_mutex; -}; \ No newline at end of file +}; diff --git a/core/soc/ssu/ssu.cpp b/core/soc/ssu/ssu.cpp index e0ad5c5..b12b01c 100644 --- a/core/soc/ssu/ssu.cpp +++ b/core/soc/ssu/ssu.cpp @@ -190,4 +190,15 @@ std::shared_ptr SSU::ActivePeripheral() } return nullptr; -} \ No newline at end of file +} + +void SSU::SaveEmulatorState(std::ostream& stream) const +{ + stream.write(reinterpret_cast(&ssu_cycles), sizeof(ssu_cycles)); +} + +bool SSU::LoadEmulatorState(std::istream& stream) +{ + stream.read(reinterpret_cast(&ssu_cycles), sizeof(ssu_cycles)); + return static_cast(stream); +} diff --git a/core/soc/ssu/ssu.h b/core/soc/ssu/ssu.h index c93092b..1252b53 100644 --- a/core/soc/ssu/ssu.h +++ b/core/soc/ssu/ssu.h @@ -1,5 +1,7 @@ #pragma once +#include #include +#include #include #include @@ -152,6 +154,8 @@ class SSU uint8_t pin_index); void Cycle(uint8_t cycles); + void SaveEmulatorState(std::ostream& stream) const; + bool LoadEmulatorState(std::istream& stream); PDRB_t PDRB = {}; PMRB_t PMRB = {}; diff --git a/core/soc/timer/timerb1.cpp b/core/soc/timer/timerb1.cpp index 217fb88..78d92dc 100644 --- a/core/soc/timer/timerb1.cpp +++ b/core/soc/timer/timerb1.cpp @@ -48,3 +48,14 @@ void TimerB1::Cycle(uint8_t cycles) } } } + +void TimerB1::SaveEmulatorState(std::ostream& stream) const +{ + stream.write(reinterpret_cast(&timer_b1_cycles), sizeof(timer_b1_cycles)); +} + +bool TimerB1::LoadEmulatorState(std::istream& stream) +{ + stream.read(reinterpret_cast(&timer_b1_cycles), sizeof(timer_b1_cycles)); + return static_cast(stream); +} diff --git a/core/soc/timer/timerb1.h b/core/soc/timer/timerb1.h index 64e27eb..9f8a946 100644 --- a/core/soc/timer/timerb1.h +++ b/core/soc/timer/timerb1.h @@ -1,5 +1,7 @@ #pragma once +#include #include +#include #include "core/memory/interface.h" #include "core/soc/interrupts/interrupts.h" @@ -28,6 +30,8 @@ class TimerB1 void RegisterIOHandlers(const std::shared_ptr& io); void Cycle(uint8_t cycles); + void SaveEmulatorState(std::ostream& stream) const; + bool LoadEmulatorState(std::istream& stream); TMB1_t TMB1 = {}; uint8_t TCB1 = 0; diff --git a/core/soc/timer/timerw.cpp b/core/soc/timer/timerw.cpp index 2579569..9f62e05 100644 --- a/core/soc/timer/timerw.cpp +++ b/core/soc/timer/timerw.cpp @@ -62,3 +62,14 @@ void TimerW::Cycle(uint8_t cycles) } } } + +void TimerW::SaveEmulatorState(std::ostream& stream) const +{ + stream.write(reinterpret_cast(&timer_w_cycles), sizeof(timer_w_cycles)); +} + +bool TimerW::LoadEmulatorState(std::istream& stream) +{ + stream.read(reinterpret_cast(&timer_w_cycles), sizeof(timer_w_cycles)); + return static_cast(stream); +} diff --git a/core/soc/timer/timerw.h b/core/soc/timer/timerw.h index eeea10a..8d4abdf 100644 --- a/core/soc/timer/timerw.h +++ b/core/soc/timer/timerw.h @@ -1,4 +1,7 @@ #pragma once +#include +#include + #include "core/memory/interface.h" #include "core/soc/interrupts/interrupts.h" #include "core/utils/h8300h_ptr.h" @@ -53,6 +56,8 @@ class TimerW void RegisterIOHandlers(const std::shared_ptr& io); void Cycle(uint8_t cycles); + void SaveEmulatorState(std::ostream& stream) const; + bool LoadEmulatorState(std::istream& stream); TMRW_t TMRW = {}; TCRW_t TCRW = {}; diff --git a/desktop/src/main.cpp b/desktop/src/main.cpp index 4c89ad8..fc1caa7 100644 --- a/desktop/src/main.cpp +++ b/desktop/src/main.cpp @@ -30,6 +30,10 @@ int main(int argc, char* argv[]) .help("Runs in server mode.") .flag(); + arguments.add_argument("--no-menu") + .help("Hides the main window menu bar.") + .flag(); + arguments.add_argument("--ip") .help("IP address to connect to (client mode)."); @@ -54,6 +58,7 @@ int main(int argc, char* argv[]) args.rom_path = arguments.present("rom"); args.save_path = arguments.present("save"); args.server_mode = arguments.get("--server"); + args.no_menu = arguments.get("--no-menu"); args.host = arguments.present("--ip"); args.port = arguments.present("--port"); diff --git a/desktop/src/qt/application_args.h b/desktop/src/qt/application_args.h index 7e60e98..2cf66e6 100644 --- a/desktop/src/qt/application_args.h +++ b/desktop/src/qt/application_args.h @@ -8,6 +8,7 @@ struct ApplicationArguments std::optional rom_path; std::optional save_path; std::optional server_mode; + bool no_menu = false; std::optional host; std::optional port; }; diff --git a/desktop/src/qt/emulator/emulator_context.cpp b/desktop/src/qt/emulator/emulator_context.cpp index 64cbb20..616cf34 100644 --- a/desktop/src/qt/emulator/emulator_context.cpp +++ b/desktop/src/qt/emulator/emulator_context.cpp @@ -15,6 +15,11 @@ EmulatorContext::EmulatorContext(const std::string& rom_path, const std::string& emu.emplace(rom_buffer); loadSave(); + if (!this->save_path.empty()) + { + emu->LoadRtcState(this->save_path + ".rtc"); + emu->LoadEmulatorState(this->save_path + ".state"); + } audio = std::make_unique(); emu->OnSamplePushed([this](BuzzerInformation info) @@ -87,4 +92,6 @@ void EmulatorContext::writeSave() const EepromBuffer buf = emu->GetEepromBuffer(); std::ofstream f(save_path, std::ios::binary); f.write(reinterpret_cast(buf.data()), buf.size()); + emu->SaveRtcState(save_path + ".rtc"); + emu->SaveEmulatorState(save_path + ".state"); } diff --git a/desktop/src/qt/emulator/emulator_context.h b/desktop/src/qt/emulator/emulator_context.h index 533001a..ef8eec7 100644 --- a/desktop/src/qt/emulator/emulator_context.h +++ b/desktop/src/qt/emulator/emulator_context.h @@ -37,4 +37,4 @@ class EmulatorContext : public QObject std::unique_ptr network; std::unique_ptr network_thread; std::unique_ptr emulator_thread; -}; \ No newline at end of file +}; diff --git a/desktop/src/qt/window/qt_window_system.cpp b/desktop/src/qt/window/qt_window_system.cpp index 296c132..fdff1a1 100644 --- a/desktop/src/qt/window/qt_window_system.cpp +++ b/desktop/src/qt/window/qt_window_system.cpp @@ -24,6 +24,8 @@ QtWindowSystem::QtWindowSystem(ApplicationArguments args, QWidget* parent) : QMainWindow(parent), args(args) { setWindowTitle("PocketWalker"); + if (!args.no_menu) + { menuBar()->setNativeMenuBar(true); #if WIN32 if (QApplication::style()->name() == "windows11") @@ -154,6 +156,7 @@ QtWindowSystem::QtWindowSystem(ApplicationArguments args, QWidget* parent) dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->exec(); }); + } display = new DisplayWidget(this); setCentralWidget(display); @@ -264,7 +267,8 @@ void QtWindowSystem::resetEmulator() const std::string path = context->romPath(); launchEmulator(path); - context->emulator().UseSyntheticSteps(synthetic_steps_action->isChecked()); + if (synthetic_steps_action) + context->emulator().UseSyntheticSteps(synthetic_steps_action->isChecked()); } void QtWindowSystem::launchEmulator(const std::string& rom_path, const std::string& save_path) @@ -302,13 +306,28 @@ void QtWindowSystem::shutdownEmulator() void QtWindowSystem::setEmulatorActionsEnabled(bool enabled) { - import_save_action->setEnabled(enabled); - reset_action->setEnabled(enabled); - pause_action->setEnabled(enabled); - stop_action->setEnabled(enabled); - synthetic_steps_action->setEnabled(enabled); - set_watts_action->setEnabled(enabled); - set_session_steps_action->setEnabled(enabled); + if (import_save_action) import_save_action->setEnabled(enabled); + if (reset_action) reset_action->setEnabled(enabled); + if (pause_action) pause_action->setEnabled(enabled); + if (stop_action) stop_action->setEnabled(enabled); + if (synthetic_steps_action) synthetic_steps_action->setEnabled(enabled); + if (set_watts_action) set_watts_action->setEnabled(enabled); + if (set_session_steps_action) set_session_steps_action->setEnabled(enabled); +} + +void QtWindowSystem::releaseHeldInputs() +{ + if (!context) + return; + + context->emulator().ReleaseButton(ButtonType::LEFT); + context->emulator().ReleaseButton(ButtonType::RIGHT); + context->emulator().ReleaseButton(ButtonType::CENTER); + context->emulator().UseFastMode(false); + context->emulator().UseSyntheticSteps(false); + + if (synthetic_steps_action) + synthetic_steps_action->setChecked(false); } void QtWindowSystem::addToRecentROMs(const std::string& path) @@ -324,6 +343,9 @@ void QtWindowSystem::addToRecentROMs(const std::string& path) void QtWindowSystem::updateRecentROMsMenu() { + if (!recent_roms_menu) + return; + recent_roms_menu->clear(); const auto& recent = AppSettings::instance.general.recent_roms; @@ -392,7 +414,8 @@ void QtWindowSystem::keyPressEvent(QKeyEvent* event) else if (key == controls.key_synthetic_steps_hold) { context->emulator().UseSyntheticSteps(true); - synthetic_steps_action->setChecked(true); + if (synthetic_steps_action) + synthetic_steps_action->setChecked(true); } else QMainWindow::keyPressEvent(event); } @@ -415,13 +438,23 @@ void QtWindowSystem::keyReleaseEvent(QKeyEvent* event) else if (key == controls.key_synthetic_steps_hold) { context->emulator().UseSyntheticSteps(false); - synthetic_steps_action->setChecked(false); + if (synthetic_steps_action) + synthetic_steps_action->setChecked(false); } else QMainWindow::keyReleaseEvent(event); } +void QtWindowSystem::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::ActivationChange && !isActiveWindow()) + releaseHeldInputs(); + + QMainWindow::changeEvent(event); +} + void QtWindowSystem::closeEvent(QCloseEvent* event) { + releaseHeldInputs(); shutdownEmulator(); event->accept(); } diff --git a/desktop/src/qt/window/qt_window_system.h b/desktop/src/qt/window/qt_window_system.h index 3b28394..9672195 100644 --- a/desktop/src/qt/window/qt_window_system.h +++ b/desktop/src/qt/window/qt_window_system.h @@ -21,6 +21,7 @@ class QtWindowSystem : public QMainWindow protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; + void changeEvent(QEvent* event) override; void closeEvent(QCloseEvent* event) override; private slots: @@ -35,6 +36,7 @@ private slots: void addToRecentROMs(const std::string& path); void updateRecentROMsMenu(); void setEmulatorActionsEnabled(bool enabled); + void releaseHeldInputs(); void applyTheme(); void setBypassPowerSave();