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 <type_traits>
10#include <vector>
11#include <string>
12
13#include "app/emu/audio/apu.h"
14#include "app/emu/memory/dma.h"
16#include "app/emu/video/ppu.h"
18#include "absl/status/status.h"
19#include "absl/strings/str_format.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{tag, version, static_cast<uint32_t>(payload.size()),
141 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 ppu_latch_ = false;
217 multiply_a_ = 0xff;
218 multiply_result_ = 0xFE01;
219 divide_a_ = 0xffFF;
220 divide_result_ = 0x101;
221 fast_mem_ = false;
223 next_horiz_event = 16;
224 InitAccessTime(false);
225 LOG_DEBUG("SNES", "Reset complete - CPU will start at $%02X:%04X", cpu_.PB,
226 cpu_.PC);
227}
228
230 while (in_vblank_) {
231 cpu_.RunOpcode();
232 }
233
234 uint32_t frame = frames_;
235
236
237
238 while (!in_vblank_ && frame == frames_) {
239 cpu_.RunOpcode();
240 }
241}
242
244 // Audio-focused frame execution: runs CPU+APU but skips PPU rendering
245 // This maintains CPU-APU communication timing while reducing overhead
246 // Used by MusicEditor for authentic audio playback
247 // Note: PPU registers are still writable, but rendering logic (StartLine/RunLine)
248 // in RunCycle() is skipped when audio_only_mode_ is true.
249
250 audio_only_mode_ = true;
251
252 // Run through vblank if we're in it
253 while (in_vblank_) {
254 cpu_.RunOpcode();
255 }
256
257 uint32_t frame = frames_;
258
259 // Run until next vblank
260 while (!in_vblank_ && frame == frames_) {
261 cpu_.RunOpcode();
262 }
263
264 audio_only_mode_ = false;
265}
266
268 // Bring APU up to the same master cycle count since last catch-up.
269 // cycles_ is monotonically increasing in RunCycle().
271}
272
274 // IMPORTANT: Clear and repopulate auto-read data
275 // This data persists until the next call, allowing NMI to read it
276 memset(port_auto_read_, 0, sizeof(port_auto_read_));
277
278 // Populate port_auto_read_ non-destructively by reading current_state_ directly.
279 // The SNES shifts out 16 bits: B, Y, Select, Start, Up, Down, Left, Right, A, X, L, R, 0, 0, 0, 0
280 // This corresponds to bits 0-11 of our input state.
281 // Note: This assumes current_state_ matches the SNES controller bit order:
282 // 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
283 uint16_t latched1 = input1.current_state_;
284 uint16_t latched2 = input2.current_state_;
285 // if (input1.previous_state_ != input1.current_state_) {
286 // LOG_DEBUG("HandleInput: P1 state 0x%04X -> 0x%04X", input1.previous_state_,
287 // input1.current_state_);
288 // }
289 // if (input2.previous_state_ != input2.current_state_) {
290 // LOG_DEBUG("HandleInput: P2 state 0x%04X -> 0x%04X", input2.previous_state_,
291 // input2.current_state_);
292 // }
293 for (int i = 0; i < 16; i++) {
294 // Read bit i from current state (0 for bits >= 12)
295 uint8_t val1 = (latched1 >> i) & 1;
296 uint8_t val2 = (latched2 >> i) & 1;
297
298 // Store in port_auto_read_ (Big Endian format for registers $4218-$421F)
299 // port_auto_read_[0/1] gets bits 0-15 shifted into position
300 port_auto_read_[0] |= (val1 << (15 - i));
301 port_auto_read_[1] |= (val2 << (15 - i));
302 // port_auto_read_[2/3] remain 0 as standard controllers don't use them
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) LOG_INFO("SNES", "Frames incremented 60 times");
391 }
392 } else {
393 // even interlace frame is 313 lines
394 if ((memory_.v_pos() == 312 &&
396 memory_.v_pos() == 313) {
398 frames_++;
399 static int frame_log_pal = 0;
400 if (++frame_log_pal % 60 == 0) LOG_INFO("SNES", "Frames (PAL) incremented 60 times");
401 }
402 }
403
404 // end of hblank, do most memory_.v_pos()-tests
405 bool starting_vblank = false;
406 if (memory_.v_pos() == 0) {
407 // end of vblank
408 static int vblank_end_count = 0;
409 if (vblank_end_count++ < 10) {
410 LOG_DEBUG(
411 "SNES",
412 "VBlank END - v_pos=0, setting in_vblank_=false at frame %d",
413 frames_);
414 }
415 in_vblank_ = false;
416 in_nmi_ = false;
418 } else if (memory_.v_pos() == 225) {
419 // ask the ppu if we start vblank now or at memory_.v_pos() 240
420 // (overscan)
421 starting_vblank = !ppu_.CheckOverscan();
422 } else if (memory_.v_pos() == 240) {
423 // if we are not yet in vblank, we had an overscan frame, set
424 // starting_vblank
425 if (!in_vblank_)
426 starting_vblank = true;
427 }
428 if (starting_vblank) {
429 // catch up the apu at end of emulated frame (we end frame @ start of
430 // vblank)
431 CatchUpApu();
432 // IMPORTANT: This is the ONLY location where NewFrame() should be called
433 // during frame execution. It marks the DSP sample boundary at vblank start,
434 // after CatchUpApu() has synced the APU. Do NOT call NewFrame() from
435 // Emulator::RunAudioFrame() - that causes incorrect frame boundary timing
436 // and results in audio playing at wrong speed (1.5x due to 48000/32040 ratio).
437 // This also handles games where DMA extends past vblank (Megaman X2 titlescreen,
438 // Tales of Phantasia demo, Actraiser 2 fade-in).
439 apu_.dsp().NewFrame();
440 // we are starting vblank
442
443 static int vblank_start_count = 0;
444 if (vblank_start_count++ < 10) {
445 LOG_DEBUG(
446 "SNES",
447 "VBlank START - v_pos=%d, setting in_vblank_=true at frame %d",
449 }
450
451 in_vblank_ = true;
452 in_nmi_ = true;
453 if (auto_joy_read_) {
454 // TODO: this starts a little after start of vblank
455 auto_joy_timer_ = 4224;
456 HandleInput();
457
458
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) return 0;
568 uint8_t result = port_auto_read_[(adr - 0x4218) / 2] & 0xff;
569 return result;
570 }
571 case 0x4219:
572 case 0x421b:
573 case 0x421d:
574 case 0x421f: {
575 // If transfer is still in progress, data is not yet valid
576 if (auto_joy_timer_ > 0) return 0;
577 uint8_t result = port_auto_read_[(adr - 0x4219) / 2] >> 8;
578
579 return result;
580 }
581 default: {
582 return memory_.open_bus();
583 }
584 }
585}
586
587uint8_t Snes::Rread(uint32_t adr) {
588 uint8_t bank = adr >> 16;
589 adr &= 0xffff;
590 if (bank == 0x7e || bank == 0x7f) {
591 return ram[((bank & 1) << 16) | adr]; // ram
592 }
593 if (bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
594 if (adr < 0x2000) {
595 return ram[adr]; // ram mirror
596 }
597 if (adr >= 0x2100 && adr < 0x2200) {
598 return ReadBBus(adr & 0xff); // B-bus
599 }
600 if (adr == 0x4016) {
601 uint8_t result = input_read(&input1) | (memory_.open_bus() & 0xfc);
602
603 return result;
604 }
605 if (adr == 0x4017) {
606 return input_read(&input2) | (memory_.open_bus() & 0xe0) | 0x1c;
607 }
608 if (adr >= 0x4200 && adr < 0x4220) {
609
610 return ReadReg(adr); // internal registers
611 }
612 if (adr >= 0x4300 && adr < 0x4380) {
613 return ReadDma(&memory_, adr); // dma registers
614 }
615 }
616 // read from cart
617 return memory_.cart_read(bank, adr);
618}
619
620uint8_t Snes::Read(uint32_t adr) {
621 uint8_t val = Rread(adr);
623 return val;
624}
625
626void Snes::WriteBBus(uint8_t adr, uint8_t val) {
627 if (adr < 0x40) {
628 // PPU Register write - catch up rendering first to ensure mid-scanline effects work
629 // Only needed if we are in the visible portion of a visible scanline
630 // Skip in audio-only mode for performance (no video output needed)
631 if (!audio_only_mode_ && !in_vblank_ && memory_.v_pos() > 0 && memory_.h_pos() < 1100) {
633 }
634 ppu_.Write(adr, val);
635 return;
636 }
637 if (adr < 0x80) {
638 CatchUpApu(); // catch up the apu before writing
639 apu_.in_ports_[adr & 0x3] = val;
640
641 // Track CPU port writes for handshake debugging
642 uint32_t full_pc = (static_cast<uint32_t>(cpu_.PB) << 16) | cpu_.PC;
643 apu_handshake_tracker_.OnCpuPortWrite(adr & 0x3, val, full_pc);
644
645
646
647 // NOTE: Auto-reset disabled - relying on complete IPL ROM with counter
648 // protocol The IPL ROM will handle multi-upload sequences via its transfer
649 // loop
650
651 return;
652 }
653 switch (adr) {
654 case 0x80: {
655 ram[ram_adr_++] = val;
656 ram_adr_ &= 0x1ffff;
657 break;
658 }
659 case 0x81: {
660 ram_adr_ = (ram_adr_ & 0x1ff00) | val;
661 break;
662 }
663 case 0x82: {
664 ram_adr_ = (ram_adr_ & 0x100ff) | (val << 8);
665 break;
666 }
667 case 0x83: {
668 ram_adr_ = (ram_adr_ & 0x0ffff) | ((val & 1) << 16);
669 break;
670 }
671 }
672}
673
674void Snes::WriteReg(uint16_t adr, uint8_t val) {
675 switch (adr) {
676 case 0x4200: {
677 // Log ALL writes to $4200 unconditionally
678
679
680 auto_joy_read_ = val & 0x1;
681 if (!auto_joy_read_)
682 auto_joy_timer_ = 0;
683
684 // Debug: Log when auto-joy-read is enabled/disabled
685
686 h_irq_enabled_ = val & 0x10;
687 v_irq_enabled_ = val & 0x20;
689 in_irq_ = false;
690 cpu_.SetIrq(false);
691 }
692 // if nmi is enabled while in_nmi_ is still set, immediately generate nmi
693 if (!nmi_enabled_ && (val & 0x80) && in_nmi_) {
694 cpu_.Nmi();
695 }
696 bool old_nmi = nmi_enabled_;
697 nmi_enabled_ = val & 0x80;
698
699 cpu_.set_int_delay(true);
700 break;
701 }
702 case 0x4201: {
703 if (!(val & 0x80) && ppu_latch_) {
704 // latch the ppu
705 ppu_.LatchHV();
706 }
707 ppu_latch_ = val & 0x80;
708 break;
709 }
710 case 0x4202: {
711 multiply_a_ = val;
712 break;
713 }
714 case 0x4203: {
716 break;
717 }
718 case 0x4204: {
719 divide_a_ = (divide_a_ & 0xff00) | val;
720 break;
721 }
722 case 0x4205: {
723 divide_a_ = (divide_a_ & 0x00ff) | (val << 8);
724 break;
725 }
726 case 0x4206: {
727 if (val == 0) {
728 divide_result_ = 0xffff;
730 } else {
733 }
734 break;
735 }
736 case 0x4207: {
737 h_timer_ = (h_timer_ & 0x100) | val;
738 break;
739 }
740 case 0x4208: {
741 h_timer_ = (h_timer_ & 0x0ff) | ((val & 1) << 8);
742 break;
743 }
744 case 0x4209: {
745 v_timer_ = (v_timer_ & 0x100) | val;
746 break;
747 }
748 case 0x420a: {
749 v_timer_ = (v_timer_ & 0x0ff) | ((val & 1) << 8);
750 break;
751 }
752 case 0x420b: {
753 StartDma(&memory_, val, false);
754 break;
755 }
756 case 0x420c: {
757 StartDma(&memory_, val, true);
758 break;
759 }
760 case 0x420d: {
761 fast_mem_ = val & 0x1;
762 break;
763 }
764 default: {
765 break;
766 }
767 }
768}
769
770void Snes::Write(uint32_t adr, uint8_t val) {
772
773
774
775 uint8_t bank = adr >> 16;
776 adr &= 0xffff;
777 if (bank == 0x7e || bank == 0x7f) {
778
779 ram[((bank & 1) << 16) | adr] = val; // ram
780 }
781 if (bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) {
782 if (adr < 0x2000) {
783
784 ram[adr] = val; // ram mirror
785 }
786 if (adr >= 0x2100 && adr < 0x2200) {
787 WriteBBus(adr & 0xff, val); // B-bus
788 }
789 if (adr == 0x4016) {
790
791 input_latch(&input1, val & 1); // input latch
792 input_latch(&input2, val & 1);
793 }
794 if (adr >= 0x4200 && adr < 0x4220) {
795 WriteReg(adr, val); // internal registers
796 }
797 if (adr >= 0x4300 && adr < 0x4380) {
798 WriteDma(&memory_, adr, val); // dma registers
799 }
800 }
801
802 // write to cart
803 memory_.cart_write(bank, adr, val);
804}
805
806int Snes::GetAccessTime(uint32_t adr) {
807 uint8_t bank = adr >> 16;
808 adr &= 0xffff;
809 if ((bank < 0x40 || (bank >= 0x80 && bank < 0xc0)) && adr < 0x8000) {
810 // 00-3f,80-bf:0-7fff
811 if (adr < 0x2000 || adr >= 0x6000)
812 return 8; // 0-1fff, 6000-7fff
813 if (adr < 0x4000 || adr >= 0x4200)
814 return 6; // 2000-3fff, 4200-5fff
815 return 12; // 4000-41ff
816 }
817 // 40-7f,co-ff:0000-ffff, 00-3f,80-bf:8000-ffff
818 return (fast_mem_ && bank >= 0x80) ? 6
819 : 8; // depends on setting in banks 80+
820}
821
822uint8_t Snes::CpuRead(uint32_t adr) {
823 cpu_.set_int_delay(false);
824 const int cycles = access_time[adr] - 4;
825 HandleDma(this, &memory_, cycles);
826 RunCycles(cycles);
827 uint8_t rv = Read(adr);
828 HandleDma(this, &memory_, 4);
829 RunCycles(4);
830 return rv;
831}
832
833void Snes::CpuWrite(uint32_t adr, uint8_t val) {
834 cpu_.set_int_delay(false);
835 const int cycles = access_time[adr];
836 HandleDma(this, &memory_, cycles);
837 RunCycles(cycles);
838 Write(adr, val);
839}
840
841void Snes::CpuIdle(bool waiting) {
842 cpu_.set_int_delay(false);
843 HandleDma(this, &memory_, 6);
844 RunCycles(6);
845}
846
847void Snes::SetSamples(int16_t* sample_data, int wanted_samples) {
848 apu_.dsp().GetSamples(sample_data, wanted_samples, memory_.pal_timing());
849}
850
851void Snes::SetPixels(uint8_t* pixel_data) {
852 ppu_.PutPixels(pixel_data);
853}
854
855void Snes::SetButtonState(int player, int button, bool pressed) {
856 // Select the appropriate input based on player number
857 // Select the appropriate input based on player number
858 // Player 0 = Controller 1, Player 1 = Controller 2
859 Input* input = (player == 0) ? &input1 : &input2;
860
861 // SNES controller button mapping (standard layout)
862 // Bit 0: B, Bit 1: Y, Bit 2: Select, Bit 3: Start
863 // Bit 4: Up, Bit 5: Down, Bit 6: Left, Bit 7: Right
864 // Bit 8: A, Bit 9: X, Bit 10: L, Bit 11: R
865
866 if (button < 0 || button > 11)
867 return; // Validate button range
868
869 uint16_t old_state = input->current_state_;
870
871 if (pressed) {
872 // Set the button bit
873 input->current_state_ |= (1 << button);
874 } else {
875 // Clear the button bit
876 input->current_state_ &= ~(1 << button);
877 }
878}
879
880absl::Status Snes::LoadLegacyState(std::istream& file) {
881 uint32_t version = 0;
882 RETURN_IF_ERROR(ReadUint32LE(file, &version));
883 if (version != 1) {
884 return absl::FailedPreconditionError("Unsupported legacy state version");
885 }
886
887 RETURN_IF_ERROR(ReadBytes(file, ram, sizeof(ram)));
888 RETURN_IF_ERROR(ReadScalar(file, &ram_adr_));
889 RETURN_IF_ERROR(ReadScalar(file, &cycles_));
890 RETURN_IF_ERROR(ReadScalar(file, &sync_cycle_));
891 RETURN_IF_ERROR(ReadScalar(file, &apu_catchup_cycles_));
892 RETURN_IF_ERROR(ReadScalar(file, &h_irq_enabled_));
893 RETURN_IF_ERROR(ReadScalar(file, &v_irq_enabled_));
894 RETURN_IF_ERROR(ReadScalar(file, &nmi_enabled_));
895 RETURN_IF_ERROR(ReadScalar(file, &h_timer_));
896 RETURN_IF_ERROR(ReadScalar(file, &v_timer_));
897 RETURN_IF_ERROR(ReadScalar(file, &in_nmi_));
898 RETURN_IF_ERROR(ReadScalar(file, &irq_condition_));
899 RETURN_IF_ERROR(ReadScalar(file, &in_irq_));
900 RETURN_IF_ERROR(ReadScalar(file, &in_vblank_));
901 RETURN_IF_ERROR(ReadBytes(file, port_auto_read_, sizeof(port_auto_read_)));
902 RETURN_IF_ERROR(ReadScalar(file, &auto_joy_read_));
903 RETURN_IF_ERROR(ReadScalar(file, &auto_joy_timer_));
904 RETURN_IF_ERROR(ReadScalar(file, &ppu_latch_));
905 RETURN_IF_ERROR(ReadScalar(file, &multiply_a_));
906 RETURN_IF_ERROR(ReadScalar(file, &multiply_result_));
907 RETURN_IF_ERROR(ReadScalar(file, &divide_a_));
908 RETURN_IF_ERROR(ReadScalar(file, &divide_result_));
909 RETURN_IF_ERROR(ReadScalar(file, &fast_mem_));
910 RETURN_IF_ERROR(ReadScalar(file, &next_horiz_event));
911
912 cpu_.LoadState(file);
913 ppu_.LoadState(file);
914 apu_.LoadState(file);
915
916 if (!file) {
917 return absl::InternalError("Failed while reading legacy state");
918 }
919 return absl::OkStatus();
920}
921
922absl::Status Snes::saveState(const std::string& path) {
923 std::ofstream file(path, std::ios::binary);
924 if (!file) {
925 return absl::InternalError("Failed to open state file for writing");
926 }
927 if (!IsLittleEndianHost()) {
928 return absl::FailedPreconditionError(
929 "State serialization requires a little-endian host");
930 }
931
932 RETURN_IF_ERROR(WriteUint32LE(file, kStateMagic));
933 RETURN_IF_ERROR(WriteUint32LE(file, kStateFormatVersion));
934
935 auto write_core_chunk = [&]() -> absl::Status {
936 std::ostringstream chunk(std::ios::binary);
937 RETURN_IF_ERROR(WriteBytes(chunk, ram, sizeof(ram)));
938 RETURN_IF_ERROR(WriteScalar(chunk, ram_adr_));
939 RETURN_IF_ERROR(WriteScalar(chunk, cycles_));
940 RETURN_IF_ERROR(WriteScalar(chunk, sync_cycle_));
941 RETURN_IF_ERROR(WriteScalar(chunk, apu_catchup_cycles_));
942 RETURN_IF_ERROR(WriteScalar(chunk, h_irq_enabled_));
943 RETURN_IF_ERROR(WriteScalar(chunk, v_irq_enabled_));
944 RETURN_IF_ERROR(WriteScalar(chunk, nmi_enabled_));
945 RETURN_IF_ERROR(WriteScalar(chunk, h_timer_));
946 RETURN_IF_ERROR(WriteScalar(chunk, v_timer_));
947 RETURN_IF_ERROR(WriteScalar(chunk, in_nmi_));
948 RETURN_IF_ERROR(WriteScalar(chunk, irq_condition_));
949 RETURN_IF_ERROR(WriteScalar(chunk, in_irq_));
950 RETURN_IF_ERROR(WriteScalar(chunk, in_vblank_));
951 for (const auto val : port_auto_read_) {
952 RETURN_IF_ERROR(WriteScalar(chunk, val));
953 }
954 RETURN_IF_ERROR(WriteScalar(chunk, auto_joy_read_));
955 RETURN_IF_ERROR(WriteScalar(chunk, auto_joy_timer_));
956 RETURN_IF_ERROR(WriteScalar(chunk, ppu_latch_));
957 RETURN_IF_ERROR(WriteScalar(chunk, multiply_a_));
958 RETURN_IF_ERROR(WriteScalar(chunk, multiply_result_));
959 RETURN_IF_ERROR(WriteScalar(chunk, divide_a_));
960 RETURN_IF_ERROR(WriteScalar(chunk, divide_result_));
961 RETURN_IF_ERROR(WriteScalar(chunk, fast_mem_));
962 RETURN_IF_ERROR(WriteScalar(chunk, next_horiz_event));
963
964 if (!chunk) {
965 return absl::InternalError("Failed to buffer core state");
966 }
967 return WriteChunk(file, MakeTag('S', 'N', 'E', 'S'), 1, chunk.str());
968 };
969
970 RETURN_IF_ERROR(write_core_chunk());
971
972 auto write_component = [&](uint32_t tag, uint32_t version,
973 auto&& writer) -> absl::Status {
974 std::ostringstream chunk(std::ios::binary);
975 writer(chunk);
976 if (!chunk) {
977 return absl::InternalError(
978 absl::StrFormat("Failed to serialize chunk %08x", tag));
979 }
980 auto payload = chunk.str();
981 if (payload.size() > kMaxChunkSize) {
982 return absl::FailedPreconditionError(
983 "Serialized chunk exceeded maximum allowed size");
984 }
985 return WriteChunk(file, tag, version, payload);
986 };
987
988 RETURN_IF_ERROR(write_component(MakeTag('C', 'P', 'U', ' '), 1,
989 [&](std::ostream& out) { cpu_.SaveState(out); }));
990 RETURN_IF_ERROR(write_component(MakeTag('P', 'P', 'U', ' '), 1,
991 [&](std::ostream& out) { ppu_.SaveState(out); }));
992 RETURN_IF_ERROR(write_component(MakeTag('A', 'P', 'U', ' '), 1,
993 [&](std::ostream& out) { apu_.SaveState(out); }));
994
995 return absl::OkStatus();
996}
997
998absl::Status Snes::loadState(const std::string& path) {
999 std::ifstream file(path, std::ios::binary);
1000 if (!file) {
1001 return absl::InternalError("Failed to open state file for reading");
1002 }
1003 if (!IsLittleEndianHost()) {
1004 return absl::FailedPreconditionError(
1005 "State serialization requires a little-endian host");
1006 }
1007
1008 // Peek to determine format
1009 uint32_t magic = 0;
1010 auto magic_status = ReadUint32LE(file, &magic);
1011 if (!magic_status.ok() || magic != kStateMagic) {
1012 file.clear();
1013 file.seekg(0);
1014 return LoadLegacyState(file);
1015 }
1016
1017 uint32_t format_version = 0;
1018 RETURN_IF_ERROR(ReadUint32LE(file, &format_version));
1019 if (format_version != kStateFormatVersion) {
1020 return absl::FailedPreconditionError("Unsupported state file format");
1021 }
1022
1023 auto load_core_chunk = [&](std::istream& stream) -> absl::Status {
1024 RETURN_IF_ERROR(ReadBytes(stream, ram, sizeof(ram)));
1025 RETURN_IF_ERROR(ReadScalar(stream, &ram_adr_));
1026 RETURN_IF_ERROR(ReadScalar(stream, &cycles_));
1027 RETURN_IF_ERROR(ReadScalar(stream, &sync_cycle_));
1028 RETURN_IF_ERROR(ReadScalar(stream, &apu_catchup_cycles_));
1029 RETURN_IF_ERROR(ReadScalar(stream, &h_irq_enabled_));
1030 RETURN_IF_ERROR(ReadScalar(stream, &v_irq_enabled_));
1031 RETURN_IF_ERROR(ReadScalar(stream, &nmi_enabled_));
1032 RETURN_IF_ERROR(ReadScalar(stream, &h_timer_));
1033 RETURN_IF_ERROR(ReadScalar(stream, &v_timer_));
1034 RETURN_IF_ERROR(ReadScalar(stream, &in_nmi_));
1035 RETURN_IF_ERROR(ReadScalar(stream, &irq_condition_));
1036 RETURN_IF_ERROR(ReadScalar(stream, &in_irq_));
1037 RETURN_IF_ERROR(ReadScalar(stream, &in_vblank_));
1038 for (auto& val : port_auto_read_) {
1039 RETURN_IF_ERROR(ReadScalar(stream, &val));
1040 }
1041 RETURN_IF_ERROR(ReadScalar(stream, &auto_joy_read_));
1042 RETURN_IF_ERROR(ReadScalar(stream, &auto_joy_timer_));
1043 RETURN_IF_ERROR(ReadScalar(stream, &ppu_latch_));
1044 RETURN_IF_ERROR(ReadScalar(stream, &multiply_a_));
1045 RETURN_IF_ERROR(ReadScalar(stream, &multiply_result_));
1046 RETURN_IF_ERROR(ReadScalar(stream, &divide_a_));
1047 RETURN_IF_ERROR(ReadScalar(stream, &divide_result_));
1048 RETURN_IF_ERROR(ReadScalar(stream, &fast_mem_));
1049 RETURN_IF_ERROR(ReadScalar(stream, &next_horiz_event));
1050 return absl::OkStatus();
1051 };
1052
1053 bool core_loaded = false;
1054 bool cpu_loaded = false;
1055 bool ppu_loaded = false;
1056 bool apu_loaded = false;
1057
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;
1063 }
1064
1065 if (header.size > kMaxChunkSize) {
1066 return absl::FailedPreconditionError("State chunk too large");
1067 }
1068
1069 std::string payload(header.size, '\0');
1070 auto read_status = ReadBytes(file, payload.data(), header.size);
1071 if (!read_status.ok()) {
1072 return read_status;
1073 }
1074
1075 uint32_t crc = render::CalculateCRC32(
1076 reinterpret_cast<const uint8_t*>(payload.data()), payload.size());
1077 if (crc != header.crc32) {
1078 return absl::FailedPreconditionError("State chunk CRC mismatch");
1079 }
1080
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");
1086 }
1087 auto status = load_core_chunk(chunk_stream);
1088 if (!status.ok()) return status;
1089 core_loaded = true;
1090 break;
1091 }
1092 case MakeTag('C', 'P', 'U', ' '): {
1093 if (header.version != 1) {
1094 return absl::FailedPreconditionError("Unsupported CPU chunk version");
1095 }
1096 cpu_.LoadState(chunk_stream);
1097 if (!chunk_stream) {
1098 return absl::InternalError("Failed to load CPU chunk");
1099 }
1100 cpu_loaded = true;
1101 break;
1102 }
1103 case MakeTag('P', 'P', 'U', ' '): {
1104 if (header.version != 1) {
1105 return absl::FailedPreconditionError("Unsupported PPU chunk version");
1106 }
1107 ppu_.LoadState(chunk_stream);
1108 if (!chunk_stream) {
1109 return absl::InternalError("Failed to load PPU chunk");
1110 }
1111 ppu_loaded = true;
1112 break;
1113 }
1114 case MakeTag('A', 'P', 'U', ' '): {
1115 if (header.version != 1) {
1116 return absl::FailedPreconditionError("Unsupported APU chunk version");
1117 }
1118 apu_.LoadState(chunk_stream);
1119 if (!chunk_stream) {
1120 return absl::InternalError("Failed to load APU chunk");
1121 }
1122 apu_loaded = true;
1123 break;
1124 }
1125 default:
1126 // Skip unknown chunk types
1127 break;
1128 }
1129 }
1130
1131 if (!core_loaded || !cpu_loaded || !ppu_loaded || !apu_loaded) {
1132 return absl::FailedPreconditionError("Missing required chunks in state");
1133 }
1134 return absl::OkStatus();
1135}
1136
1137void Snes::InitAccessTime(bool recalc) {
1138 int start = (recalc) ? 0x800000 : 0; // recalc only updates fast rom
1139 access_time.resize(0x1000000);
1140 for (int i = start; i < 0x1000000; i++) {
1141 access_time[i] = GetAccessTime(i);
1142 }
1143}
1144
1145} // namespace emu
1146} // 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 SaveState(std::ostream &stream)
Definition cpu.cc:2031
void set_int_delay(bool delay)
Definition cpu.h:84
void LoadState(std::istream &stream)
Definition cpu.cc:2062
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
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:73
void run_hdma_request() override
Definition memory.h:266
void cart_write(uint8_t bank, uint16_t adr, uint8_t val)
Definition memory.cc:88
auto pal_timing() const -> bool override
Definition memory.h:278
void Initialize(const std::vector< uint8_t > &romData, bool verbose=false)
Definition memory.cc:12
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:847
void CatchUpApu()
Definition snes.cc:267
uint8_t Rread(uint32_t adr)
Definition snes.cc:587
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:626
uint8_t CpuRead(uint32_t adr)
Definition snes.cc:822
absl::Status saveState(const std::string &path)
Definition snes.cc:922
void SetButtonState(int player, int button, bool pressed)
Definition snes.cc:855
uint8_t Read(uint32_t adr)
Definition snes.cc:620
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:229
uint8_t ReadReg(uint16_t adr)
Definition snes.cc:527
void CpuIdle(bool waiting)
Definition snes.cc:841
uint16_t divide_result_
Definition snes.h:156
void WriteReg(uint16_t adr, uint8_t val)
Definition snes.cc:674
void CpuWrite(uint32_t adr, uint8_t val)
Definition snes.cc:833
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:770
uint64_t sync_cycle_
Definition snes.h:135
uint16_t divide_a_
Definition snes.h:155
void InitAccessTime(bool recalc)
Definition snes.cc:1137
void RunAudioFrame()
Definition snes.cc:243
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:880
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:998
Input input2
Definition snes.h:160
uint8_t multiply_a_
Definition snes.h:153
void SetPixels(uint8_t *pixel_data)
Definition snes.cc:851
uint8_t ram[0x20000]
Definition snes.h:129
uint16_t auto_joy_timer_
Definition snes.h:163
void HandleInput()
Definition snes.cc:273
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:806
void OnCpuPortWrite(uint8_t port, uint8_t value, uint32_t pc)
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_INFO(category, format,...)
Definition log.h:105
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