18#include "absl/status/status.h"
19#include "absl/strings/str_format.h"
22#define RETURN_IF_ERROR(expr) \
24 absl::Status _status = (expr); \
25 if (!_status.ok()) { \
48 uint8_t ret = (~input->latched_state_) & 1;
56 return *
reinterpret_cast<uint8_t*
>(&test) == 1;
63constexpr uint32_t
MakeTag(
char a,
char b,
char c,
char d) {
64 return static_cast<uint32_t
>(a) | (
static_cast<uint32_t
>(b) << 8) |
65 (
static_cast<uint32_t
>(c) << 16) | (
static_cast<uint32_t
>(d) << 24);
68absl::Status
WriteBytes(std::ostream& out,
const void* data,
size_t size) {
69 out.write(
reinterpret_cast<const char*
>(data), size);
71 return absl::InternalError(
"Failed to write bytes to state stream");
73 return absl::OkStatus();
76absl::Status
ReadBytes(std::istream& in,
void* data,
size_t size) {
77 in.read(
reinterpret_cast<char*
>(data), size);
79 return absl::InternalError(
"Failed to read bytes from state stream");
81 return absl::OkStatus();
85 std::array<uint8_t, 4> bytes = {
static_cast<uint8_t
>(value & 0xFF),
86 static_cast<uint8_t
>((value >> 8) & 0xFF),
87 static_cast<uint8_t
>((value >> 16) & 0xFF),
88 static_cast<uint8_t
>(value >> 24)};
89 return WriteBytes(out, bytes.data(), bytes.size());
93 std::array<uint8_t, 4> bytes{};
94 auto status =
ReadBytes(in, bytes.data(), bytes.size());
98 *value =
static_cast<uint32_t
>(bytes[0]) |
99 (
static_cast<uint32_t
>(bytes[1]) << 8) |
100 (
static_cast<uint32_t
>(bytes[2]) << 16) |
101 (
static_cast<uint32_t
>(bytes[3]) << 24);
102 return absl::OkStatus();
107 static_assert(std::is_trivially_copyable<T>::value,
108 "Only trivial scalars supported");
109 std::array<uint8_t,
sizeof(T)> buffer{};
110 std::memcpy(buffer.data(), &value,
sizeof(T));
111 return WriteBytes(out, buffer.data(), buffer.size());
116 static_assert(std::is_trivially_copyable<T>::value,
117 "Only trivial scalars supported");
118 std::array<uint8_t,
sizeof(T)> buffer{};
119 auto status =
ReadBytes(in, buffer.data(), buffer.size());
123 std::memcpy(value, buffer.data(),
sizeof(T));
124 return absl::OkStatus();
134absl::Status
WriteChunk(std::ostream& out, uint32_t tag, uint32_t version,
135 const std::string& payload) {
137 return absl::FailedPreconditionError(
"Serialized chunk too large");
139 ChunkHeader header{tag, version,
static_cast<uint32_t
>(payload.size()),
141 reinterpret_cast<const uint8_t*
>(payload.data()),
149 return absl::OkStatus();
157 return absl::OkStatus();
163 LOG_DEBUG(
"SNES",
"Initializing emulator with ROM size %zu bytes",
178 LOG_DEBUG(
"SNES",
"Emulator initialization complete");
182 LOG_DEBUG(
"SNES",
"Reset called (hard=%d)", hard);
196 memset(
ram, 0,
sizeof(
ram));
225 LOG_DEBUG(
"SNES",
"Reset complete - CPU will start at $%02X:%04X",
cpu_.
PB,
293 for (
int i = 0; i < 16; i++) {
295 uint8_t val1 = (latched1 >> i) & 1;
296 uint8_t val2 = (latched2 >> i) & 1;
389 static int frame_log = 0;
390 if (++frame_log % 60 == 0)
LOG_INFO(
"SNES",
"Frames incremented 60 times");
399 static int frame_log_pal = 0;
400 if (++frame_log_pal % 60 == 0)
LOG_INFO(
"SNES",
"Frames (PAL) incremented 60 times");
405 bool starting_vblank =
false;
408 static int vblank_end_count = 0;
409 if (vblank_end_count++ < 10) {
412 "VBlank END - v_pos=0, setting in_vblank_=false at frame %d",
426 starting_vblank =
true;
428 if (starting_vblank) {
443 static int vblank_start_count = 0;
444 if (vblank_start_count++ < 10) {
447 "VBlank START - v_pos=%d, setting in_vblank_=true at frame %d",
478 for (
int i = 0; i < cycles; i += 2) {
487 count = sync_cycles - (
cycles_ % sync_cycles);
502 static int cpu_port_read_count = 0;
503 static uint8_t last_f4 = 0xFF, last_f5 = 0xFF;
504 bool value_changed = ((adr & 0x3) == 0 && val != last_f4) ||
505 ((adr & 0x3) == 1 && val != last_f5);
506 if (value_changed || cpu_port_read_count++ < 5) {
508 "CPU read APU port $21%02X (F%d) = $%02X at PC=$%02X:%04X "
509 "[AFTER CatchUp: APU_cycles=%llu CPU_cycles=%llu]",
510 0x40 + (adr & 0x3), (adr & 0x3) + 4, val,
cpu_.
PB,
cpu_.
PC,
512 if ((adr & 0x3) == 0)
514 if ((adr & 0x3) == 1)
588 uint8_t bank = adr >> 16;
590 if (bank == 0x7e || bank == 0x7f) {
591 return ram[((bank & 1) << 16) | adr];
593 if (bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
597 if (adr >= 0x2100 && adr < 0x2200) {
608 if (adr >= 0x4200 && adr < 0x4220) {
612 if (adr >= 0x4300 && adr < 0x4380) {
621 uint8_t val =
Rread(adr);
642 uint32_t full_pc = (
static_cast<uint32_t
>(
cpu_.
PB) << 16) |
cpu_.
PC;
775 uint8_t bank = adr >> 16;
777 if (bank == 0x7e || bank == 0x7f) {
779 ram[((bank & 1) << 16) | adr] = val;
781 if (bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
786 if (adr >= 0x2100 && adr < 0x2200) {
791 input_latch(&
input1, val & 1);
792 input_latch(&
input2, val & 1);
794 if (adr >= 0x4200 && adr < 0x4220) {
797 if (adr >= 0x4300 && adr < 0x4380) {
807 uint8_t bank = adr >> 16;
809 if ((bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) && adr < 0x8000) {
811 if (adr < 0x2000 || adr >= 0x6000)
813 if (adr < 0x4000 || adr >= 0x4200)
827 uint8_t rv =
Read(adr);
866 if (button < 0 || button > 11)
881 uint32_t version = 0;
884 return absl::FailedPreconditionError(
"Unsupported legacy state version");
917 return absl::InternalError(
"Failed while reading legacy state");
919 return absl::OkStatus();
923 std::ofstream file(path, std::ios::binary);
925 return absl::InternalError(
"Failed to open state file for writing");
927 if (!IsLittleEndianHost()) {
928 return absl::FailedPreconditionError(
929 "State serialization requires a little-endian host");
935 auto write_core_chunk = [&]() -> absl::Status {
936 std::ostringstream chunk(std::ios::binary);
965 return absl::InternalError(
"Failed to buffer core state");
967 return WriteChunk(file, MakeTag(
'S',
'N',
'E',
'S'), 1, chunk.str());
972 auto write_component = [&](uint32_t tag, uint32_t version,
973 auto&& writer) -> absl::Status {
974 std::ostringstream chunk(std::ios::binary);
977 return absl::InternalError(
978 absl::StrFormat(
"Failed to serialize chunk %08x", tag));
980 auto payload = chunk.str();
981 if (payload.size() > kMaxChunkSize) {
982 return absl::FailedPreconditionError(
983 "Serialized chunk exceeded maximum allowed size");
985 return WriteChunk(file, tag, version, payload);
995 return absl::OkStatus();
999 std::ifstream file(path, std::ios::binary);
1001 return absl::InternalError(
"Failed to open state file for reading");
1003 if (!IsLittleEndianHost()) {
1004 return absl::FailedPreconditionError(
1005 "State serialization requires a little-endian host");
1010 auto magic_status = ReadUint32LE(file, &magic);
1011 if (!magic_status.ok() || magic != kStateMagic) {
1017 uint32_t format_version = 0;
1019 if (format_version != kStateFormatVersion) {
1020 return absl::FailedPreconditionError(
"Unsupported state file format");
1023 auto load_core_chunk = [&](std::istream& stream) -> absl::Status {
1050 return absl::OkStatus();
1053 bool core_loaded =
false;
1054 bool cpu_loaded =
false;
1055 bool ppu_loaded =
false;
1056 bool apu_loaded =
false;
1058 while (file && file.peek() != EOF) {
1059 ChunkHeader header{};
1060 auto header_status = ReadChunkHeader(file, &header);
1061 if (!header_status.ok()) {
1062 return header_status;
1065 if (header.size > kMaxChunkSize) {
1066 return absl::FailedPreconditionError(
"State chunk too large");
1069 std::string payload(header.size,
'\0');
1070 auto read_status = ReadBytes(file, payload.data(), header.size);
1071 if (!read_status.ok()) {
1076 reinterpret_cast<const uint8_t*
>(payload.data()), payload.size());
1077 if (crc != header.crc32) {
1078 return absl::FailedPreconditionError(
"State chunk CRC mismatch");
1081 std::istringstream chunk_stream(payload, std::ios::binary);
1082 switch (header.tag) {
1083 case MakeTag(
'S',
'N',
'E',
'S'): {
1084 if (header.version != 1) {
1085 return absl::FailedPreconditionError(
"Unsupported SNES chunk version");
1087 auto status = load_core_chunk(chunk_stream);
1088 if (!status.ok())
return status;
1092 case MakeTag(
'C',
'P',
'U',
' '): {
1093 if (header.version != 1) {
1094 return absl::FailedPreconditionError(
"Unsupported CPU chunk version");
1097 if (!chunk_stream) {
1098 return absl::InternalError(
"Failed to load CPU chunk");
1103 case MakeTag(
'P',
'P',
'U',
' '): {
1104 if (header.version != 1) {
1105 return absl::FailedPreconditionError(
"Unsupported PPU chunk version");
1108 if (!chunk_stream) {
1109 return absl::InternalError(
"Failed to load PPU chunk");
1114 case MakeTag(
'A',
'P',
'U',
' '): {
1115 if (header.version != 1) {
1116 return absl::FailedPreconditionError(
"Unsupported APU chunk version");
1119 if (!chunk_stream) {
1120 return absl::InternalError(
"Failed to load APU chunk");
1131 if (!core_loaded || !cpu_loaded || !ppu_loaded || !apu_loaded) {
1132 return absl::FailedPreconditionError(
"Missing required chunks in state");
1134 return absl::OkStatus();
1138 int start = (recalc) ? 0x800000 : 0;
1140 for (
int i = start; i < 0x1000000; i++) {
void LoadState(std::istream &stream)
void set_handshake_tracker(debug::ApuHandshakeTracker *tracker)
void RunCycles(uint64_t cycles)
std::array< uint8_t, 4 > out_ports_
uint64_t GetCycles() const
std::array< uint8_t, 6 > in_ports_
void SaveState(std::ostream &stream)
void SaveState(std::ostream &stream)
void set_int_delay(bool delay)
void LoadState(std::istream &stream)
void Reset(bool hard=false)
auto open_bus() const -> uint8_t override
void set_h_pos(uint16_t value) override
void init_hdma_request() override
void set_v_pos(uint16_t value) override
auto v_pos() const -> uint16_t override
auto h_pos() const -> uint16_t override
void set_open_bus(uint8_t value) override
uint8_t cart_read(uint8_t bank, uint16_t adr)
void run_hdma_request() override
void cart_write(uint8_t bank, uint16_t adr, uint8_t val)
auto pal_timing() const -> bool override
void Initialize(const std::vector< uint8_t > &romData, bool verbose=false)
void SaveState(std::ostream &stream)
void Write(uint8_t adr, uint8_t val)
void PutPixels(uint8_t *pixel_data)
uint8_t Read(uint8_t adr, bool latch)
void LoadState(std::istream &stream)
void SetSamples(int16_t *sample_data, int wanted_samples)
uint8_t Rread(uint32_t adr)
void RunCycles(int cycles)
uint16_t port_auto_read_[4]
void WriteBBus(uint8_t adr, uint8_t val)
uint8_t CpuRead(uint32_t adr)
absl::Status saveState(const std::string &path)
void SetButtonState(int player, int button, bool pressed)
uint8_t Read(uint32_t adr)
std::vector< uint8_t > access_time
debug::ApuHandshakeTracker apu_handshake_tracker_
void Reset(bool hard=false)
uint8_t ReadReg(uint16_t adr)
void CpuIdle(bool waiting)
void WriteReg(uint16_t adr, uint8_t val)
void CpuWrite(uint32_t adr, uint8_t val)
uint16_t multiply_result_
uint32_t next_horiz_event
uint8_t ReadBBus(uint8_t adr)
void Write(uint32_t adr, uint8_t val)
void InitAccessTime(bool recalc)
void Init(const std::vector< uint8_t > &rom_data)
absl::Status LoadLegacyState(std::istream &file)
void SyncCycles(bool start, int sync_cycles)
std::vector< uint8_t > rom_data
absl::Status loadState(const std::string &path)
void SetPixels(uint8_t *pixel_data)
double apu_catchup_cycles_
int GetAccessTime(uint32_t adr)
void OnCpuPortWrite(uint8_t port, uint8_t value, uint32_t pc)
#define LOG_DEBUG(category, format,...)
#define LOG_INFO(category, format,...)
absl::Status WriteChunk(std::ostream &out, uint32_t tag, uint32_t version, const std::string &payload)
absl::Status ReadBytes(std::istream &in, void *data, size_t size)
absl::Status ReadChunkHeader(std::istream &in, ChunkHeader *header)
bool IsLittleEndianHost()
void input_latch(Input *input, bool value)
absl::Status WriteScalar(std::ostream &out, T value)
constexpr uint32_t kMaxChunkSize
constexpr uint32_t MakeTag(char a, char b, char c, char d)
absl::Status ReadScalar(std::istream &in, T *value)
absl::Status ReadUint32LE(std::istream &in, uint32_t *value)
absl::Status WriteBytes(std::ostream &out, const void *data, size_t size)
uint8_t input_read(Input *input)
constexpr uint32_t kStateFormatVersion
constexpr uint32_t kStateMagic
absl::Status WriteUint32LE(std::ostream &out, uint32_t value)
uint32_t CalculateCRC32(const uint8_t *data, size_t size)
void ResetDma(MemoryImpl *memory)
void WriteDma(MemoryImpl *memory, uint16_t adr, uint8_t val)
void HandleDma(Snes *snes, MemoryImpl *memory, int cpu_cycles)
uint8_t ReadDma(MemoryImpl *memory, uint16_t adr)
void StartDma(MemoryImpl *memory, uint8_t val, bool hdma)
#define RETURN_IF_ERROR(expr)