Compare commits
8
Commits
cf0378719c
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6957c8eece | ||
|
|
6378f5715d | ||
|
|
b3fd9f00b8 | ||
|
|
0c831f22d0 | ||
|
|
22840cfe92 | ||
|
|
4791f5b6cf | ||
|
|
e6e33eef17 | ||
|
|
44dea81bf2 |
@@ -7,10 +7,11 @@ Experimental N64 emulator
|
||||
- [**Panda3DS**](https://github.com/wheremyfoodat/Panda3DS): A new HLE Nintendo 3DS emulator
|
||||
- [**Dust**](https://github.com/kelpsyberry/dust): Nintendo DS emulator for desktop devices and the web, with debugging features and a focus on accuracy
|
||||
- [**SkyEmu**](https://github.com/skylersaleh/SkyEmu): A low-level GameBoy, GameBoy Color, GameBoy Advance and Nintendo DS emulator that is designed to be easy to use, cross platform and accurate
|
||||
- [**NanoBoyAdvance**](https://github.com/nba-emu/NanoBoyAdvance): A Game Boy Advance emulator focusing on hardware research and cycle-accurate emulation
|
||||
- [**NanoBoyAdvance**](https://codeberg.org/nba-emu/NanoBoyAdvance): A Game Boy Advance emulator focusing on hardware research and cycle-accurate emulation
|
||||
- [**melonDS**](https://github.com/melonDS-emu/melonDS): "DS emulator, sorta"; a Nintendo DS emulator focused on accuracy and ease-of-use
|
||||
- [**n64-emu**](https://github.com/kmc-jp/n64-emu): Experimental N64 emulator
|
||||
- [**ares**](https://github.com/ares-emulator/ares): ares is a multi-system emulator that began development on October 14th, 2004. It focuses on accuracy and preservation.
|
||||
- [**solstice**](https://codeberg.org/hazelwiss/solstice): A WIP gamecube/wii/wii-u emulator
|
||||
|
||||
## Build instructions:
|
||||
First clone the repository: `git clone https://git.sr.ht/~irisz64/kaizen`
|
||||
@@ -62,7 +63,7 @@ This list will probably grow with time!
|
||||
- [ares](https://github.com/ares-emulator/ares) for being the cleanest and most accurate Nintendo 64 emulator out there. It served as a reference time and time again. Especially regarding FPU accuracy.
|
||||
- [Dillonb](https://github.com/Dillonb) and [KieronJ](https://github.com/KieronJ) for bearing with me and my recurring brainfarts, and for the support :heart:
|
||||
- [WhoBrokeTheBuild](https://github.com/WhoBrokeTheBuild) for the shader that allows letterboxing :rocket:
|
||||
- [Kelpsy](https://github.com/kelpsyberry), [fleroviux](https://github.com/fleroviux), [Kim-Dewelski](https://github.com/Kim-Dewelski), [Peach](https://github.com/wheremyfoodat/),
|
||||
- [Kelpsy](https://github.com/kelpsyberry), [fleroviux](https://codeberg.org/fleroviux), [hazelwiss](https://codeberg.org/hazelwiss/), [Peach](https://github.com/wheremyfoodat/),
|
||||
[kivan](https://github.com/kivan117), [liuk](https://github.com/liuk7071) and [Skyler](https://github.com/skylersaleh) for the general support and motivation :heart:
|
||||
- [Spec](https://github.com/spec-chum/) for being an awesome person in general :heart:
|
||||
|
||||
|
||||
Vendored
+16
@@ -12,6 +12,22 @@ static inline std::vector<u8> read_file_binary(const std::string &path) {
|
||||
return {std::istreambuf_iterator{file}, {}};
|
||||
}
|
||||
|
||||
static inline std::vector<u8> read_file_binary(const std::string &path, uint32_t size, uint32_t offset = 0) {
|
||||
FILE *file = fopen(path.c_str(), "rb");
|
||||
if (!file)
|
||||
return {};
|
||||
|
||||
std::vector<u8> res;
|
||||
|
||||
fseek(file, offset, SEEK_SET);
|
||||
|
||||
res.resize(size);
|
||||
fread(res.data(), 1, size, file);
|
||||
fclose(file);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static inline void write_file_binary(const std::vector<u8> &data, const std::string &path) {
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
std::copy(data.begin(), data.end(), std::ostreambuf_iterator{file});
|
||||
|
||||
@@ -32,13 +32,9 @@ void Core::LoadROM(const std::string &romFilename) {
|
||||
const bool isArchive = std::ranges::any_of(archive_types, [&extension](const auto &e) { return e == extension; });
|
||||
|
||||
auto rom = Mem::LoadROM(isArchive, romPath);
|
||||
GameDB::match(rom);
|
||||
|
||||
mem->rom = rom;
|
||||
if (rom.gameNameDB.empty()) {
|
||||
rom.gameNameDB = fs::path(romPath).stem().string();
|
||||
mem->rom.gameNameDB = rom.gameNameDB;
|
||||
}
|
||||
mem->mmio.vi.isPal = Mem::IsROMPAL(rom);
|
||||
mem->mmio.vi.isPal = Mem::IsROMPAL(rom.header);
|
||||
mem->mmio.si.pif.InitDevices(mem->rom.saveType);
|
||||
mem->mmio.si.pif.mempakPath = romPath;
|
||||
mem->mmio.si.pif.LoadEeprom(mem->rom.saveType, romPath);
|
||||
@@ -57,6 +53,7 @@ u32 Core::StepCPU() {
|
||||
return interpreter.ExecuteCached() + regs.PopStalledCycles();
|
||||
|
||||
ircolib::panic("Invalid CPU type?");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Core::StepRSP(const u32 cpuCycles) {
|
||||
|
||||
+23
-15
@@ -48,43 +48,51 @@ const char *GameDB::regionCodeToReadable(char r) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string GameDB::match(ROM &rom) {
|
||||
std::string result = "";
|
||||
GameDBMatchResults GameDB::match(const ROMHeader &header) {
|
||||
GameDBMatchResults result;
|
||||
|
||||
char codeFromHeader[] = {
|
||||
(char)header.categoryCode,
|
||||
header.uniqueCode[0],
|
||||
header.uniqueCode[1],
|
||||
'\0',
|
||||
};
|
||||
|
||||
for (const auto &[code, regions, saveType, name] : gamedb) {
|
||||
bool matchesRegion = false;
|
||||
|
||||
if (code != rom.code)
|
||||
if (code != codeFromHeader)
|
||||
continue;
|
||||
|
||||
for (int j = 0; j < regions.size(); j++) {
|
||||
if (j == regions.size() - 1)
|
||||
result += regionCodeToReadable(regions[j]);
|
||||
result.regions += regionCodeToReadable(regions[j]);
|
||||
else
|
||||
result += std::string(regionCodeToReadable(regions[j])) + ", ";
|
||||
result.regions += std::string(regionCodeToReadable(regions[j])) + ", ";
|
||||
|
||||
if (regions[j] == rom.header.countryCode)
|
||||
if (regions[j] == header.countryCode)
|
||||
matchesRegion = true;
|
||||
}
|
||||
|
||||
if (matchesRegion) {
|
||||
rom.saveType = saveType;
|
||||
rom.gameNameDB = name;
|
||||
result.saveType = saveType;
|
||||
result.resolvedName = name;
|
||||
return result;
|
||||
}
|
||||
|
||||
ircolib::info("Matched code for {}, but not region! Game supposedly exists in regions [{}] but this image has "
|
||||
"region {}",
|
||||
name, regions, rom.header.countryCode);
|
||||
rom.saveType = saveType;
|
||||
rom.gameNameDB = name;
|
||||
name, regions, header.countryCode);
|
||||
result.saveType = saveType;
|
||||
result.resolvedName = name;
|
||||
return result;
|
||||
}
|
||||
|
||||
ircolib::info("Did not match any Game DB entries. Code: {} Region: {}", rom.code, rom.header.countryCode);
|
||||
ircolib::info("Did not match any Game DB entries. Code: {} Region: {}", codeFromHeader, header.countryCode);
|
||||
|
||||
rom.gameNameDB = "";
|
||||
rom.saveType = SAVE_NONE;
|
||||
return "Unknown";
|
||||
result.resolvedName = "";
|
||||
result.saveType = SAVE_NONE;
|
||||
result.regions = "Unknown";
|
||||
return result;
|
||||
}
|
||||
} // namespace n64
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace n64 {
|
||||
enum SaveType { SAVE_NONE, SAVE_EEPROM_4k, SAVE_EEPROM_16k, SAVE_FLASH_1m, SAVE_SRAM_256k };
|
||||
struct ROM;
|
||||
struct ROMHeader;
|
||||
|
||||
struct GameDBEntry {
|
||||
std::string code;
|
||||
@@ -12,8 +12,13 @@ struct GameDBEntry {
|
||||
const char *name;
|
||||
};
|
||||
|
||||
struct GameDBMatchResults {
|
||||
SaveType saveType;
|
||||
std::string regions, resolvedName;
|
||||
};
|
||||
|
||||
namespace GameDB {
|
||||
std::string match(ROM &);
|
||||
GameDBMatchResults match(const ROMHeader &);
|
||||
const char *regionCodeToReadable(char);
|
||||
} // namespace GameDB
|
||||
|
||||
|
||||
+35
-13
@@ -90,6 +90,31 @@ FORCE_INLINE void SetROMCIC(u32 checksum, ROM &rom) {
|
||||
}
|
||||
}
|
||||
|
||||
ROMHeader Mem::ReadROMHeader(bool isArchive, const std::string &filename) {
|
||||
ROMHeader res;
|
||||
|
||||
u32 endianness;
|
||||
|
||||
std::vector<u8> buf{};
|
||||
if (isArchive) {
|
||||
buf = Util::ExtractROMHeaderFromArchive(filename);
|
||||
} else {
|
||||
buf = Util::OpenROMHeader(filename);
|
||||
}
|
||||
|
||||
endianness = std::byteswap(ircolib::read_access<u32>(buf, 0));
|
||||
Util::SwapN64Rom<true>(buf, endianness);
|
||||
memcpy(&res, buf.data(), sizeof(ROMHeader));
|
||||
|
||||
res.clockRate = std::byteswap(res.clockRate);
|
||||
res.programCounter = std::byteswap(res.programCounter);
|
||||
res.release = std::byteswap(res.release);
|
||||
res.checkCode = std::byteswap(res.checkCode);
|
||||
res.reserved = std::byteswap(res.reserved);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ROM Mem::LoadROM(const bool isArchive, const std::string &filename) {
|
||||
ROM res;
|
||||
res.cart.resize(CART_SIZE);
|
||||
@@ -99,7 +124,7 @@ ROM Mem::LoadROM(const bool isArchive, const std::string &filename) {
|
||||
size_t sizeAdjusted;
|
||||
std::vector<u8> buf{};
|
||||
if (isArchive) {
|
||||
buf = Util::OpenArchive(filename, sizeAdjusted);
|
||||
buf = Util::ExtractROMFromArchive(filename, sizeAdjusted);
|
||||
} else {
|
||||
buf = Util::OpenROM(filename, sizeAdjusted);
|
||||
}
|
||||
@@ -108,31 +133,27 @@ ROM Mem::LoadROM(const bool isArchive, const std::string &filename) {
|
||||
Util::SwapN64Rom<true>(buf, endianness);
|
||||
|
||||
std::ranges::copy(buf, res.cart.begin());
|
||||
res.mask = sizeAdjusted - 1;
|
||||
memcpy(&res.header, buf.data(), sizeof(ROMHeader));
|
||||
}
|
||||
memcpy(res.gameNameCart, res.header.imageName, sizeof(res.header.imageName));
|
||||
|
||||
res.header.clockRate = std::byteswap(res.header.clockRate);
|
||||
res.header.programCounter = std::byteswap(res.header.programCounter);
|
||||
res.header.release = std::byteswap(res.header.release);
|
||||
res.header.unknown = std::byteswap(res.header.unknown);
|
||||
res.header.unknown2 = std::byteswap(res.header.unknown2);
|
||||
res.header.checkCode = std::byteswap(res.header.checkCode);
|
||||
res.header.reserved = std::byteswap(res.header.reserved);
|
||||
|
||||
res.code[0] = res.header.categoryCode;
|
||||
res.code[1] = res.header.uniqueCode[0];
|
||||
res.code[2] = res.header.uniqueCode[1];
|
||||
res.code[3] = '\0';
|
||||
|
||||
for (int i = sizeof(res.header.imageName) - 1; res.gameNameCart[i] == ' '; i--) {
|
||||
res.gameNameCart[i] = '\0';
|
||||
auto [saveType, _, gameNameDB] = GameDB::match(res.header);
|
||||
res.saveType = saveType;
|
||||
if (gameNameDB.empty()) {
|
||||
gameNameDB = fs::path(filename).stem().string();
|
||||
}
|
||||
res.gameNameDB = gameNameDB;
|
||||
|
||||
const u32 checksum = SDL_crc32(0, &res.cart[0x40], 0x9C0);
|
||||
SetROMCIC(checksum, res);
|
||||
endianness = std::byteswap(ircolib::read_access<u32>(res.cart, 0));
|
||||
Util::SwapN64Rom(res.cart, endianness);
|
||||
res.pal = IsROMPAL(res);
|
||||
res.pal = IsROMPAL(res.header);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -491,6 +512,7 @@ u8 Mem::BackupRead<u8>(const u32 addr) {
|
||||
}
|
||||
default:
|
||||
ircolib::panic("Backup read word with unknown save type");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,15 @@
|
||||
|
||||
namespace n64 {
|
||||
struct ROMHeader {
|
||||
u8 initialValues[4];
|
||||
u8 unused;
|
||||
u8 initialValues[3];
|
||||
u32 clockRate;
|
||||
u32 programCounter;
|
||||
u32 release;
|
||||
u64 unknown;
|
||||
u64 unknown2;
|
||||
u64 checkCode;
|
||||
u64 reserved;
|
||||
char imageName[20];
|
||||
char unknown3[7];
|
||||
u8 reserved2[7];
|
||||
u8 categoryCode;
|
||||
char uniqueCode[2];
|
||||
u8 countryCode;
|
||||
@@ -26,11 +27,8 @@ struct ROMHeader {
|
||||
|
||||
struct ROM {
|
||||
bool pal;
|
||||
char gameNameCart[20];
|
||||
char code[4];
|
||||
ROMHeader header;
|
||||
SaveType saveType = SAVE_NONE;
|
||||
size_t mask;
|
||||
CICType cicType;
|
||||
std::vector<u8> cart;
|
||||
std::string gameNameDB;
|
||||
@@ -80,6 +78,7 @@ struct Mem {
|
||||
void Reset();
|
||||
void LoadSRAM(SaveType, fs::path);
|
||||
static ROM LoadROM(bool, const std::string &);
|
||||
static ROMHeader ReadROMHeader(bool, const std::string &);
|
||||
|
||||
[[nodiscard]] auto GetRDRAMPtr() -> u8 * { return mmio.rdp.rdram.data(); }
|
||||
|
||||
@@ -135,9 +134,9 @@ struct Mem {
|
||||
std::string sramPath{};
|
||||
mio::mmap_sink saveData{};
|
||||
|
||||
[[nodiscard]] static FORCE_INLINE bool IsROMPAL(ROM &rom) {
|
||||
static constexpr char pal_codes[] = {'D', 'F', 'I', 'P', 'S', 'U', 'X', 'Y'};
|
||||
return std::ranges::any_of(pal_codes, [&rom](char a) { return rom.cart[0x3d] == a; });
|
||||
[[nodiscard]] static FORCE_INLINE bool IsROMPAL(ROMHeader &header) {
|
||||
return std::ranges::any_of(std::array{'D', 'F', 'I', 'P', 'S', 'U', 'X', 'Y', 'Z'},
|
||||
[&header](uint8_t a) { return header.countryCode == a; });
|
||||
}
|
||||
};
|
||||
} // namespace n64
|
||||
|
||||
@@ -26,24 +26,23 @@ void PI::Reset() {
|
||||
}
|
||||
|
||||
bool PI::WriteLatch(u32 value) {
|
||||
if (ioBusy) {
|
||||
if (ioBusy)
|
||||
return false;
|
||||
} else {
|
||||
ioBusy = true;
|
||||
latch = value;
|
||||
Scheduler::GetInstance().EnqueueRelative(100, PI_BUS_WRITE_COMPLETE);
|
||||
return true;
|
||||
}
|
||||
|
||||
ioBusy = true;
|
||||
latch = value;
|
||||
Scheduler::GetInstance().EnqueueRelative(100, PI_BUS_WRITE_COMPLETE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PI::ReadLatch() {
|
||||
n64::Registers ®s = n64::Core::GetRegs();
|
||||
if (ioBusy) [[unlikely]] {
|
||||
ioBusy = false;
|
||||
regs.CpuStall(Scheduler::GetInstance().Remove(PI_BUS_WRITE_COMPLETE));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (!ioBusy) [[likely]]
|
||||
return true;
|
||||
|
||||
ioBusy = false;
|
||||
regs.CpuStall(Scheduler::GetInstance().Remove(PI_BUS_WRITE_COMPLETE));
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -84,15 +83,15 @@ auto PI::BusRead<u8, true>(u32 addr) -> u8 {
|
||||
default:
|
||||
ircolib::panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!",
|
||||
addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
auto PI::BusRead<u8, false>(u32 addr) -> u8 {
|
||||
n64::Mem &mem = n64::Core::GetMem();
|
||||
if (!ReadLatch()) [[unlikely]] {
|
||||
if (!ReadLatch()) [[unlikely]]
|
||||
return latch >> 24;
|
||||
}
|
||||
|
||||
switch (addr) {
|
||||
case REGION_PI_UNKNOWN:
|
||||
@@ -129,6 +128,7 @@ auto PI::BusRead<u8, false>(u32 addr) -> u8 {
|
||||
default:
|
||||
ircolib::panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!",
|
||||
addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,9 +166,8 @@ template <>
|
||||
void PI::BusWrite<u8, false>(u32 addr, u32 val) {
|
||||
u8 latch_shift = 24 - (addr & 1) * 8;
|
||||
|
||||
if (!WriteLatch(val << latch_shift) && addr != 0x05000020) [[unlikely]] {
|
||||
if (!WriteLatch(val << latch_shift) && addr != 0x05000020) [[unlikely]]
|
||||
return;
|
||||
}
|
||||
|
||||
BusWrite<u8, true>(addr, val);
|
||||
}
|
||||
@@ -176,9 +175,8 @@ void PI::BusWrite<u8, false>(u32 addr, u32 val) {
|
||||
template <>
|
||||
auto PI::BusRead<u16, false>(u32 addr) -> u16 {
|
||||
n64::Mem &mem = n64::Core::GetMem();
|
||||
if (!ReadLatch()) [[unlikely]] {
|
||||
if (!ReadLatch()) [[unlikely]]
|
||||
return latch >> 16;
|
||||
}
|
||||
|
||||
switch (addr) {
|
||||
case REGION_PI_UNKNOWN:
|
||||
@@ -211,6 +209,7 @@ auto PI::BusRead<u16, false>(u32 addr) -> u16 {
|
||||
default:
|
||||
ircolib::panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!",
|
||||
addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,9 +220,8 @@ auto PI::BusRead<u16, true>(u32 addr) -> u16 {
|
||||
|
||||
template <>
|
||||
void PI::BusWrite<u16, false>(u32 addr, u32 val) {
|
||||
if (!WriteLatch(val << 16)) [[unlikely]] {
|
||||
if (!WriteLatch(val << 16)) [[unlikely]]
|
||||
return;
|
||||
}
|
||||
|
||||
switch (addr) {
|
||||
case REGION_PI_UNKNOWN:
|
||||
@@ -254,9 +252,8 @@ void PI::BusWrite<u16, true>(u32 addr, u32 val) {
|
||||
template <>
|
||||
auto PI::BusRead<u32, false>(u32 addr) -> u32 {
|
||||
n64::Mem &mem = n64::Core::GetMem();
|
||||
if (!ReadLatch()) [[unlikely]] {
|
||||
if (!ReadLatch()) [[unlikely]]
|
||||
return latch;
|
||||
}
|
||||
|
||||
switch (addr) {
|
||||
case REGION_PI_UNKNOWN:
|
||||
@@ -302,6 +299,7 @@ auto PI::BusRead<u32, false>(u32 addr) -> u32 {
|
||||
default:
|
||||
ircolib::panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!",
|
||||
addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,29 +313,29 @@ void PI::BusWrite<u32, false>(u32 addr, u32 val) {
|
||||
n64::Mem &mem = n64::Core::GetMem();
|
||||
switch (addr) {
|
||||
case REGION_PI_UNKNOWN:
|
||||
if (!WriteLatch(val)) [[unlikely]] {
|
||||
if (!WriteLatch(val)) [[unlikely]]
|
||||
return;
|
||||
}
|
||||
|
||||
ircolib::warn("Writing word 0x{:08X} to address 0x{:08X} in unsupported region: REGION_PI_UNKNOWN", val, addr);
|
||||
return;
|
||||
case REGION_PI_64DD_REG:
|
||||
if (!WriteLatch(val)) [[unlikely]] {
|
||||
if (!WriteLatch(val)) [[unlikely]]
|
||||
return;
|
||||
}
|
||||
|
||||
ircolib::warn(
|
||||
"Writing word 0x{:08X} to address 0x{:08X} in region: REGION_PI_64DD_ROM, this is the 64DD, ignoring!", val,
|
||||
addr);
|
||||
return;
|
||||
case REGION_PI_64DD_ROM:
|
||||
if (!WriteLatch(val)) [[unlikely]] {
|
||||
if (!WriteLatch(val)) [[unlikely]]
|
||||
return;
|
||||
}
|
||||
|
||||
ircolib::warn("Writing word 0x{:08X} to address 0x{:08X} in unsupported region: REGION_PI_64DD_ROM", val, addr);
|
||||
return;
|
||||
case REGION_PI_SRAM:
|
||||
if (!WriteLatch(val)) [[unlikely]] {
|
||||
if (!WriteLatch(val)) [[unlikely]]
|
||||
return;
|
||||
}
|
||||
|
||||
mem.BackupWrite<u32>(addr - SREGION_PI_SRAM, val);
|
||||
return;
|
||||
case REGION_PI_ROM:
|
||||
@@ -346,19 +344,17 @@ void PI::BusWrite<u32, false>(u32 addr, u32 val) {
|
||||
ircolib::write_access<u32>(mem.isviewer, addr - SREGION_CART_ISVIEWER_BUFFER, std::byteswap(val));
|
||||
break;
|
||||
case CART_ISVIEWER_FLUSH:
|
||||
{
|
||||
if (val < CART_ISVIEWER_SIZE) {
|
||||
std::string message(val, 0);
|
||||
std::copy_n(mem.isviewer.begin(), val, message.begin());
|
||||
mem.isviewer_sink << message;
|
||||
mem.isviewer_sink.flush();
|
||||
} else {
|
||||
ircolib::panic(
|
||||
"ISViewer buffer size is emulated at {} bytes, but received a flush command for {} bytes!",
|
||||
CART_ISVIEWER_SIZE, val);
|
||||
}
|
||||
break;
|
||||
if (val < CART_ISVIEWER_SIZE) {
|
||||
std::string message(val, 0);
|
||||
std::copy_n(mem.isviewer.begin(), val, message.begin());
|
||||
mem.isviewer_sink << message;
|
||||
mem.isviewer_sink.flush();
|
||||
} else {
|
||||
ircolib::panic(
|
||||
"ISViewer buffer size is emulated at {} bytes, but received a flush command for {} bytes!",
|
||||
CART_ISVIEWER_SIZE, val);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!WriteLatch(val)) [[unlikely]] {
|
||||
ircolib::warn("Couldn't latch PI bus, ignoring write to REGION_PI_ROM");
|
||||
@@ -381,9 +377,8 @@ void PI::BusWrite<u32, true>(u32 addr, u32 val) {
|
||||
template <>
|
||||
auto PI::BusRead<u64, false>(u32 addr) -> u64 {
|
||||
n64::Mem &mem = n64::Core::GetMem();
|
||||
if (!ReadLatch()) [[unlikely]] {
|
||||
if (!ReadLatch()) [[unlikely]]
|
||||
return static_cast<u64>(latch) << 32;
|
||||
}
|
||||
|
||||
switch (addr) {
|
||||
case REGION_PI_UNKNOWN:
|
||||
@@ -406,6 +401,7 @@ auto PI::BusRead<u64, false>(u32 addr) -> u64 {
|
||||
default:
|
||||
ircolib::panic("Should never end up here! Access to address {:08X} which did not match any PI bus regions!",
|
||||
addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,6 +481,7 @@ auto PI::Read(u32 addr) const -> u32 {
|
||||
return piBsdDom2Rls;
|
||||
default:
|
||||
ircolib::panic("Unhandled PI[{:08X}] read", addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,6 +496,7 @@ u8 PI::GetDomain(const u32 address) {
|
||||
return 2;
|
||||
default:
|
||||
ircolib::panic("Unknown PI domain for address {:08X}!", address);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -463,9 +463,8 @@ void Cop0::decode(const Instruction instr) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <>
|
||||
bool Cop0::MapVirtualAddress<u32, true>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
bool Cop0::MapVirtualAddress<u32, Cop0::User>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
if (ircolib::is_inside_range(vaddr, START_VREGION_KUSEG, END_VREGION_KUSEG))
|
||||
return ProbeTLB(accessType, s64(s32(vaddr)), paddr);
|
||||
|
||||
@@ -474,26 +473,52 @@ bool Cop0::MapVirtualAddress<u32, true>(const TLBAccessType accessType, const u6
|
||||
}
|
||||
|
||||
template <>
|
||||
bool Cop0::MapVirtualAddress<u32, false>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
bool Cop0::MapVirtualAddress<u32, Cop0::Supervisor>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
u8 segment = static_cast<u32>(vaddr) >> 29 & 7;
|
||||
if (ircolib::is_inside_range(segment, 0, 3) || segment == 7)
|
||||
if (ircolib::is_inside_range(segment, 0, 3) || segment == 6)
|
||||
return ProbeTLB(accessType, static_cast<s32>(vaddr), paddr);
|
||||
|
||||
if (ircolib::is_inside_range(segment, 4, 5)) {
|
||||
paddr = vaddr & 0x1FFFFFFF;
|
||||
return true;
|
||||
if (ircolib::is_inside_range(segment, 4, 5) || segment == 7) {
|
||||
tlbError = DISALLOWED_ADDRESS;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (segment == 6)
|
||||
ircolib::panic("Unimplemented virtual mapping in KSSEG! ({:08X})", vaddr);
|
||||
|
||||
ircolib::panic("Should never end up in base case in MapVirtualAddress! ({:08X})", vaddr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool Cop0::MapVirtualAddress<u64, true>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
bool Cop0::MapVirtualAddress<u32, Cop0::Kernel>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
u8 segment = static_cast<u32>(vaddr) >> 29 & 7;
|
||||
if (ircolib::is_inside_range(segment, 0, 3) || segment == 7 || segment == 6)
|
||||
return ProbeTLB(accessType, static_cast<s32>(vaddr), paddr);
|
||||
|
||||
if (ircolib::is_inside_range(segment, 4, 5)) {
|
||||
paddr = vaddr & 0x1FFFFFFF;
|
||||
return true;
|
||||
}
|
||||
|
||||
ircolib::panic("Should never end up in base case in MapVirtualAddress! ({:08X})", vaddr);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool Cop0::MapVirtualAddress<u64, Cop0::Supervisor>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
bool unused1 = ircolib::is_inside_range(vaddr, 0x0000'0100'0000'0000ull, 0x3fff'ffff'ffff'ffffull);
|
||||
bool unused2 = ircolib::is_inside_range(vaddr, 0x4000'0100'0000'0000ull, 0xffff'ffff'bfff'ffffull);
|
||||
bool unused3 = ircolib::is_inside_range(vaddr, 0xffff'ffff'e000'0000ull, 0xffff'ffff'ffff'ffffull);
|
||||
if (unused1 || unused2 || unused3) {
|
||||
tlbError = DISALLOWED_ADDRESS;
|
||||
return false;
|
||||
}
|
||||
|
||||
return ProbeTLB(accessType, static_cast<s32>(vaddr), paddr);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool Cop0::MapVirtualAddress<u64, Cop0::User>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
if (ircolib::is_inside_range(vaddr, 0x0000000000000000, 0x000000FFFFFFFFFF))
|
||||
return ProbeTLB(accessType, vaddr, paddr);
|
||||
|
||||
@@ -502,7 +527,7 @@ bool Cop0::MapVirtualAddress<u64, true>(const TLBAccessType accessType, const u6
|
||||
}
|
||||
|
||||
template <>
|
||||
bool Cop0::MapVirtualAddress<u64, false>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
bool Cop0::MapVirtualAddress<u64, Cop0::Kernel>(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
if (ircolib::is_inside_range(vaddr, 0x0000000000000000, 0x000000FFFFFFFFFF) || // VREGION_XKUSEG
|
||||
ircolib::is_inside_range(vaddr, 0x4000000000000000, 0x400000FFFFFFFFFF) || // VREGION_XKSSEG
|
||||
ircolib::is_inside_range(vaddr, 0xC000000000000000, 0xC00000FF7FFFFFFF) || // VREGION_XKSEG
|
||||
@@ -553,23 +578,29 @@ bool Cop0::MapVirtualAddress<u64, false>(const TLBAccessType accessType, const u
|
||||
}
|
||||
|
||||
bool Cop0::MapVAddr(const TLBAccessType accessType, const u64 vaddr, u32 &paddr) {
|
||||
if (supervisorMode)
|
||||
ircolib::panic("Supervisor mode memory access");
|
||||
|
||||
if (is64BitAddressing) [[unlikely]] {
|
||||
if (kernelMode) [[likely]]
|
||||
return MapVirtualAddress<u64, false>(accessType, vaddr, paddr);
|
||||
return MapVirtualAddress<u64, Cop0::Kernel>(accessType, vaddr, paddr);
|
||||
|
||||
if (supervisorMode)
|
||||
return MapVirtualAddress<u64, Cop0::Supervisor>(accessType, vaddr, paddr);
|
||||
|
||||
if (userMode)
|
||||
return MapVirtualAddress<u64, true>(accessType, vaddr, paddr);
|
||||
return MapVirtualAddress<u64, Cop0::User>(accessType, vaddr, paddr);
|
||||
|
||||
ircolib::panic("Unknown mode! This should never happen!");
|
||||
}
|
||||
|
||||
if (kernelMode) [[likely]]
|
||||
return MapVirtualAddress<u32, false>(accessType, vaddr, paddr);
|
||||
return MapVirtualAddress<u32, Cop0::Kernel>(accessType, vaddr, paddr);
|
||||
|
||||
if (supervisorMode)
|
||||
return MapVirtualAddress<u32, Cop0::Supervisor>(accessType, vaddr, paddr);
|
||||
|
||||
if (userMode)
|
||||
return MapVirtualAddress<u32, true>(accessType, vaddr, paddr);
|
||||
return MapVirtualAddress<u32, Cop0::User>(accessType, vaddr, paddr);
|
||||
|
||||
ircolib::panic("Unknown mode! This should never happen!");
|
||||
return false;
|
||||
}
|
||||
} // namespace n64
|
||||
|
||||
@@ -288,7 +288,9 @@ struct Cop0 {
|
||||
void tlbw(int);
|
||||
void tlbp();
|
||||
|
||||
template <typename T, bool User>
|
||||
enum MemoryMapType { User, Supervisor, Kernel };
|
||||
|
||||
template <typename T, MemoryMapType mapType>
|
||||
bool MapVirtualAddress(TLBAccessType accessType, u64 vaddr, u32 &paddr);
|
||||
};
|
||||
} // namespace n64
|
||||
|
||||
@@ -189,6 +189,8 @@ void KaizenGui::cleanup() {
|
||||
SDL_Quit();
|
||||
core.Stop();
|
||||
emuThread->requestInterruption();
|
||||
while (emuThread->isRunning())
|
||||
;
|
||||
emuThread->quit();
|
||||
delete emuThread;
|
||||
delete vulkanWidget;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <ParallelRDPWrapper.hpp>
|
||||
#include <QVulkanWindow>
|
||||
#include <QMainWindow>
|
||||
#include <QThread>
|
||||
|
||||
struct InputSettings;
|
||||
|
||||
|
||||
+10
-10
@@ -1,9 +1,9 @@
|
||||
#include <RomsList.hpp>
|
||||
#include <QHeaderView>
|
||||
#include <QThread>
|
||||
#include <GeneralSettings.hpp>
|
||||
#include <Options.hpp>
|
||||
#include <Mem.hpp>
|
||||
#include <thread>
|
||||
|
||||
RomsListTable::RomsListTable(GeneralSettings *general) {
|
||||
verticalHeader()->hide();
|
||||
@@ -54,18 +54,18 @@ void RomsListTable::populate(const std::string &romsPath) {
|
||||
if (!isArchive && !isPlain)
|
||||
continue;
|
||||
|
||||
auto rom = n64::Mem::LoadROM(isArchive, filename);
|
||||
auto regions = n64::GameDB::match(rom);
|
||||
auto header = n64::Mem::ReadROMHeader(isArchive, filename);
|
||||
auto [saveType, regions, gameNameDB] = n64::GameDB::match(header);
|
||||
|
||||
if (rom.gameNameDB.empty())
|
||||
rom.gameNameDB = fs::path(filename).stem().string();
|
||||
if (gameNameDB.empty())
|
||||
gameNameDB = fs::path(filename).stem().string();
|
||||
|
||||
insertRow(i);
|
||||
setItem(i, 0,
|
||||
new QTableWidgetItem(std::format("{} ({}) (Rev {})", rom.gameNameDB,
|
||||
n64::GameDB::regionCodeToReadable(rom.header.countryCode),
|
||||
rom.header.version)
|
||||
.c_str()));
|
||||
setItem(
|
||||
i, 0,
|
||||
new QTableWidgetItem(std::format("{} ({}) (Rev {})", gameNameDB,
|
||||
n64::GameDB::regionCodeToReadable(header.countryCode), header.version)
|
||||
.c_str()));
|
||||
setItem(i, 1, new QTableWidgetItem(regions.c_str()));
|
||||
setItem(i, 2, new QTableWidgetItem("Never"));
|
||||
setItem(i, 3, new QTableWidgetItem("0h 0m 0s"));
|
||||
|
||||
@@ -18,5 +18,6 @@ AudioSettings::AudioSettings() : settings(QSettings::UserScope) {
|
||||
v = new QVBoxLayout();
|
||||
v->addWidget(volume);
|
||||
v->addWidget(volumePercent);
|
||||
v->addStretch(-1);
|
||||
setLayout(v);
|
||||
}
|
||||
|
||||
@@ -51,5 +51,6 @@ CPUSettings::CPUSettings() : settings(QSettings::UserScope) {
|
||||
h->addWidget(idleSkip);
|
||||
v->addWidget(types);
|
||||
v->addLayout(h);
|
||||
v->addStretch(-1);
|
||||
setLayout(v);
|
||||
}
|
||||
|
||||
@@ -43,8 +43,6 @@ GeneralSettings::GeneralSettings() : settings(QSettings::UserScope) {
|
||||
emit romFolderSelected();
|
||||
});
|
||||
|
||||
gl = new QGridLayout();
|
||||
|
||||
QPushButton *clearRoms = new QPushButton("Clear");
|
||||
connect(clearRoms, &QPushButton::clicked, this, [&] {
|
||||
selectedRomsFolderLabel->clear();
|
||||
@@ -62,6 +60,8 @@ GeneralSettings::GeneralSettings() : settings(QSettings::UserScope) {
|
||||
settings.sync();
|
||||
});
|
||||
|
||||
gl = new QGridLayout();
|
||||
|
||||
gl->addWidget(new QLabel("ROMs path:"), 0, 0);
|
||||
gl->addWidget(selectedRomsFolderLabel, 0, 1);
|
||||
gl->addWidget(romsFolderSelectButton, 0, 2);
|
||||
@@ -71,6 +71,7 @@ GeneralSettings::GeneralSettings() : settings(QSettings::UserScope) {
|
||||
gl->addWidget(selectedSavesFolderLabel, 1, 1);
|
||||
gl->addWidget(savesFolderSelectButton, 1, 2);
|
||||
gl->addWidget(clearSaves, 1, 3);
|
||||
gl->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding), 2, 0, 2, -1);
|
||||
|
||||
setLayout(gl);
|
||||
}
|
||||
|
||||
+55
-4
@@ -1,5 +1,6 @@
|
||||
#include <File.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <unarr.h>
|
||||
|
||||
namespace Util {
|
||||
@@ -9,7 +10,12 @@ std::vector<u8> OpenROM(const std::string &filename, size_t &sizeAdjusted) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::vector<u8> OpenArchive(const std::string &path, size_t &sizeAdjusted) {
|
||||
std::vector<u8> OpenROMHeader(const std::string &filename) {
|
||||
auto buf = ircolib::read_file_binary(filename, 0x40);
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::vector<u8> ExtractROMFromArchive(const std::string &path, size_t &sizeAdjusted) {
|
||||
const auto stream = ar_open_file(fs::path(path).string().c_str());
|
||||
|
||||
if (!stream) {
|
||||
@@ -33,13 +39,12 @@ std::vector<u8> OpenArchive(const std::string &path, size_t &sizeAdjusted) {
|
||||
|
||||
std::vector<u8> buf{};
|
||||
|
||||
std::vector<std::string> rom_exts{".n64", ".z64", ".v64", ".N64", ".Z64", ".V64"};
|
||||
|
||||
while (ar_parse_entry(archive)) {
|
||||
auto filename = ar_entry_get_name(archive);
|
||||
auto extension = fs::path(filename).extension();
|
||||
|
||||
if (std::ranges::any_of(rom_exts, [&](const auto &x) { return extension == x; })) {
|
||||
if (std::ranges::any_of(std::array{".n64", ".z64", ".v64", ".N64", ".Z64", ".V64"},
|
||||
[&](const auto &x) { return extension == x; })) {
|
||||
const auto size = ar_entry_get_size(archive);
|
||||
sizeAdjusted = ircolib::next_pow2(size);
|
||||
buf.resize(sizeAdjusted);
|
||||
@@ -56,4 +61,50 @@ std::vector<u8> OpenArchive(const std::string &path, size_t &sizeAdjusted) {
|
||||
ar_close(stream);
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::vector<u8> ExtractROMHeaderFromArchive(const std::string &path) {
|
||||
const auto stream = ar_open_file(fs::path(path).string().c_str());
|
||||
|
||||
if (!stream) {
|
||||
ircolib::panic("Could not open archive! Are you sure it's an archive?");
|
||||
}
|
||||
|
||||
ar_archive *archive = ar_open_zip_archive(stream, false);
|
||||
|
||||
if (!archive)
|
||||
archive = ar_open_rar_archive(stream);
|
||||
if (!archive)
|
||||
archive = ar_open_7z_archive(stream);
|
||||
if (!archive)
|
||||
archive = ar_open_tar_archive(stream);
|
||||
|
||||
if (!archive) {
|
||||
ar_close(stream);
|
||||
ircolib::panic(
|
||||
"Could not open archive! Are you sure it's a supported archive? (7z, zip, rar and tar are supported)");
|
||||
}
|
||||
|
||||
std::vector<u8> buf{};
|
||||
|
||||
while (ar_parse_entry(archive)) {
|
||||
auto filename = ar_entry_get_name(archive);
|
||||
auto extension = fs::path(filename).extension();
|
||||
|
||||
if (std::ranges::any_of(std::array{".n64", ".z64", ".v64", ".N64", ".Z64", ".V64"},
|
||||
[&](const auto &x) { return extension == x; })) {
|
||||
const auto size = 0x40;
|
||||
buf.resize(size);
|
||||
ar_entry_uncompress(archive, buf.data(), size);
|
||||
break;
|
||||
}
|
||||
|
||||
ar_close_archive(archive);
|
||||
ar_close(stream);
|
||||
ircolib::panic("Could not find any rom image in the archive!");
|
||||
}
|
||||
|
||||
ar_close_archive(archive);
|
||||
ar_close(stream);
|
||||
return buf;
|
||||
}
|
||||
} // namespace Util
|
||||
|
||||
+3
-1
@@ -9,6 +9,8 @@
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Util {
|
||||
std::vector<u8> OpenROMHeader(const std::string &filename);
|
||||
std::vector<u8> ExtractROMHeaderFromArchive(const std::string &path);
|
||||
std::vector<u8> OpenROM(const std::string &filename, size_t &sizeAdjusted);
|
||||
std::vector<u8> OpenArchive(const std::string &path, size_t &sizeAdjusted);
|
||||
std::vector<u8> ExtractROMFromArchive(const std::string &path, size_t &sizeAdjusted);
|
||||
} // namespace Util
|
||||
|
||||
Reference in New Issue
Block a user