diff --git a/.gitmodules b/.gitmodules index 5c71050b..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "external/toml11"] - path = external/toml11 - url = https://github.com/ToruNiina/toml11 diff --git a/CMakeLists.txt b/CMakeLists.txt index 96e88f16..af6715dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,4 +6,5 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE) add_subdirectory(src) add_executable(natsukashii src/main.cpp) +target_include_directories(natsukashii PUBLIC src/frontend/) target_link_libraries(natsukashii cores frontend) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt new file mode 100644 index 00000000..a4d17a75 --- /dev/null +++ b/external/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.20) + +include_directories(.) \ No newline at end of file diff --git a/external/portable_endian_bswap.h b/external/portable_endian_bswap.h new file mode 100644 index 00000000..e2087165 --- /dev/null +++ b/external/portable_endian_bswap.h @@ -0,0 +1,177 @@ +// "License": Public Domain +// I, Mathias Panzenböck, place this file hereby into the public domain. Use it at your own risk for whatever you like. +// In case there are jurisdictions that don't support putting things in the public domain you can also consider it to +// be "dual licensed" under the BSD, MIT and Apache licenses, if you want to. This code is trivial anyway. Consider it +// an example on how to get the endian conversion functions on different platforms. + +#pragma once + +#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__) + +# define __WINDOWS__ + +#endif + +#if defined(__linux__) || defined(__CYGWIN__) + +# include + +#elif defined(__APPLE__) + +# include + +# define htobe16(x) OSSwapHostToBigInt16(x) +# define htole16(x) OSSwapHostToLittleInt16(x) +# define be16toh(x) OSSwapBigToHostInt16(x) +# define le16toh(x) OSSwapLittleToHostInt16(x) + +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htole32(x) OSSwapHostToLittleInt32(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) + +# define htobe64(x) OSSwapHostToBigInt64(x) +# define htole64(x) OSSwapHostToLittleInt64(x) +# define be64toh(x) OSSwapBigToHostInt64(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#elif defined(__OpenBSD__) + +# include + +#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) + +# include + +# define be16toh(x) betoh16(x) +# define le16toh(x) letoh16(x) + +# define be32toh(x) betoh32(x) +# define le32toh(x) letoh32(x) + +# define be64toh(x) betoh64(x) +# define le64toh(x) letoh64(x) + +#elif defined(__WINDOWS__) + +# include +# include + +# if BYTE_ORDER == LITTLE_ENDIAN + +# define htobe16(x) htons(x) +# define htole16(x) (x) +# define be16toh(x) ntohs(x) +# define le16toh(x) (x) + +# define htobe32(x) htonl(x) +# define htole32(x) (x) +# define be32toh(x) ntohl(x) +# define le32toh(x) (x) + +# define htobe64(x) htonll(x) +# define htole64(x) (x) +# define be64toh(x) ntohll(x) +# define le64toh(x) (x) + +# elif BYTE_ORDER == BIG_ENDIAN + + /* that would be xbox 360 */ +# define htobe16(x) (x) +# define htole16(x) __builtin_bswap16(x) +# define be16toh(x) (x) +# define le16toh(x) __builtin_bswap16(x) + +# define htobe32(x) (x) +# define htole32(x) __builtin_bswap32(x) +# define be32toh(x) (x) +# define le32toh(x) __builtin_bswap32(x) + +# define htobe64(x) (x) +# define htole64(x) __builtin_bswap64(x) +# define be64toh(x) (x) +# define le64toh(x) __builtin_bswap64(x) + +# else + +# error byte order not supported + +# endif + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#else + +# error platform not supported + +#endif + +// Adapted from Google's CityHash source code: +// +// https://github.com/google/cityhash/blob/8af9b8c2b889d80c22d6bc26ba0df1afb79a30db/src/city.cc#L50 + +#if defined(_MSC_VER) + +#include +#define bswap_16(x) _byteswap_ushort(x) +#define bswap_32(x) _byteswap_ulong(x) +#define bswap_64(x) _byteswap_uint64(x) + +#elif defined(__APPLE__) + +// Mac OS X / Darwin features +#include +#define bswap_16(x) OSSwapInt16(x) +#define bswap_32(x) OSSwapInt32(x) +#define bswap_64(x) OSSwapInt64(x) + +#elif defined(__sun) || defined(sun) + +#include +#define bswap_16(x) BSWAP_16(x) +#define bswap_32(x) BSWAP_32(x) +#define bswap_64(x) BSWAP_64(x) + +#elif defined(__FreeBSD__) + +#include +#define bswap_16(x) bswap16(x) +#define bswap_32(x) bswap32(x) +#define bswap_64(x) bswap64(x) + +#elif defined(__OpenBSD__) + +#include +#define bswap_16(x) swap16(x) +#define bswap_32(x) swap32(x) +#define bswap_64(x) swap64(x) + +#elif defined(__NetBSD__) + +#include +#include +#if defined(__BSWAP_RENAME) && !defined(__bswap_32) +#define bswap_16(x) bswap16(x) +#define bswap_32(x) bswap32(x) +#define bswap_64(x) bswap64(x) +#endif + +#elif __has_builtin(__builtin_bswap16) && __has_builtin(__builtin_bswap32) && __has_builtin(__builtin_bswap64) + +#define bswap_16(x) __builtin_bswap16(x) +#define bswap_32(x) __builtin_bswap32(x) +#define bswap_64(x) __builtin_bswap64(x) + +#else + +#include + +#endif \ No newline at end of file diff --git a/external/toml11 b/external/toml11 deleted file mode 160000 index 59243256..00000000 --- a/external/toml11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 59243256528d4133321e845c3193db2d2725e6ee diff --git a/src/core/BaseCore.hpp b/src/core/BaseCore.hpp new file mode 100644 index 00000000..a41e37e9 --- /dev/null +++ b/src/core/BaseCore.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace natsukashii::core { +struct BaseCore { + virtual void Run() {} +}; +} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 862d9c9d..7cbef9ee 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -5,9 +5,13 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_subdirectory(gb) +add_subdirectory(n64) + add_library(cores Scheduler.cpp Scheduler.hpp - common.hpp) -target_include_directories(cores PUBLIC .) -target_link_libraries(cores PUBLIC gb) + common.hpp + BaseCore.hpp) +target_include_directories(cores PRIVATE .) +target_include_directories(cores PUBLIC ../../external) +target_link_libraries(cores PUBLIC gb n64) diff --git a/src/core/gb/CMakeLists.txt b/src/core/gb/CMakeLists.txt index 38a67b04..4f291cf4 100644 --- a/src/core/gb/CMakeLists.txt +++ b/src/core/gb/CMakeLists.txt @@ -5,7 +5,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(mio REQUIRED) find_package(fmt REQUIRED) +find_package(toml11 REQUIRED) add_library(gb Core.hpp Core.cpp Cpu.hpp Cpu.cpp Ppu.hpp Ppu.cpp Mem.cpp Mem.hpp mbc.cpp mbc.hpp memory_regions.hpp) -target_include_directories(gb PUBLIC . ..) -target_link_libraries(gb PUBLIC mio::mio fmt::fmt) +target_include_directories(gb PRIVATE . ..) +target_link_libraries(gb PUBLIC toml11::toml11 mio::mio fmt::fmt) diff --git a/src/core/gb/Core.cpp b/src/core/gb/Core.cpp index b62a2ef8..5f3f66b3 100644 --- a/src/core/gb/Core.cpp +++ b/src/core/gb/Core.cpp @@ -1,7 +1,9 @@ #include -namespace natsukashii::core { -Core::Core() {} +namespace natsukashii::gb::core { +Core::Core(const std::string& rom) { + mem.LoadROM(rom); +} void Core::Run() { while(true) { diff --git a/src/core/gb/Core.hpp b/src/core/gb/Core.hpp index 7e9a23f3..90763449 100644 --- a/src/core/gb/Core.hpp +++ b/src/core/gb/Core.hpp @@ -1,12 +1,13 @@ #pragma once -#include "Cpu.hpp" -#include "Ppu.hpp" -#include "Mem.hpp" +#include +#include +#include +#include -namespace natsukashii::core { -struct Core { - Core(); - void Run(); +namespace natsukashii::gb::core { +struct Core : natsukashii::core::BaseCore { + Core(const std::string&); + void Run() override; private: Mem mem; Cpu cpu; diff --git a/src/core/gb/Cpu.cpp b/src/core/gb/Cpu.cpp index 81ffb218..ac28c986 100644 --- a/src/core/gb/Cpu.cpp +++ b/src/core/gb/Cpu.cpp @@ -1,18 +1,30 @@ #include #include -namespace natsukashii::core { -Cpu::Cpu() { -} +namespace natsukashii::gb::core { +Cpu::Cpu() {} void Cpu::Step(Mem& mem) { - u8 opcode = mem.Consume(pc); - DecodeAndExecute(opcode); + FetchDecodeExecute(mem); } -void Cpu::DecodeAndExecute(u8 opcode) { +void Cpu::FetchDecodeExecute(Mem& mem) { + u8 opcode = mem.Read8(regs.pc); switch(opcode) { - default: util::panic("Unimplemented opcode %02X", opcode); + case 0x01: case 0x11: case 0x21: case 0x31: // LD r16, u16 + SetR16<1>((opcode >> 4) & 3, mem.Consume16(regs.pc)); + break; + case 0xA8 ... 0xAF: // XOR A, r8 + regs.a() ^= GetR8(opcode & 7, mem); + regs.f().set(regs.a() == 0, false, false, false); + break; + case 0x02: case 0x12: case 0x22: case 0x32: { + u8 bits = (opcode >> 4) & 3; + mem.Write8(GetR16<2>(bits), regs.a()); + } break; + default: util::panic("Unimplemented opcode {:02X}, pc: {:04X}", opcode, regs.pc); } + + regs.pc++; } } diff --git a/src/core/gb/Cpu.hpp b/src/core/gb/Cpu.hpp index ccab9ab6..454abd22 100644 --- a/src/core/gb/Cpu.hpp +++ b/src/core/gb/Cpu.hpp @@ -1,23 +1,8 @@ #pragma once -#include "Mem.hpp" - -namespace natsukashii::core { -#define af regs.AF -#define bc regs.BC -#define de regs.DE -#define hl regs.HL -#define pc regs.PC -#define sp regs.SP - -#define a af.A -#define f af.F -#define b bc.B -#define c bc.C -#define d de.D -#define e de.E -#define h hl.H -#define l hl.L +#include +#include +namespace natsukashii::gb::core { #define REGIMPL(type1, reg1, type2, reg2) \ struct reg##reg1##reg2 { \ reg##reg1##reg2() {} \ @@ -26,17 +11,11 @@ namespace natsukashii::core { type2 reg2; \ }; \ u16 raw = 0; \ - reg##reg1##reg2& operator=(const u16& rhs) { \ - reg1 = rhs >> 8; \ - reg2 = rhs & 0xff; \ - return *this; \ - } \ } reg1##reg2 struct RegF { RegF() : raw(0) {} RegF(const u8& val) : raw(val) {} - u8 raw = 0; RegF& operator=(const u8& rhs) { raw |= ((rhs >> 7) << 7); @@ -52,6 +31,24 @@ struct RegF { bool halfcarry() { return (raw >> 5) & 1; } bool carry() { return (raw >> 4) & 1; } + void reset() { + zero(false); + negative(false); + halfcarry(false); + carry(false); + } + + void set(bool z, bool n, bool hc, bool ca) { + zero(z); + negative(n); + halfcarry(hc); + carry(ca); + } + + u8& get() { return raw; } +private: + u8 raw = 0; + void zero(const bool& rhs) { raw &= ~0xF; raw |= (rhs << 7); @@ -76,16 +73,111 @@ struct RegF { struct Registers { REGIMPL(u8, A, RegF, F); REGIMPL(u8, B, u8, C); - REGIMPL(u8, C, u8, E); - REGIMPL(u8, D, u8, L); - u16 PC = 0, SP = 0; + REGIMPL(u8, D, u8, E); + REGIMPL(u8, H, u8, L); + u16 pc = 0, sp = 0; + + u8& a() { return AF.A; } + RegF& f() { return AF.F; } + u8& b() { return BC.B; } + u8& c() { return BC.C; } + u8& d() { return DE.D; } + u8& e() { return DE.E; } + u8& h() { return HL.H; } + u8& l() { return HL.L; } + + u16& af() { return AF.raw; } + u16& bc() { return BC.raw; } + u16& de() { return DE.raw; } + u16& hl() { return HL.raw; } }; struct Cpu { Cpu(); void Step(Mem&); private: - void DecodeAndExecute(u8); + void FetchDecodeExecute(Mem& mem); Registers regs; + + template + u16 GetR16(u8 bits) { + static_assert(group > 0 && group < 3, "Invalid GetR16 group"); + if constexpr (group == 1) { + switch(bits & 3) { + case 0: return regs.bc(); + case 1: return regs.de(); + case 2: return regs.hl(); + case 3: return regs.sp; + } + } else if constexpr (group == 2) { + switch(bits & 3) { + case 0: return regs.bc(); + case 1: return regs.de(); + case 2: return regs.hl()++; + case 3: return regs.hl()--; + } + } else if constexpr (group == 3) { + switch(bits & 3) { + case 0: return regs.bc(); + case 1: return regs.de(); + case 2: return regs.hl(); + case 3: return regs.af(); + } + } + } + + template + void SetR16(u8 bits, u16 val) { + static_assert(group > 0 && group < 3, "Invalid SetR16 group"); + if constexpr (group == 1) { + switch(bits & 3) { + case 0: regs.bc() = val; break; + case 1: regs.de() = val; break; + case 2: regs.hl() = val; break; + case 3: regs.sp = val; break; + } + } else if constexpr (group == 2) { + switch(bits & 3) { + case 0: regs.bc() = val; break; + case 1: regs.de() = val; break; + case 2: regs.hl() = val; regs.hl()++; break; + case 3: regs.hl() = val; regs.hl()--; break; + } + } else if constexpr (group == 3) { + switch(bits & 3) { + case 0: regs.bc() = val; break; + case 1: regs.de() = val; break; + case 2: regs.hl() = val; break; + case 3: regs.af() = val; break; + } + } + } + + u8 GetR8(u8 bits, Mem& mem) { + switch(bits & 7) { + case 0: return regs.b(); + case 1: return regs.c(); + case 2: return regs.d(); + case 3: return regs.e(); + case 4: return regs.h(); + case 5: return regs.l(); + case 6: return mem.Read8(regs.hl()); + case 7: return regs.a(); + } + return 0; + } + + void SetR8(u8 bits, u8 val, Mem& mem) { + switch(bits & 7) { + case 0: regs.b() = val; break; + case 1: regs.c() = val; break; + case 2: regs.d() = val; break; + case 3: regs.e() = val; break; + case 4: regs.h() = val; break; + case 5: regs.l() = val; break; + case 6: return mem.Write8(regs.hl(), val); + case 7: regs.a() = val; break; + } + } }; } diff --git a/src/core/gb/Mem.cpp b/src/core/gb/Mem.cpp index 37dd5464..4b86f174 100644 --- a/src/core/gb/Mem.cpp +++ b/src/core/gb/Mem.cpp @@ -2,20 +2,27 @@ #include #include #include +#include -namespace natsukashii::core { -template -T ReadCart(const std::unique_ptr& cart, u16 addr) { - if constexpr(sizeof(T) == 1) return cart->Read8(addr); - else if constexpr(sizeof(T) == 2) return cart->Read16(addr); - else if constexpr(sizeof(T) == 4) return cart->Read32(addr); +namespace natsukashii::gb::core { +Mem::Mem() { + auto data = toml::parse("config.toml"); + auto gb = toml::find(data, "gb"); + auto bootromPath = toml::find(gb, "bootrom"); + + LoadBootROM(bootromPath); } -template -void WriteCart(const std::unique_ptr& cart, u16 addr, T val) { - if constexpr(sizeof(T) == 1) cart->Write8(addr, val); - else if constexpr(sizeof(T) == 2) cart->Write16(addr, val); - else if constexpr(sizeof(T) == 4) cart->Write32(addr, val); +void Mem::LoadBootROM(const std::string &filename) { + std::ifstream file(filename, std::ios::binary); + file.unsetf(std::ios::skipws); + + if(!file.is_open()) { + util::panic("Unable to open {}!", filename); + } + + file.read(reinterpret_cast(bootrom), 256); + file.close(); } void Mem::LoadROM(const std::string& filename) { @@ -37,45 +44,49 @@ void Mem::LoadROM(const std::string& filename) { std::istream_iterator()); file.close(); - - cart = std::make_unique(rom); + switch(rom[0x147]) { + case 0: + cart = std::make_unique(rom); + break; + default: + util::panic("Unimplemented cartridge type {:02X}!", rom[0x147]); + } } -template -T Mem::Read(u16 addr) { +u8 Mem::Read8(u16 addr) { switch(addr) { - case ROM_RNG00: return io.BootROMMapped() ? bootrom[addr] : ReadCart(cart, addr); - case ROM_RNGNN: return ReadCart(cart, addr); + case ROM_RNG00: return io.BootROMMapped() ? bootrom[addr] : cart->Read(addr); + case ROM_RNGNN: return cart->Read(addr); default: util::panic("[READ] Unimplemented addr: {:04X}", addr); } return 0; } -template u8 Mem::Read(u16); -template u16 Mem::Read(u16); -template u32 Mem::Read(u16); - -template -void Mem::Write(u16 addr, T val) { +void Mem::Write8(u16 addr, u8 val) { switch(addr) { - case ROM_RNG00: case ROM_RNGNN: WriteCart(cart, addr, val); + case ROM_RNG00: case ROM_RNGNN: cart->Write(addr, val); default: util::panic("[WRITE] Unimplemented addr: {:04X}", addr); } } -template void Mem::Write(u16, u8); -template void Mem::Write(u16, u16); -template void Mem::Write(u16, u32); - -template -T Mem::Consume(u16& pc) { - T result = Read(pc); - pc += sizeof(T); +u8 Mem::Consume8(u16& pc) { + u8 result = Read8(pc); + pc += 1; return result; } -template u8 Mem::Consume(u16&); -template u16 Mem::Consume(u16&); -template u32 Mem::Consume(u16&); +u16 Mem::Read16(u16 addr) { + return ((u16)Read8(addr) << 8) | Read8(addr + 1); +} + +void Mem::Write16(u16 addr, u16 val) { + Write8(addr, val >> 8); + Write8(addr + 1, val & 0xff); +} + +u16 Mem::Consume16(u16& pc) { + u8 hi = Consume8(pc); + return ((u16)hi << 8) | Consume8(pc); +} } diff --git a/src/core/gb/Mem.hpp b/src/core/gb/Mem.hpp index 95b6d0ec..e04a081a 100644 --- a/src/core/gb/Mem.hpp +++ b/src/core/gb/Mem.hpp @@ -3,10 +3,10 @@ #include #include #include -#include -#include +#include "memory_regions.hpp" +#include "mbc.hpp" -namespace natsukashii::core { +namespace natsukashii::gb::core { struct IO { [[nodiscard]] bool BootROMMapped() const { return ff50 != 1; } private: @@ -14,15 +14,16 @@ private: }; struct Mem { - Mem() = default; + Mem(); void LoadROM(const std::string& filename); - template - T Read(u16 addr); - template - void Write(u16 addr, T val); - template - T Consume(u16& pc); + u8 Read8(u16 addr); + void Write8(u16 addr, u8 val); + u8 Consume8(u16& pc); + u16 Read16(u16 addr); + void Write16(u16 addr, u16 val); + u16 Consume16(u16& pc); private: + void LoadBootROM(const std::string& filename); std::unique_ptr cart; IO io{}; u8 bootrom[BOOTROM_SIZE]{}; diff --git a/src/core/gb/mbc.cpp b/src/core/gb/mbc.cpp index f3a4dc75..1fd22424 100644 --- a/src/core/gb/mbc.cpp +++ b/src/core/gb/mbc.cpp @@ -2,23 +2,14 @@ #include #include -namespace natsukashii::core { +namespace natsukashii::gb::core { NoMBC::NoMBC(std::vector rom) : data(std::move(rom)) {} -u8 NoMBC::Read8(u16 addr) { +u8 NoMBC::Read(u16 addr) { return data[addr]; } -void NoMBC::Write8(u16 addr, u8 val) { - util::panic("Writing to a NoMBC cartridge is not allowed!"); +void NoMBC::Write(u16 addr, u8 val) { + util::panic("Writing to a NoMBC cartridge is not allowed! (Addr: {:04X})", addr); } - -u16 NoMBC::Read16(u16 addr) { - return ((u16)Read8(addr) << 8) | Read8(addr + 1); } - -void NoMBC::Write16(u16 addr, u16 val) { - Write8(addr, val >> 8); - Write8(addr + 1, val & 0xff); -} -} \ No newline at end of file diff --git a/src/core/gb/mbc.hpp b/src/core/gb/mbc.hpp index e06d1d16..3ebf8e7c 100644 --- a/src/core/gb/mbc.hpp +++ b/src/core/gb/mbc.hpp @@ -1,23 +1,18 @@ #pragma once #include -#include #include -namespace natsukashii::core { +namespace natsukashii::gb::core { struct Cartridge { - virtual u8 Read8(u16 addr); - virtual u16 Read16(u16 addr); - virtual void Write8(u16 addr, u8 val); - virtual void Write16(u16 addr, u16 val); + virtual u8 Read(u16 addr) { return 0; } + virtual void Write(u16 addr, u8 val) {} }; struct NoMBC : public Cartridge { explicit NoMBC(std::vector rom); - u8 Read8(u16 addr) override; - void Write8(u16 addr, u8 val) override; - u16 Read16(u16 addr) override; - void Write16(u16 addr, u16 val) override; + u8 Read(u16 addr) override; + void Write(u16 addr, u8 val) override; private: std::vector data{}; }; -} \ No newline at end of file +} diff --git a/src/core/gb/memory_regions.hpp b/src/core/gb/memory_regions.hpp index 580a25cf..3e06cc04 100644 --- a/src/core/gb/memory_regions.hpp +++ b/src/core/gb/memory_regions.hpp @@ -3,21 +3,21 @@ #define BOOTROM_SIZE 512 #define ROM_STR00 0x0000 -#define ROM_END00 ROM_STR00 + 16 KiB - 1 +#define ROM_END00 ROM_STR00 + 0x3fff #define ROM_STRNN ROM_END00 + 1 -#define ROM_ENDNN ROM_STRNN + 16 KiB - 1 +#define ROM_ENDNN ROM_STRNN + 0x3fff #define ROM_RNG00 ROM_STR00 ... ROM_END00 #define ROM_RNGNN ROM_STRNN ... ROM_ENDNN #define VRAM_START ROM_ENDNN + 1 -#define VRAM_END VRAM_START + 8 KiB - 1 +#define VRAM_END VRAM_START + 0x1fff #define VRAM_RANGE VRAM_START ... VRAM_END #define EXTRAM_START VRAM_END + 1 -#define EXTRAM_END EXTRAM_START + 8 KiB - 1 +#define EXTRAM_END EXTRAM_START + 0x1fff #define EXTRAM_RANGE EXTRAM_START ... EXTRAM_END #define WRAM_STR00 EXTRAM_END + 1 -#define WRAM_END00 WRAM_STR00 + 4 KiB - 1 +#define WRAM_END00 WRAM_STR00 + 0xfff #define WRAM_STRNN WRAM_END00 + 1 -#define WRAM_ENDNN WRAM_STRNN + 4 KiB - 1 +#define WRAM_ENDNN WRAM_STRNN + 0xfff #define WRAM_RNG00 WRAM_STR00 ... WRAM_END00 #define WRAM_RNGNN WRAM_STRNN ... WRAM_ENDNN #define OAM_START WRAM_ENDNN + 1 diff --git a/src/core/n64/CMakeLists.txt b/src/core/n64/CMakeLists.txt new file mode 100644 index 00000000..2319dc41 --- /dev/null +++ b/src/core/n64/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.20) +project(n64 CXX) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +#find_package(mio REQUIRED) +find_package(fmt REQUIRED) +#find_package(toml11 REQUIRED) + +add_library(n64 Core.hpp Core.cpp Cpu.hpp Cpu.cpp Mem.cpp Mem.hpp memory_regions.hpp) +target_include_directories(n64 PRIVATE . ..) +target_link_libraries(n64 PUBLIC fmt::fmt) diff --git a/src/core/n64/Core.cpp b/src/core/n64/Core.cpp new file mode 100644 index 00000000..e649ea76 --- /dev/null +++ b/src/core/n64/Core.cpp @@ -0,0 +1,9 @@ +#include + +namespace natsukashii::n64::core { +Core::Core(const std::string& rom) {} + +void Core::Run() { + +} +} diff --git a/src/core/n64/Core.hpp b/src/core/n64/Core.hpp new file mode 100644 index 00000000..75f105c0 --- /dev/null +++ b/src/core/n64/Core.hpp @@ -0,0 +1,12 @@ +#pragma once +#include +#include +#include +#include + +namespace natsukashii::n64::core { +struct Core : natsukashii::core::BaseCore { + Core(const std::string&); + virtual void Run() override; +}; +} diff --git a/src/core/n64/Cpu.cpp b/src/core/n64/Cpu.cpp new file mode 100644 index 00000000..44ff5d7a --- /dev/null +++ b/src/core/n64/Cpu.cpp @@ -0,0 +1 @@ +#include diff --git a/src/core/n64/Cpu.hpp b/src/core/n64/Cpu.hpp new file mode 100644 index 00000000..13eb8b61 --- /dev/null +++ b/src/core/n64/Cpu.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace natsukashii::n64::core { +struct Cpu { + +}; +} diff --git a/src/core/n64/Mem.cpp b/src/core/n64/Mem.cpp new file mode 100644 index 00000000..bf1652ed --- /dev/null +++ b/src/core/n64/Mem.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +namespace natsukashii::n64::core { +Mem::Mem() { + rdram.resize(RDRAM_SIZE); + sram.resize(SRAM_SIZE); +} + +void Mem::LoadROM(const std::string& filename) { + std::ifstream file(filename, std::ios::binary); + file.unsetf(std::ios::skipws); + + if(!file.is_open()) { + util::panic("Unable to open {}!", filename); + } + + file.seekg(std::ios::end); + auto size = file.tellg(); + file.seekg(std::ios::beg); + + std::vector rom; + rom.reserve(size); + rom.insert(rom.begin(), + std::istream_iterator(file), + std::istream_iterator()); + + file.close(); + util::SwapN64Rom(size, rom.data()); + memcpy(dmem, rom.data(), 0x1000); +} +} \ No newline at end of file diff --git a/src/core/n64/Mem.hpp b/src/core/n64/Mem.hpp new file mode 100644 index 00000000..568e4c81 --- /dev/null +++ b/src/core/n64/Mem.hpp @@ -0,0 +1,16 @@ +#pragma once +#include +#include +#include + +namespace natsukashii::n64::core { +struct Mem { + Mem(); + void LoadROM(const std::string&); +private: + std::vector cart, rdram, sram; + u8 dmem[DMEM_SIZE]{}, imem[IMEM_SIZE]{}, pif_ram[PIF_RAM_SIZE]{}; + u8 pif_bootrom[PIF_BOOTROM_SIZE]{}; + size_t rom_mask; +}; +} diff --git a/src/core/n64/memory_regions.hpp b/src/core/n64/memory_regions.hpp new file mode 100644 index 00000000..8036044f --- /dev/null +++ b/src/core/n64/memory_regions.hpp @@ -0,0 +1,14 @@ +#pragma once + +#define RDRAM_SIZE 0x800000 +#define RDRAM_DSIZE (RDRAM_SIZE - 1) +#define SRAM_SIZE 0x8000000 +#define SRAM_DSIZE (SRAM_SIZE - 1) +#define DMEM_SIZE 0x1000 +#define DMEM_DSIZE (DMEM_SIZE - 1) +#define IMEM_SIZE 0x1000 +#define IMEM_DSIZE (IMEM_SIZE - 1) +#define PIF_RAM_SIZE 0x40 +#define PIF_RAM_DSIZE (PIF_RAM_SIZE - 1) +#define PIF_BOOTROM_SIZE 0x7C0 +#define PIF_BOOTROM_DSIZE (PIF_BOOTROM_SIZE - 1) \ No newline at end of file diff --git a/src/core/util.hpp b/src/core/util.hpp index fc0e3611..a9ce5619 100644 --- a/src/core/util.hpp +++ b/src/core/util.hpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace natsukashii::util { template @@ -36,4 +37,62 @@ T BitSlice(const T& num, int start, int end) { return (num >> start) & ((1 << correctedEnd) - 1); } +template +auto GetSwapFunc(T num) -> T { + if constexpr(sizeof(T) == 2) { + if constexpr(FromHToBE) + return htobe16(num); + return be16toh(num); + } else if constexpr(sizeof(T) == 4) { + if constexpr(FromHToBE) + return htobe32(num); + return be32toh(num); + } else if constexpr(sizeof(T) == 8) { + if constexpr(FromHToBE) + return htobe32(num); + return be32toh(num); + } +} + +template +inline T ReadAccess(u8* data, u32 index) { + static_assert(sizeof(T) != 2 && sizeof(T) != 4 && sizeof(T) != 8); + T result = 0; + memcpy(&result, &data[index], sizeof(T)); + return GetSwapFunc(result); +} + +template +inline void WriteAccess(u8* data, u32 index, T val) { + static_assert(sizeof(T) != 2 && sizeof(T) != 4 && sizeof(T) != 8); + T temp = GetSwapFunc(val); + memcpy(&data[index], &temp, sizeof(T)); +} + +#define Z64 0x80371240 +#define N64 0x40123780 +#define V64 0x37804012 + +inline void SwapN64Rom(size_t size, u8* data) { + u32 endianness; + memcpy(&endianness, data, 4); + endianness = be32toh(endianness); + switch(endianness) { + case V64: + for(int i = 0; i < size; i += 2) { + u16 original = *(u16*)&data[i]; + *(u16*)&data[i] = bswap_16(original); + } + break; + case N64: + for(int i = 0; i < size; i += 4) { + u32 original = *(u32*)&data[i]; + *(u32*)&data[i] = bswap_32(original); + } + break; + case Z64: break; + default: + panic("Unrecognized rom format! Make sure this is a valid Nintendo 64 ROM dump!\n"); + } +} } diff --git a/src/frontend/qt/CMakeLists.txt b/src/frontend/qt/CMakeLists.txt index 49624d70..d0e43b19 100644 --- a/src/frontend/qt/CMakeLists.txt +++ b/src/frontend/qt/CMakeLists.txt @@ -9,6 +9,8 @@ set(CMAKE_AUTOUIC ON) find_package(Qt5 COMPONENTS Widgets REQUIRED) -add_library(frontend Frontend.hpp) +add_library(frontend Frontend.hpp Frontend.cpp) + +target_compile_definitions(frontend PUBLIC FRONTEND_QT) target_include_directories(frontend PUBLIC .) target_link_libraries(frontend PUBLIC Qt5::Widgets) diff --git a/src/frontend/sdl/CMakeLists.txt b/src/frontend/sdl/CMakeLists.txt index 1d2bd5a5..89a36a98 100644 --- a/src/frontend/sdl/CMakeLists.txt +++ b/src/frontend/sdl/CMakeLists.txt @@ -6,5 +6,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(SDL2 REQUIRED) add_library(frontend Frontend.cpp Frontend.hpp) -target_include_directories(frontend PUBLIC . ../../core) +target_compile_definitions(frontend PUBLIC FRONTEND_SDL) +target_include_directories(frontend PUBLIC . ../../core ../../core/gb ../../core/n64) target_link_libraries(frontend PUBLIC SDL2) diff --git a/src/frontend/sdl/Frontend.cpp b/src/frontend/sdl/Frontend.cpp index a2c9bac3..2637b4c4 100644 --- a/src/frontend/sdl/Frontend.cpp +++ b/src/frontend/sdl/Frontend.cpp @@ -1,4 +1,8 @@ #include +#include +#include +#include +#include namespace natsukashii::frontend { App::~App() { @@ -7,16 +11,25 @@ App::~App() { SDL_Quit(); } -App::App() { +App::App(const std::string& rom) { SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); window = SDL_CreateWindow("natukashii", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN); renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); id = SDL_GetWindowID(window); + + std::string ext{rom.begin() + rom.find_first_of('.') + 1, rom.end()}; + std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ + return std::tolower(c); + }); + + if(ext == "gb") core = std::make_unique(rom); + else if(ext == "n64") core = std::make_unique(rom); + else util::panic("Unimplemented core!"); } void App::Run() { while(!quit) { - gb.Run(); + core->Run(); SDL_Event event; SDL_PollEvent(&event); diff --git a/src/frontend/sdl/Frontend.hpp b/src/frontend/sdl/Frontend.hpp index dd9058c7..25e23844 100644 --- a/src/frontend/sdl/Frontend.hpp +++ b/src/frontend/sdl/Frontend.hpp @@ -1,17 +1,21 @@ #pragma once #include -#include +#include +#include +#include +#include namespace natsukashii::frontend { +using namespace natsukashii::core; struct App { ~App(); - App(); + App(const std::string&); void Run(); private: SDL_Window *window = nullptr; SDL_Renderer *renderer = nullptr; Uint32 id; bool quit = false; - core::Core gb; + std::unique_ptr core; }; } diff --git a/src/main.cpp b/src/main.cpp index d7fc0e98..c6026fcd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,20 @@ -#include +#ifdef FRONTEND_QT +#include +#elifdef FRONTEND_SDL +#include +#endif -int main() { +#include + +int main(int argc, char* argv[]) { +#ifdef FRONTEND_SDL + if(argc < 2) { + natsukashii::util::panic("Usage: natsukashii [rom]"); + } + natsukashii::frontend::App app(argv[1]); +#else natsukashii::frontend::App app; +#endif app.Run(); return 0; }