From 817827b4ad0ae714d8c036f1da5af2d6965f32b0 Mon Sep 17 00:00:00 2001 From: PalindromicBreadLoaf Date: Mon, 21 Jul 2025 20:27:57 -0400 Subject: [PATCH] src/main.cpp --- .gitignore | 3 + CMakeLists.txt | 125 +++++++++++++++ src/main.cpp | 425 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 553 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..362b619 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +cmake-build-debug/ +build/ +.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..920b4e0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,125 @@ +cmake_minimum_required(VERSION 3.16) +project(breadedSNES VERSION 1.0.0) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") + +# Platform-specific settings +if(WIN32) + add_definitions(-DWIN32_LEAN_AND_MEAN) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +elseif(APPLE) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15") + set(CMAKE_INSTALL_RPATH "@executable_path") +elseif(UNIX) + set(CMAKE_INSTALL_RPATH "$ORIGIN") +endif() + +# Find SDL2 +if(WIN32) + # Windows things + set(SDL2_DIR "C:/SDL2" CACHE PATH "/path/to/sdl2") # Do this later when I have access to a Windows machine + find_package(SDL2 REQUIRED CONFIG) +elseif(APPLE) + # For macOS + execute_process( + COMMAND brew --prefix sdl2 + OUTPUT_VARIABLE SDL2_BREW_PREFIX + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + + if(SDL2_BREW_PREFIX) + list(APPEND CMAKE_PREFIX_PATH "${SDL2_BREW_PREFIX}") + endif() + + list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew" "/usr/local") + + find_package(SDL2 REQUIRED CONFIG) + + message(STATUS "Found SDL2 at: ${SDL2_DIR}") +else() + find_package(PkgConfig REQUIRED) + pkg_check_modules(SDL2 REQUIRED sdl2) +endif() + +add_executable(breadedSNES + src/main.cpp + src/cpu.cpp + src/ppu.cpp + src/apu.cpp + src/bus.cpp + src/system.cpp +) + +target_include_directories(breadedSNES PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${SDL2_INCLUDE_DIRS} +) + +# Link libraries +if(WIN32) + target_link_libraries(breadedSNES + SDL2::SDL2 + SDL2::SDL2main + ) +else() + # Unix systems + target_link_libraries(breadedSNES SDL2::SDL2) + + # macOS only + if(APPLE AND TARGET SDL2::SDL2main) + target_link_libraries(breadedSNES SDL2::SDL2main) + endif() + + # pkg-config + if(SDL2_LIBRARIES AND NOT TARGET SDL2::SDL2) + target_link_libraries(breadedSNES ${SDL2_LIBRARIES}) + target_compile_options(breadedSNES PRIVATE ${SDL2_CFLAGS_OTHER}) + endif() +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options(breadedSNES PRIVATE + -Wall -Wextra -Wpedantic + -Wno-unused-parameter + ) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(breadedSNES PRIVATE + /W4 + /wd4100 # Disable unused parameter warning + ) +endif() + +# Install +install(TARGETS breadedSNES + RUNTIME DESTINATION bin +) + +# Windows SDL2 Stuff +if(WIN32) + if(TARGET SDL2::SDL2) + get_target_property(SDL2_DLL_PATH SDL2::SDL2 IMPORTED_LOCATION) + if(SDL2_DLL_PATH) + install(FILES ${SDL2_DLL_PATH} DESTINATION bin) + endif() + endif() +endif() + +# Package Config +set(CPACK_PACKAGE_NAME "SNES Emulator") +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) +set(CPACK_PACKAGE_DESCRIPTION "Cross-platform SNES Emulator") + +if(WIN32) + set(CPACK_GENERATOR "ZIP") +elseif(APPLE) + set(CPACK_GENERATOR "DragNDrop") +else() + set(CPACK_GENERATOR "TGZ") +endif() + +include(CPack) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a5d5cea --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,425 @@ +// +// Created by Palindromic Bread Loaf on 7/21/25. +// + +#include +#include +#include +#include +#include + +class CPU; +class PPU; +class APU; +class Bus; + +// Main SNES System class +class System { +private: + std::unique_ptr cpu; + std::unique_ptr ppu; + std::unique_ptr apu; + std::unique_ptr bus; + + std::vector cartridge_data; + bool running; + +public: + System(); + ~System(); + + bool LoadROM(const std::string& filename); + void Reset(); + void Run(); + void Step(); + void Shutdown(); +}; + +// Memory Bus - handles memory mapping +class Bus { +private: + uint8_t wram[0x20000]; // 128KB Work RAM + uint8_t sram[0x8000]; // 32KB Save RAM + std::vector* cartridge; // Cartridge Data + +public: + Bus(std::vector* cart) : cartridge(cart) { + std::fill(wram, wram + sizeof(wram), 0); + std::fill(sram, sram + sizeof(sram), 0); + } + + uint8_t Read(uint32_t address); + void Write(uint32_t address, uint8_t value); + uint16_t Read16(uint32_t address); + void Write16(uint32_t address, uint16_t value); +}; + +// 65816 CPU implementation +class CPU { +private: + // Registers + uint16_t A; // Accumulator + uint16_t X, Y; // Index registers + uint16_t SP; // Stack pointer + uint32_t PC; // Program counter (24-bit) + uint8_t P; // Processor status + uint8_t DB; // Data bank + uint8_t PB; // Program bank + uint16_t D; // Direct page + + Bus* bus; + uint64_t cycles; + + // Status flags + enum Flags { + FLAG_C = 0x01, // Carry + FLAG_Z = 0x02, // Zero + FLAG_I = 0x04, // IRQ disable + FLAG_D = 0x08, // Decimal mode + FLAG_X = 0x10, // Index register size (0=16-bit, 1=8-bit) + FLAG_M = 0x20, // Memory/Accumulator size (0=16-bit, 1=8-bit) + FLAG_V = 0x40, // Overflow + FLAG_N = 0x80 // Negative + }; + +public: + CPU(Bus* memory_bus) : bus(memory_bus) { + Reset(); + } + + void Reset(); + void Step(); + void ExecuteInstruction(); + uint64_t GetCycles() const { return cycles; } + + // Instruction implementations + // TODO: Implement remaining instructions + void NOP(); + void LDA(); + void STA(); + void JMP(); +}; + +// PPU (Picture Processing Unit) +class PPU { +private: + uint8_t vram[0x10000]; // 64KB Video RAM + uint8_t oam[0x220]; // Object Attribute Memory + uint8_t cgram[0x200]; // Color Generator RAM + + uint16_t scanline; + uint16_t dot; + bool frame_complete; + + // PPU registers + uint8_t brightness; + uint8_t bg_mode; + // TODO: Add remaining registers + +public: + PPU() { + Reset(); + } + + void Reset(); + void Step(); + bool IsFrameComplete() const { return frame_complete; } + void SetFrameComplete(bool complete) { frame_complete = complete; } + + uint8_t ReadVRAM(uint16_t address); + void WriteVRAM(uint16_t address, uint8_t value); + + // TODO: Implement Rendering + void RenderScanline(); + void UpdateScreen(); +}; + +// SPC700 APU +class APU { +private: + uint8_t spc_ram[0x10000]; // 64KB SPC700 RAM + + // APU registers + uint8_t A, X, Y, SP; + uint16_t PC; + uint8_t PSW; + +public: + APU() { + Reset(); + } + + void Reset(); + void Step(); + + uint8_t ReadSPC(uint16_t address); + void WriteSPC(uint16_t address, uint8_t value); +}; + +// Bus class +uint8_t Bus::Read(uint32_t address) { + // TODO: Map properly based on SNES memory map + if (address < 0x2000) { + return wram[address]; + } else if (address >= 0x7E0000 && address < 0x800000) { + return wram[address - 0x7E0000]; + } else if (address >= 0x800000 && cartridge) { + // ROM access + uint32_t rom_addr = address & 0x7FFFFF; + if (rom_addr < cartridge->size()) { + return (*cartridge)[rom_addr]; + } + } + return 0x00; // Open bus +} + +void Bus::Write(uint32_t address, uint8_t value) { + if (address < 0x2000) { + wram[address] = value; + } else if (address >= 0x7E0000 && address < 0x800000) { + wram[address - 0x7E0000] = value; + } + // TODO: Add PPU/APU register writes here +} + +uint16_t Bus::Read16(uint32_t address) { + return Read(address) | (Read(address + 1) << 8); +} + +void Bus::Write16(uint32_t address, uint16_t value) { + Write(address, value & 0xFF); + Write(address + 1, (value >> 8) & 0xFF); +} + +// CPU Implementation +void CPU::Reset() { + A = X = Y = 0; + SP = 0x01FF; + PC = 0x8000; // Will be loaded from reset vector + P = 0x34; // Start in emulation mode + DB = PB = 0; + D = 0; + cycles = 0; +} + +void CPU::Step() { + ExecuteInstruction(); +} + +void CPU::ExecuteInstruction() { + uint8_t opcode = bus->Read(PC++); + + // TODO: Actual Opcode decoding + switch (opcode) { + case 0xEA: NOP(); break; + case 0xA9: LDA(); break; + + default: + std::cout << "Unknown opcode: 0x" << std::hex << (int)opcode << std::endl; + break; + } + + cycles++; +} + +void CPU::NOP() { + // No operation +} + +void CPU::LDA() { + // Load accumulator - immediate mode + if (P & FLAG_M) { + // 8-bit mode + A = (A & 0xFF00) | bus->Read(PC++); + } else { + // 16-bit mode + A = bus->Read16(PC); + PC += 2; + } +} + +// PPU Implementation +void PPU::Reset() { + scanline = 0; + dot = 0; + frame_complete = false; + brightness = 0x0F; + bg_mode = 0; + + std::fill(vram, vram + sizeof(vram), 0); + std::fill(oam, oam + sizeof(oam), 0); + std::fill(cgram, cgram + sizeof(cgram), 0); +} + +void PPU::Step() { + dot++; + if (dot >= 341) { + dot = 0; + scanline++; + + if (scanline >= 262) { + scanline = 0; + frame_complete = true; + } + } + + // TODO: Implement PPU renderer +} + +uint8_t PPU::ReadVRAM(uint16_t address) { + return vram[address & 0xFFFF]; +} + +void PPU::WriteVRAM(uint16_t address, uint8_t value) { + vram[address & 0xFFFF] = value; +} + +// APU Implementation +void APU::Reset() { + A = X = Y = 0; + SP = 0xFF; + PC = 0x0000; + PSW = 0x02; + + std::fill(spc_ram, spc_ram + sizeof(spc_ram), 0); +} + +void APU::Step() { + // TODO: Implement SPC700 instruction execution +} + +uint8_t APU::ReadSPC(uint16_t address) { + return spc_ram[address]; +} + +void APU::WriteSPC(uint16_t address, uint8_t value) { + spc_ram[address] = value; +} + +// SNES System Implementation +System::System() : running(false) { + bus = std::make_unique(&cartridge_data); + cpu = std::make_unique(bus.get()); + ppu = std::make_unique(); + apu = std::make_unique(); +} + +System::~System() { + Shutdown(); +} + +bool System::LoadROM(const std::string& filename) { + std::ifstream file(filename, std::ios::binary); + if (!file) { + std::cout << "Failed to open ROM file: " << filename << std::endl; + return false; + } + + file.seekg(0, std::ios::end); + size_t size = file.tellg(); + file.seekg(0, std::ios::beg); + + cartridge_data.resize(size); + file.read(reinterpret_cast(cartridge_data.data()), size); + + std::cout << "Loaded ROM: " << filename << " (" << size << " bytes)" << std::endl; + return true; +} + +void System::Reset() { + cpu->Reset(); + ppu->Reset(); + apu->Reset(); +} + +void System::Step() { + cpu->Step(); + ppu->Step(); + apu->Step(); +} + +void System::Run() { + running = true; + while (running) { + Step(); + + if (ppu->IsFrameComplete()) { + ppu->SetFrameComplete(false); + // TODO: Handle frame rendering and input + } + } +} + +void System::Shutdown() { + running = false; +} + +int main(int argc, char* argv[]) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { + std::cout << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl; + return -1; + } + + SDL_Window* window = SDL_CreateWindow( + "BreadedSNES", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + 256, 224, // SNES output resolution + SDL_WINDOW_SHOWN + ); + + if (!window) { + std::cout << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl; + SDL_Quit(); + return -1; + } + + SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + if (!renderer) { + std::cout << "Renderer could not be created! SDL_Error: " << SDL_GetError() << std::endl; + SDL_DestroyWindow(window); + SDL_Quit(); + return -1; + } + + System snes; + + if (argc > 1) { + if (!snes.LoadROM(argv[1])) { + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return -1; + } + } + + snes.Reset(); + + bool quit = false; + SDL_Event e; + + while (!quit) { + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + quit = true; + } + } + + // Run emulation step + snes.Step(); + + // Clear screen + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + // TODO: Render frame + + SDL_RenderPresent(renderer); + } + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} \ No newline at end of file