backend-infra-engineer: Pre-0.2.2 snapshot (2023)
This commit is contained in:
376
src/app/emu/snes.cc
Normal file
376
src/app/emu/snes.cc
Normal file
@@ -0,0 +1,376 @@
|
||||
#include "app/emu/snes.h"
|
||||
|
||||
#include <SDL_mixer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "app/emu/audio/apu.h"
|
||||
#include "app/emu/audio/spc700.h"
|
||||
#include "app/emu/cpu/clock.h"
|
||||
#include "app/emu/cpu/cpu.h"
|
||||
#include "app/emu/debug/debugger.h"
|
||||
#include "app/emu/memory/memory.h"
|
||||
#include "app/emu/video/ppu.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace emu {
|
||||
|
||||
namespace {
|
||||
|
||||
uint16_t GetHeaderOffset(const Memory& memory) {
|
||||
uint8_t mapMode = memory[(0x00 << 16) + 0xFFD5];
|
||||
uint16_t offset;
|
||||
|
||||
switch (mapMode & 0x07) {
|
||||
case 0: // LoROM
|
||||
offset = 0x7FC0;
|
||||
break;
|
||||
case 1: // HiROM
|
||||
offset = 0xFFC0;
|
||||
break;
|
||||
case 5: // ExHiROM
|
||||
offset = 0x40;
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument(
|
||||
"Unable to locate supported ROM mapping mode in the provided ROM "
|
||||
"file. Please try another ROM file.");
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
void audio_callback(void* userdata, uint8_t* stream, int len) {
|
||||
auto* apu = static_cast<APU*>(userdata);
|
||||
auto* buffer = reinterpret_cast<int16_t*>(stream);
|
||||
|
||||
for (int i = 0; i < len / 2; i++) { // Assuming 16-bit samples
|
||||
buffer[i] = apu->GetNextSample(); // This function should be implemented in
|
||||
// APU to fetch the next sample
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ROMInfo SNES::ReadRomHeader(uint32_t offset) {
|
||||
ROMInfo romInfo;
|
||||
|
||||
// Read cartridge title
|
||||
char title[22];
|
||||
for (int i = 0; i < 21; ++i) {
|
||||
title[i] = cpu_.ReadByte(offset + i);
|
||||
}
|
||||
title[21] = '\0'; // Null-terminate the string
|
||||
romInfo.title = std::string(title);
|
||||
|
||||
// Read ROM speed and memory map mode
|
||||
uint8_t romSpeedAndMapMode = cpu_.ReadByte(offset + 0x15);
|
||||
romInfo.romSpeed = (ROMSpeed)(romSpeedAndMapMode & 0x07);
|
||||
romInfo.bankSize = (BankSize)((romSpeedAndMapMode >> 5) & 0x01);
|
||||
|
||||
// Read ROM type
|
||||
romInfo.romType = (ROMType)cpu_.ReadByte(offset + 0x16);
|
||||
|
||||
// Read ROM size
|
||||
romInfo.romSize = (ROMSize)cpu_.ReadByte(offset + 0x17);
|
||||
|
||||
// Read RAM size
|
||||
romInfo.sramSize = (SRAMSize)cpu_.ReadByte(offset + 0x18);
|
||||
|
||||
// Read country code
|
||||
romInfo.countryCode = (CountryCode)cpu_.ReadByte(offset + 0x19);
|
||||
|
||||
// Read license
|
||||
romInfo.license = (License)cpu_.ReadByte(offset + 0x1A);
|
||||
|
||||
// Read ROM version
|
||||
romInfo.version = cpu_.ReadByte(offset + 0x1B);
|
||||
|
||||
// Read checksum complement
|
||||
romInfo.checksumComplement = cpu_.ReadWord(offset + 0x1E);
|
||||
|
||||
// Read checksum
|
||||
romInfo.checksum = cpu_.ReadWord(offset + 0x1C);
|
||||
|
||||
// Read NMI VBL vector
|
||||
romInfo.nmiVblVector = cpu_.ReadWord(offset + 0x3E);
|
||||
|
||||
// Read reset vector
|
||||
romInfo.resetVector = cpu_.ReadWord(offset + 0x3C);
|
||||
|
||||
return romInfo;
|
||||
}
|
||||
|
||||
void SNES::Init(ROM& rom) {
|
||||
// Perform a long jump into a FastROM bank (if the ROM speed is FastROM)
|
||||
// Disable the emulation flag (switch to 65816 native mode)
|
||||
cpu_.E = 0;
|
||||
|
||||
// Initialize CPU
|
||||
cpu_.Init();
|
||||
|
||||
// Read the ROM header
|
||||
auto header_offset = GetHeaderOffset(memory_);
|
||||
rom_info_ = ReadRomHeader((0x00 << 16) + header_offset);
|
||||
cpu_.PB = 0x00;
|
||||
cpu_.PC = 0x8000;
|
||||
|
||||
// Initialize PPU
|
||||
ppu_.Init();
|
||||
|
||||
// Initialize APU
|
||||
apu_.Init();
|
||||
|
||||
// Initialize SDL_Mixer to play the audio samples
|
||||
// Mix_HookMusic(audio_callback, &apu);
|
||||
|
||||
// Disable interrupts and rendering
|
||||
memory_.WriteByte(0x4200, 0x00); // NMITIMEN
|
||||
memory_.WriteByte(0x420C, 0x00); // HDMAEN
|
||||
|
||||
// Disable screen
|
||||
memory_.WriteByte(0x2100, 0x8F); // INIDISP
|
||||
|
||||
// Fill Work-RAM with zeros using two 64KiB fixed address DMA transfers to
|
||||
// WMDATA
|
||||
// TODO: Make this load from work ram, potentially in Memory class
|
||||
std::memset((void*)memory_.ram_.data(), 0, sizeof(memory_.ram_));
|
||||
|
||||
// Reset PPU registers to a known good state
|
||||
memory_.WriteByte(0x4201, 0xFF); // WRIO
|
||||
|
||||
// Objects
|
||||
memory_.WriteByte(0x2101, 0x00); // OBSEL
|
||||
memory_.WriteByte(0x2102, 0x00); // OAMADDL
|
||||
memory_.WriteByte(0x2103, 0x00); // OAMADDH
|
||||
|
||||
// Backgrounds
|
||||
memory_.WriteByte(0x2105, 0x00); // BGMODE
|
||||
memory_.WriteByte(0x2106, 0x00); // MOSAIC
|
||||
|
||||
memory_.WriteByte(0x2107, 0x00); // BG1SC
|
||||
memory_.WriteByte(0x2108, 0x00); // BG2SC
|
||||
memory_.WriteByte(0x2109, 0x00); // BG3SC
|
||||
memory_.WriteByte(0x210A, 0x00); // BG4SC
|
||||
|
||||
memory_.WriteByte(0x210B, 0x00); // BG12NBA
|
||||
memory_.WriteByte(0x210C, 0x00); // BG34NBA
|
||||
|
||||
// Scroll Registers
|
||||
memory_.WriteByte(0x210D, 0x00); // BG1HOFS
|
||||
memory_.WriteByte(0x210E, 0xFF); // BG1VOFS
|
||||
|
||||
memory_.WriteByte(0x210F, 0x00); // BG2HOFS
|
||||
memory_.WriteByte(0x2110, 0xFF); // BG2VOFS
|
||||
|
||||
memory_.WriteByte(0x2111, 0x00); // BG3HOFS
|
||||
memory_.WriteByte(0x2112, 0xFF); // BG3VOFS
|
||||
|
||||
memory_.WriteByte(0x2113, 0x00); // BG4HOFS
|
||||
memory_.WriteByte(0x2114, 0xFF); // BG4VOFS
|
||||
|
||||
// VRAM Registers
|
||||
memory_.WriteByte(0x2115, 0x80); // VMAIN
|
||||
|
||||
// Mode 7
|
||||
memory_.WriteByte(0x211A, 0x00); // M7SEL
|
||||
memory_.WriteByte(0x211B, 0x01); // M7A
|
||||
memory_.WriteByte(0x211C, 0x00); // M7B
|
||||
memory_.WriteByte(0x211D, 0x00); // M7C
|
||||
memory_.WriteByte(0x211E, 0x01); // M7D
|
||||
memory_.WriteByte(0x211F, 0x00); // M7X
|
||||
memory_.WriteByte(0x2120, 0x00); // M7Y
|
||||
|
||||
// Windows
|
||||
memory_.WriteByte(0x2123, 0x00); // W12SEL
|
||||
memory_.WriteByte(0x2124, 0x00); // W34SEL
|
||||
memory_.WriteByte(0x2125, 0x00); // WOBJSEL
|
||||
memory_.WriteByte(0x2126, 0x00); // WH0
|
||||
memory_.WriteByte(0x2127, 0x00); // WH1
|
||||
memory_.WriteByte(0x2128, 0x00); // WH2
|
||||
memory_.WriteByte(0x2129, 0x00); // WH3
|
||||
memory_.WriteByte(0x212A, 0x00); // WBGLOG
|
||||
memory_.WriteByte(0x212B, 0x00); // WOBJLOG
|
||||
|
||||
// Layer Enable
|
||||
memory_.WriteByte(0x212C, 0x00); // TM
|
||||
memory_.WriteByte(0x212D, 0x00); // TS
|
||||
memory_.WriteByte(0x212E, 0x00); // TMW
|
||||
memory_.WriteByte(0x212F, 0x00); // TSW
|
||||
|
||||
// Color Math
|
||||
memory_.WriteByte(0x2130, 0x30); // CGWSEL
|
||||
memory_.WriteByte(0x2131, 0x00); // CGADSUB
|
||||
memory_.WriteByte(0x2132, 0xE0); // COLDATA
|
||||
|
||||
// Misc
|
||||
memory_.WriteByte(0x2133, 0x00); // SETINI
|
||||
|
||||
// Psuedo-Init
|
||||
memory_.WriteWord(0x2140, 0xBBAA);
|
||||
|
||||
running_ = true;
|
||||
scanline = 0;
|
||||
}
|
||||
|
||||
void SNES::Run() {
|
||||
const double targetFPS = 60.0; // 60 frames per second
|
||||
const double frame_time = 1.0 / targetFPS;
|
||||
double frame_accumulated_time = 0.0;
|
||||
|
||||
auto last_time = std::chrono::high_resolution_clock::now();
|
||||
|
||||
if (running_) {
|
||||
auto current_time = std::chrono::high_resolution_clock::now();
|
||||
double delta_time =
|
||||
std::chrono::duration<double>(current_time - last_time).count();
|
||||
last_time = current_time;
|
||||
|
||||
frame_accumulated_time += delta_time;
|
||||
|
||||
// Update the CPU
|
||||
cpu_.UpdateClock(delta_time);
|
||||
cpu_.Update(GetCpuMode());
|
||||
|
||||
// Update the PPU
|
||||
ppu_.UpdateClock(delta_time);
|
||||
ppu_.Update();
|
||||
|
||||
// Update the APU
|
||||
apu_.UpdateClock(delta_time);
|
||||
apu_.Update();
|
||||
|
||||
if (frame_accumulated_time >= frame_time) {
|
||||
// renderer.Render();
|
||||
frame_accumulated_time -= frame_time;
|
||||
}
|
||||
|
||||
HandleInput();
|
||||
}
|
||||
}
|
||||
|
||||
void SNES::StepRun() {
|
||||
// Update the CPU
|
||||
cpu_.UpdateClock(0.0);
|
||||
cpu_.Update(CPU::UpdateMode::Step);
|
||||
|
||||
// Update the PPU
|
||||
ppu_.UpdateClock(0.0);
|
||||
ppu_.Update();
|
||||
|
||||
// Update the APU
|
||||
apu_.UpdateClock(0.0);
|
||||
apu_.Update();
|
||||
|
||||
HandleInput();
|
||||
}
|
||||
|
||||
// Enable NMI Interrupts
|
||||
void SNES::EnableVBlankInterrupts() {
|
||||
v_blank_flag_ = false;
|
||||
|
||||
// Clear the RDNMI VBlank flag
|
||||
memory_.ReadByte(0x4210); // RDNMI
|
||||
|
||||
// Enable vblank NMI interrupts and Joypad auto-read
|
||||
memory_.WriteByte(0x4200, 0x81); // NMITIMEN
|
||||
}
|
||||
|
||||
// Wait until the VBlank routine has been processed
|
||||
void SNES::WaitForVBlank() {
|
||||
v_blank_flag_ = true;
|
||||
|
||||
// Loop until `v_blank_flag_` is clear
|
||||
while (v_blank_flag_) {
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
// NMI Interrupt Service Routine
|
||||
void SNES::NmiIsr() {
|
||||
// Switch to a FastROM bank (assuming NmiIsr is in bank 0x80)
|
||||
// ...
|
||||
|
||||
// Push CPU registers to stack
|
||||
cpu_.PHP();
|
||||
|
||||
// Reset DB and DP registers
|
||||
cpu_.DB = 0x80; // Assuming bank 0x80, can be changed to 0x00
|
||||
cpu_.D = 0;
|
||||
|
||||
if (v_blank_flag_) {
|
||||
VBlankRoutine();
|
||||
|
||||
// Clear `v_blank_flag_`
|
||||
v_blank_flag_ = false;
|
||||
}
|
||||
|
||||
// Increment 32-bit frame_counter_
|
||||
frame_counter_++;
|
||||
|
||||
// Restore CPU registers
|
||||
cpu_.PHB();
|
||||
}
|
||||
|
||||
// VBlank routine
|
||||
void SNES::VBlankRoutine() {
|
||||
// Read the joypad state
|
||||
// ...
|
||||
|
||||
// Update the PPU
|
||||
// ...
|
||||
|
||||
// Update the APU
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::StartApuDataTransfer() {
|
||||
// 2. Setting the starting address
|
||||
const uint16_t startAddress = 0x0200;
|
||||
memory_.WriteByte(0x2142, startAddress & 0xFF); // Lower byte
|
||||
memory_.WriteByte(0x2143, startAddress >> 8); // Upper byte
|
||||
memory_.WriteByte(0x2141, 0xCC); // Any non-zero value
|
||||
memory_.WriteByte(0x2140, 0xCC); // Signal to start
|
||||
|
||||
const int DATA_SIZE = 0x1000; // 4 KiB
|
||||
|
||||
// 3. Sending data (simplified)
|
||||
// Assuming a buffer `audioData` containing the audio program/data
|
||||
uint8_t audioData[DATA_SIZE]; // Define DATA_SIZE and populate audioData as
|
||||
// needed
|
||||
for (int i = 0; i < DATA_SIZE; ++i) {
|
||||
memory_.WriteByte(0x2141, audioData[i]);
|
||||
memory_.WriteByte(0x2140, i & 0xFF);
|
||||
while (memory_.ReadByte(0x2140) != (i & 0xFF))
|
||||
; // Wait for acknowledgment
|
||||
}
|
||||
|
||||
// 4. Running the SPC700 program
|
||||
memory_.WriteByte(0x2142, startAddress & 0xFF); // Lower byte
|
||||
memory_.WriteByte(0x2143, startAddress >> 8); // Upper byte
|
||||
memory_.WriteByte(0x2141, 0x00); // Zero to start the program
|
||||
memory_.WriteByte(0x2140, 0xCE); // Increment by 2
|
||||
while (memory_.ReadByte(0x2140) != 0xCE)
|
||||
; // Wait for acknowledgment
|
||||
}
|
||||
|
||||
void SNES::HandleInput() {
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::SaveState(const std::string& path) {
|
||||
// ...
|
||||
}
|
||||
|
||||
void SNES::LoadState(const std::string& path) {
|
||||
// ...
|
||||
}
|
||||
|
||||
} // namespace emu
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
Reference in New Issue
Block a user