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