yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
snes.cc
Go to the documentation of this file.
1#include "app/emu/snes.h"
2
3#include <algorithm>
4#include <array>
5#include <cstdint>
6#include <cstring>
7#include <fstream>
8#include <sstream>
9#include <string>
10#include <type_traits>
11#include <vector>
12
13#include "absl/status/status.h"
14#include "absl/strings/str_format.h"
15#include "app/emu/audio/apu.h"
16#include "app/emu/memory/dma.h"
19#include "app/emu/video/ppu.h"
20#include "util/log.h"
21
22#define RETURN_IF_ERROR(expr) \
23 do { \
24 absl::Status _status = (expr); \
25 if (!_status.ok()) { \
26 return _status; \
27 } \
28 } while (0)
29
30namespace yaze {
31namespace emu {
32
33namespace {
34void input_latch(Input* input, bool value) {
35 input->latch_line_ = value;
36 if (input->latch_line_) {
37 input->latched_state_ = input->current_state_;
38 }
39}
40
41uint8_t input_read(Input* input) {
42 if (input->latch_line_)
43 input->latched_state_ = input->current_state_;
44
45 // Invert state for serial line: 1 (Pressed) -> 0, 0 (Released) -> 1
46 // This matches SNES hardware Active Low logic.
47 // Also ensures shifting in 0s results in 1s (Released) for bits 17+.
48 uint8_t ret = (~input->latched_state_) & 1;
49
50 input->latched_state_ >>= 1;
51 return ret;
52}
53
55 uint16_t test = 1;
56 return *reinterpret_cast<uint8_t*>(&test) == 1;
57}
58
59constexpr uint32_t kStateMagic = 0x59415A45; // 'YAZE'
60constexpr uint32_t kStateFormatVersion = 2;
61constexpr uint32_t kMaxChunkSize = 16 * 1024 * 1024; // 16MB safety cap
62
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);
66}
67
68absl::Status WriteBytes(std::ostream& out, const void* data, size_t size) {
69 out.write(reinterpret_cast<const char*>(data), size);
70 if (!out) {
71 return absl::InternalError("Failed to write bytes to state stream");
72 }
73 return absl::OkStatus();
74}
75
76absl::Status ReadBytes(std::istream& in, void* data, size_t size) {
77 in.read(reinterpret_cast<char*>(data), size);
78 if (!in) {
79 return absl::InternalError("Failed to read bytes from state stream");
80 }
81 return absl::OkStatus();
82}
83
84absl::Status WriteUint32LE(std::ostream& out, uint32_t value) {
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());
90}
91
92absl::Status ReadUint32LE(std::istream& in, uint32_t* value) {
93 std::array<uint8_t, 4> bytes{};
94 auto status = ReadBytes(in, bytes.data(), bytes.size());
95 if (!status.ok()) {
96 return status;
97 }
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();
103}
104
105template <typename T>
106absl::Status WriteScalar(std::ostream& out, T value) {
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());
112}
113
114template <typename T>
115absl::Status ReadScalar(std::istream& in, T* value) {
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());
120 if (!status.ok()) {
121 return status;
122 }
123 std::memcpy(value, buffer.data(), sizeof(T));
124 return absl::OkStatus();
125}
126
128 uint32_t tag;
129 uint32_t version;
130 uint32_t size;
131 uint32_t crc32;
132};
133
134absl::Status WriteChunk(std::ostream& out, uint32_t tag, uint32_t version,
135 const std::string& payload) {
136 if (payload.size() > kMaxChunkSize) {
137 return absl::FailedPreconditionError("Serialized chunk too large");
138 }
139 ChunkHeader header{
140 tag, version, static_cast<uint32_t>(payload.size()),
141 render::CalculateCRC32(reinterpret_cast<const uint8_t*>(payload.data()),
142 payload.size())};
143
144 RETURN_IF_ERROR(WriteUint32LE(out, header.tag));
145 RETURN_IF_ERROR(WriteUint32LE(out, header.version));
146 RETURN_IF_ERROR(WriteUint32LE(out, header.size));
147 RETURN_IF_ERROR(WriteUint32LE(out, header.crc32));
148 RETURN_IF_ERROR(WriteBytes(out, payload.data(), payload.size()));
149 return absl::OkStatus();
150}
151
152absl::Status ReadChunkHeader(std::istream& in, ChunkHeader* header) {
153 RETURN_IF_ERROR(ReadUint32LE(in, &header->tag));
154 RETURN_IF_ERROR(ReadUint32LE(in, &header->version));
155 RETURN_IF_ERROR(ReadUint32LE(in, &header->size));
156 RETURN_IF_ERROR(ReadUint32LE(in, &header->crc32));
157 return absl::OkStatus();
158}
159
160} // namespace
161
162void Snes::Init(const std::vector<uint8_t>& rom_data) {
163 LOG_DEBUG("SNES", "Initializing emulator with ROM size %zu bytes",
164 rom_data.size());
165
166 // Initialize the CPU, PPU, and APU
167 ppu_.Init();
168 apu_.Init();
169
170 // Connect handshake tracker to APU for debugging
172
173 // Load the ROM into memory and set up the memory mapping
175 Reset(true);
176
177 running_ = true;
178 LOG_DEBUG("SNES", "Emulator initialization complete");
179}
180
181void Snes::Reset(bool hard) {
182 LOG_DEBUG("SNES", "Reset called (hard=%d)", hard);
183 cpu_.Reset(hard);
184 apu_.Reset();
185 ppu_.Reset();
187 input1.latch_line_ = false;
188 input2.latch_line_ = false;
189 input1.current_state_ = 0; // Clear current button states
190 input2.current_state_ = 0; // Clear current button states
195 if (hard)
196 memset(ram, 0, sizeof(ram));
197 ram_adr_ = 0;
200 frames_ = 0;
201 cycles_ = 0;
202 sync_cycle_ = 0;
204 h_irq_enabled_ = false;
205 v_irq_enabled_ = false;
206 nmi_enabled_ = false;
207 h_timer_ = 0x1ff * 4;
208 v_timer_ = 0x1ff;
209 in_nmi_ = false;
210 irq_condition_ = false;
211 in_irq_ = false;
212 in_vblank_ = false;
213 memset(port_auto_read_, 0, sizeof(port_auto_read_));
214 auto_joy_read_ = false;
215 auto_joy_timer_ = 0;
216 // WRIO ($4201) resets to $FF. Bit 7 high lets an SLHV ($2137) read latch
217 // the H/V counters. ALttP never writes WRIO and polls OPVCT after SLHV
218 // (IrisSpotlight_ConfigureTable), so a low bit 7 hung every iris wipe.
219 ppu_latch_ = true;
220 multiply_a_ = 0xff;
221 multiply_result_ = 0xFE01;
222 divide_a_ = 0xffFF;
223 divide_result_ = 0x101;
224 fast_mem_ = false;
226 next_horiz_event = 16;
227 InitAccessTime(false);
228 LOG_DEBUG("SNES", "Reset complete - CPU will start at $%02X:%04X", cpu_.PB,
229 cpu_.PC);
230}
231
233 while (in_vblank_) {
234 cpu_.RunOpcode();
235 }
236
237 uint32_t frame = frames_;
238
239 while (!in_vblank_ && frame == frames_) {
240 cpu_.RunOpcode();
241 }
242}
243
245 // Audio-focused frame execution: runs CPU+APU but skips PPU rendering
246 // This maintains CPU-APU communication timing while reducing overhead
247 // Used by MusicEditor for authentic audio playback
248 // Note: PPU registers are still writable, but rendering logic (StartLine/RunLine)
249 // in RunCycle() is skipped when audio_only_mode_ is true.
250
251 audio_only_mode_ = true;
252
253 // Run through vblank if we're in it
254 while (in_vblank_) {
255 cpu_.RunOpcode();
256 }
257
258 uint32_t frame = frames_;
259
260 // Run until next vblank
261 while (!in_vblank_ && frame == frames_) {
262 cpu_.RunOpcode();
263 }
264
265 audio_only_mode_ = false;
266}
267
269 // Bring APU up to the same master cycle count since last catch-up.
270 // cycles_ is monotonically increasing in RunCycle().
272}
273
275 // IMPORTANT: Clear and repopulate auto-read data
276 // This data persists until the next call, allowing NMI to read it
277 memset(port_auto_read_, 0, sizeof(port_auto_read_));
278
279 // Populate port_auto_read_ non-destructively by reading current_state_ directly.
280 // The SNES shifts out 16 bits: B, Y, Select, Start, Up, Down, Left, Right, A, X, L, R, 0, 0, 0, 0
281 // This corresponds to bits 0-11 of our input state.
282 // Note: This assumes current_state_ matches the SNES controller bit order:
283 // Bit 0: B, 1: Y, 2: Select, 3: Start, 4: Up, 5: Down, 6: Left, 7: Right, 8: A, 9: X, 10: L, 11: R
284 uint16_t latched1 = input1.current_state_;
285 uint16_t latched2 = input2.current_state_;
286 for (int i = 0; i < 16; i++) {
287 // Read bit i from current state (0 for bits >= 12)
288 uint8_t val1 = (latched1 >> i) & 1;
289 uint8_t val2 = (latched2 >> i) & 1;
290
291 // Store in port_auto_read_ (Big Endian format for registers $4218-$421F)
292 // port_auto_read_[0/1] gets bits 0-15 shifted into position
293 port_auto_read_[0] |= (val1 << (15 - i));
294 port_auto_read_[1] |= (val2 << (15 - i));
295 // port_auto_read_[2/3] remain 0 as standard controllers don't use them
296 }
297
298 // Debug: Log input state when buttons are pressed
299 static int input_log_count = 0;
300 if (latched1 != 0 && input_log_count++ < 50) {
301 LOG_INFO("Input", "current_state=0x%04X -> $4218=0x%02X $4219=0x%02X",
302 latched1, port_auto_read_[0] & 0xFF, port_auto_read_[0] >> 8);
303 }
304
305 // Store previous state for edge detection
306 // Do this here instead of after a destructive latch sequence
309
310 // Make auto-joypad data immediately available; real hardware has a delay,
311 // but our current timing can leave it busy when NMI reads $4218/4219.
312 auto_joy_timer_ = 0;
313}
314
316 cycles_ += 2;
317
318 // check for h/v timer irq's
319 bool condition = ((v_irq_enabled_ || h_irq_enabled_) &&
322
323 if (!irq_condition_ && condition) {
324 in_irq_ = true;
325 cpu_.SetIrq(true);
326 }
327 irq_condition_ = condition;
328
329 // increment position; must come after irq checks! (hagane, cybernator)
331
332 // handle positional stuff
333 if (memory_.h_pos() == next_horiz_event) {
334 switch (memory_.h_pos()) {
335 case 16: {
336 next_horiz_event = 512;
337 if (memory_.v_pos() == 0)
339
340 // Start PPU line rendering (setup for JIT rendering)
341 // Skip in audio-only mode for performance
342 if (!audio_only_mode_ && !in_vblank_ && memory_.v_pos() > 0)
344 } break;
345 case 512: {
346 next_horiz_event = 1104;
347 // Render the line halfway of the screen for better compatibility
348 // Using CatchUp instead of RunLine for progressive rendering
349 // Skip in audio-only mode for performance
350 if (!audio_only_mode_ && !in_vblank_ && memory_.v_pos() > 0)
351 ppu_.CatchUp(512);
352 } break;
353 case 1104: {
354 // Finish rendering the visible line
355 // Skip in audio-only mode for performance
356 if (!audio_only_mode_ && !in_vblank_ && memory_.v_pos() > 0)
357 ppu_.CatchUp(1104);
358
359 if (!in_vblank_)
361 if (!memory_.pal_timing()) {
362 // line 240 of odd frame with no interlace is 4 cycles shorter
363 next_horiz_event = (memory_.v_pos() == 240 && !ppu_.even_frame &&
365 ? 1360
366 : 1364;
367 } else {
368 // line 311 of odd frame with interlace is 4 cycles longer
371 ? 1364
372 : 1368;
373 }
374 } break;
375 case 1360:
376 case 1364:
377 case 1368: { // this is the end (of the h-line)
378 next_horiz_event = 16;
379
382 if (!memory_.pal_timing()) {
383 // even interlace frame is 263 lines
384 if ((memory_.v_pos() == 262 &&
386 memory_.v_pos() == 263) {
388 frames_++;
389 static int frame_log = 0;
390 if (++frame_log % 60 == 0)
391 LOG_INFO("SNES", "Frames incremented 60 times");
392 }
393 } else {
394 // even interlace frame is 313 lines
395 if ((memory_.v_pos() == 312 &&
397 memory_.v_pos() == 313) {
399 frames_++;
400 static int frame_log_pal = 0;
401 if (++frame_log_pal % 60 == 0)
402 LOG_INFO("SNES", "Frames (PAL) incremented 60 times");
403 }
404 }
405
406 // end of hblank, do most memory_.v_pos()-tests
407 bool starting_vblank = false;
408 if (memory_.v_pos() == 0) {
409 // end of vblank
410 static int vblank_end_count = 0;
411 if (vblank_end_count++ < 10) {
412 LOG_DEBUG(
413 "SNES",
414 "VBlank END - v_pos=0, setting in_vblank_=false at frame %d",
415 frames_);
416 }
417 in_vblank_ = false;
418 in_nmi_ = false;
420 } else if (memory_.v_pos() == 225) {
421 // ask the ppu if we start vblank now or at memory_.v_pos() 240
422 // (overscan)
423 starting_vblank = !ppu_.CheckOverscan();
424 } else if (memory_.v_pos() == 240) {
425 // if we are not yet in vblank, we had an overscan frame, set
426 // starting_vblank
427 if (!in_vblank_)
428 starting_vblank = true;
429 }
430 if (starting_vblank) {
431 // catch up the apu at end of emulated frame (we end frame @ start of
432 // vblank)
433 CatchUpApu();
434 // IMPORTANT: This is the ONLY location where NewFrame() should be called
435 // during frame execution. It marks the DSP sample boundary at vblank start,
436 // after CatchUpApu() has synced the APU. Do NOT call NewFrame() from
437 // Emulator::RunAudioFrame() - that causes incorrect frame boundary timing
438 // and results in audio playing at wrong speed (1.5x due to 48000/32040 ratio).
439 // This also handles games where DMA extends past vblank (Megaman X2 titlescreen,
440 // Tales of Phantasia demo, Actraiser 2 fade-in).
441 apu_.dsp().NewFrame();
442 // we are starting vblank
444
445 static int vblank_start_count = 0;
446 if (vblank_start_count++ < 10) {
447 LOG_DEBUG(
448 "SNES",
449 "VBlank START - v_pos=%d, setting in_vblank_=true at frame %d",
451 }
452
453 in_vblank_ = true;
454 in_nmi_ = true;
455 if (auto_joy_read_) {
456 // TODO: this starts a little after start of vblank
457 auto_joy_timer_ = 4224;
458 HandleInput();
459 }
460
461 if (nmi_enabled_) {
462 cpu_.Nmi();
463 }
464 }
465 } break;
466 }
467 }
468 // handle auto_joy_read_-timer
469 if (auto_joy_timer_ > 0)
470 auto_joy_timer_ -= 2;
471}
472
473void Snes::RunCycles(int cycles) {
474 if (memory_.h_pos() + cycles >= 536 && memory_.h_pos() < 536) {
475 // if we go past 536, add 40 cycles for dram refersh
476 cycles += 40;
477 }
478 for (int i = 0; i < cycles; i += 2) {
479 RunCycle();
480 }
481}
482
483void Snes::SyncCycles(bool start, int sync_cycles) {
484 int count = 0;
485 if (start) {
487 count = sync_cycles - (cycles_ % sync_cycles);
488 } else {
489 count = sync_cycles - ((cycles_ - sync_cycle_) % sync_cycles);
490 }
491 RunCycles(count);
492}
493
494uint8_t Snes::ReadBBus(uint8_t adr) {
495 if (adr < 0x40) {
496 return ppu_.Read(adr, ppu_latch_);
497 }
498 if (adr < 0x80) {
499 CatchUpApu(); // catch up the apu before reading
500 uint8_t val = apu_.out_ports_[adr & 0x3];
501 // Log port reads when value changes or during critical phase
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) {
507 LOG_DEBUG("SNES",
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)
513 last_f4 = val;
514 if ((adr & 0x3) == 1)
515 last_f5 = val;
516 }
517 return val;
518 }
519 if (adr == 0x80) {
520 uint8_t ret = ram[ram_adr_++];
521 ram_adr_ &= 0x1ffff;
522 return ret;
523 }
524 return memory_.open_bus();
525}
526
527uint8_t Snes::ReadReg(uint16_t adr) {
528 switch (adr) {
529 case 0x4210: {
530 uint8_t val = 0x2; // CPU version (4 bit)
531 val |= in_nmi_ << 7;
532 in_nmi_ = false;
533 return val | (memory_.open_bus() & 0x70);
534 }
535 case 0x4211: {
536 uint8_t val = in_irq_ << 7;
537 in_irq_ = false;
538 cpu_.SetIrq(false);
539 return val | (memory_.open_bus() & 0x7f);
540 }
541 case 0x4212: {
542 uint8_t val = (auto_joy_timer_ > 0);
543 val |= (memory_.h_pos() < 4 || memory_.h_pos() >= 1096) << 6;
544 val |= in_vblank_ << 7;
545 return val | (memory_.open_bus() & 0x3e);
546 }
547 case 0x4213: {
548 return ppu_latch_ << 7; // IO-port
549 }
550 case 0x4214: {
551 return divide_result_ & 0xff;
552 }
553 case 0x4215: {
554 return divide_result_ >> 8;
555 }
556 case 0x4216: {
557 return multiply_result_ & 0xff;
558 }
559 case 0x4217: {
560 return multiply_result_ >> 8;
561 }
562 case 0x4218:
563 case 0x421a:
564 case 0x421c:
565 case 0x421e: {
566 // If transfer is still in progress, data is not yet valid
567 if (auto_joy_timer_ > 0) {
568 static int zero_return_count = 0;
569 if (zero_return_count++ < 50) {
570 LOG_WARN("SNES",
571 "Reading $%04X while auto_joy_timer_=%d, returning 0!", adr,
573 }
574 return 0;
575 }
576 uint8_t result = port_auto_read_[(adr - 0x4218) / 2] & 0xff;
577 return result;
578 }
579 case 0x4219:
580 case 0x421b:
581 case 0x421d:
582 case 0x421f: {
583 // If transfer is still in progress, data is not yet valid
584 if (auto_joy_timer_ > 0)
585 return 0;
586 uint8_t result = port_auto_read_[(adr - 0x4219) / 2] >> 8;
587
588 return result;
589 }
590 default: {
591 return memory_.open_bus();
592 }
593 }
594}
595
596uint8_t Snes::Rread(uint32_t adr) {
597 uint8_t bank = adr >> 16;
598 adr &= 0xffff;
599 if (bank == 0x7e || bank == 0x7f) {
600 return ram[((bank & 1) << 16) | adr]; // ram
601 }
602 if (bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
603 if (adr < 0x2000) {
604 return ram[adr]; // ram mirror
605 }
606 if (adr >= 0x2100 && adr < 0x2200) {
607 return ReadBBus(adr & 0xff); // B-bus
608 }
609 if (adr == 0x4016) {
610 uint8_t result = input_read(&input1) | (memory_.open_bus() & 0xfc);
611
612 return result;
613 }
614 if (adr == 0x4017) {
615 return input_read(&input2) | (memory_.open_bus() & 0xe0) | 0x1c;
616 }
617 if (adr >= 0x4200 && adr < 0x4220) {
618
619 return ReadReg(adr); // internal registers
620 }
621 if (adr >= 0x4300 && adr < 0x4380) {
622 return ReadDma(&memory_, adr); // dma registers
623 }
624 }
625 // read from cart
626 return memory_.cart_read(bank, adr);
627}
628
629uint8_t Snes::Read(uint32_t adr) {
630 uint8_t val = Rread(adr);
632 return val;
633}
634
635void Snes::WriteBBus(uint8_t adr, uint8_t val) {
636 if (adr < 0x40) {
637 // PPU Register write - catch up rendering first to ensure mid-scanline effects work
638 // Only needed if we are in the visible portion of a visible scanline
639 // Skip in audio-only mode for performance (no video output needed)
640 if (!audio_only_mode_ && !in_vblank_ && memory_.v_pos() > 0 &&
641 memory_.h_pos() < 1100) {
643 }
644 ppu_.Write(adr, val);
645 return;
646 }
647 if (adr < 0x80) {
648 CatchUpApu(); // catch up the apu before writing
649 apu_.in_ports_[adr & 0x3] = val;
650
651 // Track CPU port writes for handshake debugging
652 uint32_t full_pc = (static_cast<uint32_t>(cpu_.PB) << 16) | cpu_.PC;
653 apu_handshake_tracker_.OnCpuPortWrite(adr & 0x3, val, full_pc);
654
655 // NOTE: Auto-reset disabled - relying on complete IPL ROM with counter
656 // protocol The IPL ROM will handle multi-upload sequences via its transfer
657 // loop
658
659 return;
660 }
661 switch (adr) {
662 case 0x80: {
663 ram[ram_adr_++] = val;
664 ram_adr_ &= 0x1ffff;
665 break;
666 }
667 case 0x81: {
668 ram_adr_ = (ram_adr_ & 0x1ff00) | val;
669 break;
670 }
671 case 0x82: {
672 ram_adr_ = (ram_adr_ & 0x100ff) | (val << 8);
673 break;
674 }
675 case 0x83: {
676 ram_adr_ = (ram_adr_ & 0x0ffff) | ((val & 1) << 16);
677 break;
678 }
679 }
680}
681
682void Snes::WriteReg(uint16_t adr, uint8_t val) {
683 switch (adr) {
684 case 0x4200: {
685 // Log ALL writes to $4200 unconditionally
686
687 auto_joy_read_ = val & 0x1;
688 if (!auto_joy_read_)
689 auto_joy_timer_ = 0;
690
691 // Debug: Log when auto-joy-read is enabled/disabled
692
693 h_irq_enabled_ = val & 0x10;
694 v_irq_enabled_ = val & 0x20;
696 in_irq_ = false;
697 cpu_.SetIrq(false);
698 }
699 // if nmi is enabled while in_nmi_ is still set, immediately generate nmi
700 if (!nmi_enabled_ && (val & 0x80) && in_nmi_) {
701 cpu_.Nmi();
702 }
703 bool old_nmi = nmi_enabled_;
704 nmi_enabled_ = val & 0x80;
705
706 cpu_.set_int_delay(true);
707 break;
708 }
709 case 0x4201: {
710 if (!(val & 0x80) && ppu_latch_) {
711 // latch the ppu
712 ppu_.LatchHV();
713 }
714 ppu_latch_ = val & 0x80;
715 break;
716 }
717 case 0x4202: {
718 multiply_a_ = val;
719 break;
720 }
721 case 0x4203: {
723 break;
724 }
725 case 0x4204: {
726 divide_a_ = (divide_a_ & 0xff00) | val;
727 break;
728 }
729 case 0x4205: {
730 divide_a_ = (divide_a_ & 0x00ff) | (val << 8);
731 break;
732 }
733 case 0x4206: {
734 if (val == 0) {
735 divide_result_ = 0xffff;
737 } else {
740 }
741 break;
742 }
743 case 0x4207: {
744 h_timer_ = (h_timer_ & 0x100) | val;
745 break;
746 }
747 case 0x4208: {
748 h_timer_ = (h_timer_ & 0x0ff) | ((val & 1) << 8);
749 break;
750 }
751 case 0x4209: {
752 v_timer_ = (v_timer_ & 0x100) | val;
753 break;
754 }
755 case 0x420a: {
756 v_timer_ = (v_timer_ & 0x0ff) | ((val & 1) << 8);
757 break;
758 }
759 case 0x420b: {
760 StartDma(&memory_, val, false);
761 break;
762 }
763 case 0x420c: {
764 StartDma(&memory_, val, true);
765 break;
766 }
767 case 0x420d: {
768 fast_mem_ = val & 0x1;
769 break;
770 }
771 default: {
772 break;
773 }
774 }
775}
776
777void Snes::Write(uint32_t adr, uint8_t val) {
779
780 uint8_t bank = adr >> 16;
781 adr &= 0xffff;
782 if (bank == 0x7e || bank == 0x7f) {
783
784 ram[((bank & 1) << 16) | adr] = val; // ram
785 }
786 if (bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
787 if (adr < 0x2000) {
788
789 ram[adr] = val; // ram mirror
790 }
791 if (adr >= 0x2100 && adr < 0x2200) {
792 WriteBBus(adr & 0xff, val); // B-bus
793 }
794 if (adr == 0x4016) {
795
796 input_latch(&input1, val & 1); // input latch
797 input_latch(&input2, val & 1);
798 }
799 if (adr >= 0x4200 && adr < 0x4220) {
800 WriteReg(adr, val); // internal registers
801 }
802 if (adr >= 0x4300 && adr < 0x4380) {
803 WriteDma(&memory_, adr, val); // dma registers
804 }
805 }
806
807 // write to cart
808 memory_.cart_write(bank, adr, val);
809}
810
811int Snes::GetAccessTime(uint32_t adr) {
812 uint8_t bank = adr >> 16;
813 adr &= 0xffff;
814 if ((bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) && adr < 0x8000) {
815 // 00-3f,80-bf:0-7fff
816 if (adr < 0x2000 || adr >= 0x6000)
817 return 8; // 0-1fff, 6000-7fff
818 if (adr < 0x4000 || adr >= 0x4200)
819 return 6; // 2000-3fff, 4200-5fff
820 return 12; // 4000-41ff
821 }
822 // 40-7f,co-ff:0000-ffff, 00-3f,80-bf:8000-ffff
823 return (fast_mem_ && bank >= 0x80) ? 6
824 : 8; // depends on setting in banks 80+
825}
826
827uint8_t Snes::CpuRead(uint32_t adr) {
828 cpu_.set_int_delay(false);
829 const int cycles = access_time[adr] - 4;
830 HandleDma(this, &memory_, cycles);
831 RunCycles(cycles);
832 uint8_t rv = Read(adr);
833 HandleDma(this, &memory_, 4);
834 RunCycles(4);
835 return rv;
836}
837
838void Snes::CpuWrite(uint32_t adr, uint8_t val) {
839 cpu_.set_int_delay(false);
840 const int cycles = access_time[adr];
841 HandleDma(this, &memory_, cycles);
842 RunCycles(cycles);
843 Write(adr, val);
844}
845
846void Snes::CpuIdle(bool waiting) {
847 cpu_.set_int_delay(false);
848 HandleDma(this, &memory_, 6);
849 RunCycles(6);
850}
851
852void Snes::SetSamples(int16_t* sample_data, int wanted_samples) {
853 apu_.dsp().GetSamples(sample_data, wanted_samples, memory_.pal_timing());
854}
855
856void Snes::SetPixels(uint8_t* pixel_data) {
857 ppu_.PutPixels(pixel_data);
858}
859
860void Snes::SetButtonState(int player, int button, bool pressed) {
861 // Select the appropriate input based on player number
862 // Select the appropriate input based on player number
863 // Player 0 = Controller 1, Player 1 = Controller 2
864 Input* input = (player == 0) ? &input1 : &input2;
865
866 // SNES controller button mapping (standard layout)
867 // Bit 0: B, Bit 1: Y, Bit 2: Select, Bit 3: Start
868 // Bit 4: Up, Bit 5: Down, Bit 6: Left, Bit 7: Right
869 // Bit 8: A, Bit 9: X, Bit 10: L, Bit 11: R
870
871 if (button < 0 || button > 11)
872 return; // Validate button range
873
874 uint16_t old_state = input->current_state_;
875
876 if (pressed) {
877 // Set the button bit
878 input->current_state_ |= (1 << button);
879 } else {
880 // Clear the button bit
881 input->current_state_ &= ~(1 << button);
882 }
883}
884
885absl::Status Snes::LoadLegacyState(std::istream& file) {
886 uint32_t version = 0;
887 RETURN_IF_ERROR(ReadUint32LE(file, &version));
888 if (version != 1) {
889 return absl::FailedPreconditionError("Unsupported legacy state version");
890 }
891
892 RETURN_IF_ERROR(ReadBytes(file, ram, sizeof(ram)));
893 RETURN_IF_ERROR(ReadScalar(file, &ram_adr_));
894 RETURN_IF_ERROR(ReadScalar(file, &cycles_));
895 RETURN_IF_ERROR(ReadScalar(file, &sync_cycle_));
896 RETURN_IF_ERROR(ReadScalar(file, &apu_catchup_cycles_));
897 RETURN_IF_ERROR(ReadScalar(file, &h_irq_enabled_));
898 RETURN_IF_ERROR(ReadScalar(file, &v_irq_enabled_));
899 RETURN_IF_ERROR(ReadScalar(file, &nmi_enabled_));
900 RETURN_IF_ERROR(ReadScalar(file, &h_timer_));
901 RETURN_IF_ERROR(ReadScalar(file, &v_timer_));
902 RETURN_IF_ERROR(ReadScalar(file, &in_nmi_));
903 RETURN_IF_ERROR(ReadScalar(file, &irq_condition_));
904 RETURN_IF_ERROR(ReadScalar(file, &in_irq_));
905 RETURN_IF_ERROR(ReadScalar(file, &in_vblank_));
906 RETURN_IF_ERROR(ReadBytes(file, port_auto_read_, sizeof(port_auto_read_)));
907 RETURN_IF_ERROR(ReadScalar(file, &auto_joy_read_));
908 RETURN_IF_ERROR(ReadScalar(file, &auto_joy_timer_));
909 RETURN_IF_ERROR(ReadScalar(file, &ppu_latch_));
910 RETURN_IF_ERROR(ReadScalar(file, &multiply_a_));
911 RETURN_IF_ERROR(ReadScalar(file, &multiply_result_));
912 RETURN_IF_ERROR(ReadScalar(file, &divide_a_));
913 RETURN_IF_ERROR(ReadScalar(file, &divide_result_));
914 RETURN_IF_ERROR(ReadScalar(file, &fast_mem_));
915 RETURN_IF_ERROR(ReadScalar(file, &next_horiz_event));
916
917 cpu_.LoadState(file);
918 ppu_.LoadState(file);
919 apu_.LoadState(file);
920
921 if (!file) {
922 return absl::InternalError("Failed while reading legacy state");
923 }
924 return absl::OkStatus();
925}
926
927absl::Status Snes::saveState(const std::string& path) {
928 std::ofstream file(path, std::ios::binary);
929 if (!file) {
930 return absl::InternalError("Failed to open state file for writing");
931 }
932 if (!IsLittleEndianHost()) {
933 return absl::FailedPreconditionError(
934 "State serialization requires a little-endian host");
935 }
936
937 RETURN_IF_ERROR(WriteUint32LE(file, kStateMagic));
938 RETURN_IF_ERROR(WriteUint32LE(file, kStateFormatVersion));
939
940 auto write_core_chunk = [&]() -> absl::Status {
941 std::ostringstream chunk(std::ios::binary);
942 RETURN_IF_ERROR(WriteBytes(chunk, ram, sizeof(ram)));
943 RETURN_IF_ERROR(WriteScalar(chunk, ram_adr_));
944 RETURN_IF_ERROR(WriteScalar(chunk, cycles_));
945 RETURN_IF_ERROR(WriteScalar(chunk, sync_cycle_));
946 RETURN_IF_ERROR(WriteScalar(chunk, apu_catchup_cycles_));
947 RETURN_IF_ERROR(WriteScalar(chunk, h_irq_enabled_));
948 RETURN_IF_ERROR(WriteScalar(chunk, v_irq_enabled_));
949 RETURN_IF_ERROR(WriteScalar(chunk, nmi_enabled_));
950 RETURN_IF_ERROR(WriteScalar(chunk, h_timer_));
951 RETURN_IF_ERROR(WriteScalar(chunk, v_timer_));
952 RETURN_IF_ERROR(WriteScalar(chunk, in_nmi_));
953 RETURN_IF_ERROR(WriteScalar(chunk, irq_condition_));
954 RETURN_IF_ERROR(WriteScalar(chunk, in_irq_));
955 RETURN_IF_ERROR(WriteScalar(chunk, in_vblank_));
956 for (const auto val : port_auto_read_) {
957 RETURN_IF_ERROR(WriteScalar(chunk, val));
958 }
959 RETURN_IF_ERROR(WriteScalar(chunk, auto_joy_read_));
960 RETURN_IF_ERROR(WriteScalar(chunk, auto_joy_timer_));
961 RETURN_IF_ERROR(WriteScalar(chunk, ppu_latch_));
962 RETURN_IF_ERROR(WriteScalar(chunk, multiply_a_));
963 RETURN_IF_ERROR(WriteScalar(chunk, multiply_result_));
964 RETURN_IF_ERROR(WriteScalar(chunk, divide_a_));
965 RETURN_IF_ERROR(WriteScalar(chunk, divide_result_));
966 RETURN_IF_ERROR(WriteScalar(chunk, fast_mem_));
967 RETURN_IF_ERROR(WriteScalar(chunk, next_horiz_event));
968
969 if (!chunk) {
970 return absl::InternalError("Failed to buffer core state");
971 }
972 return WriteChunk(file, MakeTag('S', 'N', 'E', 'S'), 1, chunk.str());
973 };
974
975 RETURN_IF_ERROR(write_core_chunk());
976
977 auto write_component = [&](uint32_t tag, uint32_t version,
978 auto&& writer) -> absl::Status {
979 std::ostringstream chunk(std::ios::binary);
980 writer(chunk);
981 if (!chunk) {
982 return absl::InternalError(
983 absl::StrFormat("Failed to serialize chunk %08x", tag));
984 }
985 auto payload = chunk.str();
986 if (payload.size() > kMaxChunkSize) {
987 return absl::FailedPreconditionError(
988 "Serialized chunk exceeded maximum allowed size");
989 }
990 return WriteChunk(file, tag, version, payload);
991 };
992
994 write_component(MakeTag('C', 'P', 'U', ' '), 1,
995 [&](std::ostream& out) { cpu_.SaveState(out); }));
997 write_component(MakeTag('P', 'P', 'U', ' '), 1,
998 [&](std::ostream& out) { ppu_.SaveState(out); }));
1000 write_component(MakeTag('A', 'P', 'U', ' '), 1,
1001 [&](std::ostream& out) { apu_.SaveState(out); }));
1002
1003 return absl::OkStatus();
1004}
1005
1006absl::Status Snes::loadState(const std::string& path) {
1007 std::ifstream file(path, std::ios::binary);
1008 if (!file) {
1009 return absl::InternalError("Failed to open state file for reading");
1010 }
1011 if (!IsLittleEndianHost()) {
1012 return absl::FailedPreconditionError(
1013 "State serialization requires a little-endian host");
1014 }
1015
1016 // Peek to determine format
1017 uint32_t magic = 0;
1018 auto magic_status = ReadUint32LE(file, &magic);
1019 if (!magic_status.ok() || magic != kStateMagic) {
1020 file.clear();
1021 file.seekg(0);
1022 return LoadLegacyState(file);
1023 }
1024
1025 uint32_t format_version = 0;
1026 RETURN_IF_ERROR(ReadUint32LE(file, &format_version));
1027 if (format_version != kStateFormatVersion) {
1028 return absl::FailedPreconditionError("Unsupported state file format");
1029 }
1030
1031 auto load_core_chunk = [&](std::istream& stream) -> absl::Status {
1032 RETURN_IF_ERROR(ReadBytes(stream, ram, sizeof(ram)));
1033 RETURN_IF_ERROR(ReadScalar(stream, &ram_adr_));
1034 RETURN_IF_ERROR(ReadScalar(stream, &cycles_));
1035 RETURN_IF_ERROR(ReadScalar(stream, &sync_cycle_));
1036 RETURN_IF_ERROR(ReadScalar(stream, &apu_catchup_cycles_));
1037 RETURN_IF_ERROR(ReadScalar(stream, &h_irq_enabled_));
1038 RETURN_IF_ERROR(ReadScalar(stream, &v_irq_enabled_));
1039 RETURN_IF_ERROR(ReadScalar(stream, &nmi_enabled_));
1040 RETURN_IF_ERROR(ReadScalar(stream, &h_timer_));
1041 RETURN_IF_ERROR(ReadScalar(stream, &v_timer_));
1042 RETURN_IF_ERROR(ReadScalar(stream, &in_nmi_));
1043 RETURN_IF_ERROR(ReadScalar(stream, &irq_condition_));
1044 RETURN_IF_ERROR(ReadScalar(stream, &in_irq_));
1045 RETURN_IF_ERROR(ReadScalar(stream, &in_vblank_));
1046 for (auto& val : port_auto_read_) {
1047 RETURN_IF_ERROR(ReadScalar(stream, &val));
1048 }
1049 RETURN_IF_ERROR(ReadScalar(stream, &auto_joy_read_));
1050 RETURN_IF_ERROR(ReadScalar(stream, &auto_joy_timer_));
1051 RETURN_IF_ERROR(ReadScalar(stream, &ppu_latch_));
1052 RETURN_IF_ERROR(ReadScalar(stream, &multiply_a_));
1053 RETURN_IF_ERROR(ReadScalar(stream, &multiply_result_));
1054 RETURN_IF_ERROR(ReadScalar(stream, &divide_a_));
1055 RETURN_IF_ERROR(ReadScalar(stream, &divide_result_));
1056 RETURN_IF_ERROR(ReadScalar(stream, &fast_mem_));
1057 RETURN_IF_ERROR(ReadScalar(stream, &next_horiz_event));
1058 return absl::OkStatus();
1059 };
1060
1061 bool core_loaded = false;
1062 bool cpu_loaded = false;
1063 bool ppu_loaded = false;
1064 bool apu_loaded = false;
1065
1066 while (file && file.peek() != EOF) {
1067 ChunkHeader header{};
1068 auto header_status = ReadChunkHeader(file, &header);
1069 if (!header_status.ok()) {
1070 return header_status;
1071 }
1072
1073 if (header.size > kMaxChunkSize) {
1074 return absl::FailedPreconditionError("State chunk too large");
1075 }
1076
1077 std::string payload(header.size, '\0');
1078 auto read_status = ReadBytes(file, payload.data(), header.size);
1079 if (!read_status.ok()) {
1080 return read_status;
1081 }
1082
1083 uint32_t crc = render::CalculateCRC32(
1084 reinterpret_cast<const uint8_t*>(payload.data()), payload.size());
1085 if (crc != header.crc32) {
1086 return absl::FailedPreconditionError("State chunk CRC mismatch");
1087 }
1088
1089 std::istringstream chunk_stream(payload, std::ios::binary);
1090 switch (header.tag) {
1091 case MakeTag('S', 'N', 'E', 'S'): {
1092 if (header.version != 1) {
1093 return absl::FailedPreconditionError(
1094 "Unsupported SNES chunk version");
1095 }
1096 auto status = load_core_chunk(chunk_stream);
1097 if (!status.ok())
1098 return status;
1099 core_loaded = true;
1100 break;
1101 }
1102 case MakeTag('C', 'P', 'U', ' '): {
1103 if (header.version != 1) {
1104 return absl::FailedPreconditionError("Unsupported CPU chunk version");
1105 }
1106 cpu_.LoadState(chunk_stream);
1107 if (!chunk_stream) {
1108 return absl::InternalError("Failed to load CPU chunk");
1109 }
1110 cpu_loaded = true;
1111 break;
1112 }
1113 case MakeTag('P', 'P', 'U', ' '): {
1114 if (header.version != 1) {
1115 return absl::FailedPreconditionError("Unsupported PPU chunk version");
1116 }
1117 ppu_.LoadState(chunk_stream);
1118 if (!chunk_stream) {
1119 return absl::InternalError("Failed to load PPU chunk");
1120 }
1121 ppu_loaded = true;
1122 break;
1123 }
1124 case MakeTag('A', 'P', 'U', ' '): {
1125 if (header.version != 1) {
1126 return absl::FailedPreconditionError("Unsupported APU chunk version");
1127 }
1128 apu_.LoadState(chunk_stream);
1129 if (!chunk_stream) {
1130 return absl::InternalError("Failed to load APU chunk");
1131 }
1132 apu_loaded = true;
1133 break;
1134 }
1135 default:
1136 // Skip unknown chunk types
1137 break;
1138 }
1139 }
1140
1141 if (!core_loaded || !cpu_loaded || !ppu_loaded || !apu_loaded) {
1142 return absl::FailedPreconditionError("Missing required chunks in state");
1143 }
1144 return absl::OkStatus();
1145}
1146
1147void Snes::InitAccessTime(bool recalc) {
1148 int start = (recalc) ? 0x800000 : 0; // recalc only updates fast rom
1149 access_time.resize(0x1000000);
1150 for (int i = start; i < 0x1000000; i++) {
1151 access_time[i] = GetAccessTime(i);
1152 }
1153}
1154
1155} // namespace emu
1156} // namespace yaze
auto dsp() -> Dsp &
Definition apu.h:74
void LoadState(std::istream &stream)
Definition apu.cc:449
void Init()
Definition apu.cc:47
void Reset()
Definition apu.cc:54
void set_handshake_tracker(debug::ApuHandshakeTracker *tracker)
Definition apu.h:81
void RunCycles(uint64_t cycles)
Definition apu.cc:88
std::array< uint8_t, 4 > out_ports_
Definition apu.h:138
uint64_t GetCycles() const
Definition apu.h:78
std::array< uint8_t, 6 > in_ports_
Definition apu.h:137
void SaveState(std::ostream &stream)
Definition apu.cc:426
void SetIrq(bool state)
Definition cpu.h:54
void Nmi()
Definition cpu.h:55
void set_int_delay(bool delay)
Definition cpu.h:84
uint16_t PC
Definition cpu.h:80
uint8_t PB
Definition cpu.h:79
void Reset(bool hard=false)
Definition cpu.cc:34
void RunOpcode()
Definition cpu.cc:56
void LoadState(std::istream &stream)
Definition cpu.cc:2067
void SaveState(std::ostream &stream)
Definition cpu.cc:2031
auto open_bus() const -> uint8_t override
Definition memory.h:258
void set_h_pos(uint16_t value) override
Definition memory.h:274
void init_hdma_request() override
Definition memory.h:265
void set_v_pos(uint16_t value) override
Definition memory.h:275
auto v_pos() const -> uint16_t override
Definition memory.h:277
auto h_pos() const -> uint16_t override
Definition memory.h:276
void set_open_bus(uint8_t value) override
Definition memory.h:257
uint8_t cart_read(uint8_t bank, uint16_t adr)
Definition memory.cc:94
void run_hdma_request() override
Definition memory.h:266
void cart_write(uint8_t bank, uint16_t adr, uint8_t val)
Definition memory.cc:109
auto pal_timing() const -> bool override
Definition memory.h:278
void Initialize(const std::vector< uint8_t > &romData, bool verbose=false)
Definition memory.cc:21
void LatchHV()
Definition ppu.h:277
void CatchUp(int h_pos)
Definition ppu.cc:160
bool frame_interlace
Definition ppu.h:303
void StartLine(int line)
Definition ppu.cc:147
void Reset()
Definition ppu.cc:40
void SaveState(std::ostream &stream)
Definition ppu.cc:1206
void HandleFrameStart()
Definition ppu.cc:139
void Write(uint8_t adr, uint8_t val)
Definition ppu.cc:805
void PutPixels(uint8_t *pixel_data)
Definition ppu.cc:1117
uint8_t Read(uint8_t adr, bool latch)
Definition ppu.cc:668
bool CheckOverscan()
Definition ppu.h:307
bool even_frame
Definition ppu.h:300
void Init()
Definition ppu.h:259
void HandleVblank()
Definition ppu.cc:649
void LoadState(std::istream &stream)
Definition ppu.cc:1344
bool nmi_enabled_
Definition snes.h:144
uint32_t frames_
Definition snes.h:133
Input input1
Definition snes.h:159
void SetSamples(int16_t *sample_data, int wanted_samples)
Definition snes.cc:852
void CatchUpApu()
Definition snes.cc:268
uint8_t Rread(uint32_t adr)
Definition snes.cc:596
uint64_t cycles_
Definition snes.h:134
void RunCycles(int cycles)
Definition snes.cc:473
uint16_t v_timer_
Definition snes.h:146
uint16_t port_auto_read_[4]
Definition snes.h:161
void WriteBBus(uint8_t adr, uint8_t val)
Definition snes.cc:635
uint8_t CpuRead(uint32_t adr)
Definition snes.cc:827
absl::Status saveState(const std::string &path)
Definition snes.cc:927
void SetButtonState(int player, int button, bool pressed)
Definition snes.cc:860
uint8_t Read(uint32_t adr)
Definition snes.cc:629
uint16_t h_timer_
Definition snes.h:145
std::vector< uint8_t > access_time
Definition snes.h:71
debug::ApuHandshakeTracker apu_handshake_tracker_
Definition snes.h:167
bool irq_condition_
Definition snes.h:148
bool h_irq_enabled_
Definition snes.h:142
bool audio_only_mode_
Definition snes.h:126
void Reset(bool hard=false)
Definition snes.cc:181
bool fast_mem_
Definition snes.h:115
void RunFrame()
Definition snes.cc:232
uint8_t ReadReg(uint16_t adr)
Definition snes.cc:527
void CpuIdle(bool waiting)
Definition snes.cc:846
uint16_t divide_result_
Definition snes.h:156
void WriteReg(uint16_t adr, uint8_t val)
Definition snes.cc:682
void CpuWrite(uint32_t adr, uint8_t val)
Definition snes.cc:838
bool v_irq_enabled_
Definition snes.h:143
bool in_nmi_
Definition snes.h:147
uint16_t multiply_result_
Definition snes.h:154
uint32_t next_horiz_event
Definition snes.h:139
bool in_irq_
Definition snes.h:149
uint8_t ReadBBus(uint8_t adr)
Definition snes.cc:494
bool running_
Definition snes.h:125
void Write(uint32_t adr, uint8_t val)
Definition snes.cc:777
uint64_t sync_cycle_
Definition snes.h:135
uint16_t divide_a_
Definition snes.h:155
void InitAccessTime(bool recalc)
Definition snes.cc:1147
void RunAudioFrame()
Definition snes.cc:244
void Init(const std::vector< uint8_t > &rom_data)
Definition snes.cc:162
bool auto_joy_read_
Definition snes.h:162
bool in_vblank_
Definition snes.h:150
absl::Status LoadLegacyState(std::istream &file)
Definition snes.cc:885
void SyncCycles(bool start, int sync_cycles)
Definition snes.cc:483
std::vector< uint8_t > rom_data
Definition snes.h:123
absl::Status loadState(const std::string &path)
Definition snes.cc:1006
Input input2
Definition snes.h:160
uint8_t multiply_a_
Definition snes.h:153
void SetPixels(uint8_t *pixel_data)
Definition snes.cc:856
uint8_t ram[0x20000]
Definition snes.h:129
uint16_t auto_joy_timer_
Definition snes.h:163
void HandleInput()
Definition snes.cc:274
MemoryImpl memory_
Definition snes.h:118
void RunCycle()
Definition snes.cc:315
bool ppu_latch_
Definition snes.h:164
uint32_t ram_adr_
Definition snes.h:130
double apu_catchup_cycles_
Definition snes.h:138
int GetAccessTime(uint32_t adr)
Definition snes.cc:811
void OnCpuPortWrite(uint8_t port, uint8_t value, uint32_t pc)
#define LOG_DEBUG(category, format,...)
Definition log.h:133
#define LOG_WARN(category, format,...)
Definition log.h:138
#define LOG_INFO(category, format,...)
Definition log.h:136
absl::Status WriteChunk(std::ostream &out, uint32_t tag, uint32_t version, const std::string &payload)
Definition snes.cc:134
absl::Status ReadBytes(std::istream &in, void *data, size_t size)
Definition snes.cc:76
absl::Status ReadChunkHeader(std::istream &in, ChunkHeader *header)
Definition snes.cc:152
void input_latch(Input *input, bool value)
Definition snes.cc:34
absl::Status WriteScalar(std::ostream &out, T value)
Definition snes.cc:106
constexpr uint32_t kMaxChunkSize
Definition snes.cc:61
constexpr uint32_t MakeTag(char a, char b, char c, char d)
Definition snes.cc:63
absl::Status ReadScalar(std::istream &in, T *value)
Definition snes.cc:115
absl::Status ReadUint32LE(std::istream &in, uint32_t *value)
Definition snes.cc:92
absl::Status WriteBytes(std::ostream &out, const void *data, size_t size)
Definition snes.cc:68
uint8_t input_read(Input *input)
Definition snes.cc:41
constexpr uint32_t kStateFormatVersion
Definition snes.cc:60
absl::Status WriteUint32LE(std::ostream &out, uint32_t value)
Definition snes.cc:84
uint32_t CalculateCRC32(const uint8_t *data, size_t size)
void ResetDma(MemoryImpl *memory)
Definition dma.cc:12
void WriteDma(MemoryImpl *memory, uint16_t adr, uint8_t val)
Definition dma.cc:92
void HandleDma(Snes *snes, MemoryImpl *memory, int cpu_cycles)
Definition dma.cc:192
uint8_t ReadDma(MemoryImpl *memory, uint16_t adr)
Definition dma.cc:39
void StartDma(MemoryImpl *memory, uint8_t val, bool hdma)
Definition dma.cc:391
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
uint16_t current_state_
Definition snes.h:23
bool latch_line_
Definition snes.h:21
uint16_t previous_state_
Definition snes.h:25
uint16_t latched_state_
Definition snes.h:24