Introduce game db + fallback to stem of filename + volume control

This commit is contained in:
CocoSimone
2022-09-18 16:12:50 +02:00
parent bef4705ffa
commit 708dde5b6c
20 changed files with 3295 additions and 73 deletions

View File

@@ -10,7 +10,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y build-essential libgtk-3-dev libsdl2-dev libfmt-dev git ninja-build
sudo apt-get install -y build-essential libgtk-3-dev libsdl2-dev libfmt-dev git ninja-build nlohmann-json3-dev
sudo apt-get install -y vulkan-tools libvulkan1 libvulkan-dev vulkan-validationlayers-dev spirv-tools
- name: Build natsukashii
run: |
@@ -44,7 +44,10 @@ jobs:
submodules: recursive
- uses: msys2/setup-msys2@v2
with:
install: make git mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-SDL2 mingw-w64-x86_64-vulkan-devel mingw-w64-x86_64-fmt mingw-w64-x86_64-ninja
install:
make git mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-SDL2
mingw-w64-x86_64-vulkan-devel mingw-w64-x86_64-fmt mingw-w64-x86_64-ninja
mingw-w64-x86_64-nlohmann-json
- name: Build natsukashii
run: |
cmake \

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
*.toml
*.ini
*.json
*build*/
.idea/
roms/

3110
resources/game_db.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ void App::Run() {
// Main loop
const u8* state = SDL_GetKeyboardState(nullptr);
while (!core.done) {
core.Run(window);
core.Run(window, window.volumeL, window.volumeR);
core.UpdateController(state);
SDL_Event event;
@@ -33,7 +33,7 @@ void App::Run() {
const nfdu8filteritem_t filter {"Nintendo 64 roms", "n64,z64,v64,N64,Z64,V64"};
nfdresult_t result = NFD_OpenDialog(&outpath, &filter, 1, nullptr);
if(result == NFD_OKAY) {
core.LoadROM(outpath);
LoadROM(outpath);
NFD_FreePath(outpath);
}
} break;

View File

@@ -6,7 +6,7 @@ struct App {
~App() = default;
void Run();
inline void LoadROM(const std::string& path) {
core.LoadROM(path);
window.LoadROM(core, path);
}
private:
n64::Core core;

View File

@@ -7,6 +7,7 @@ SET(NFD_PORTAL ON CACHE BOOL "Use dbus for native file dialog instead of gtk")
find_package(SDL2 REQUIRED)
find_package(fmt REQUIRED)
find_package(nlohmann_json REQUIRED)
add_library(frontend-imgui STATIC
Window.cpp
@@ -34,4 +35,4 @@ else()
set(LIBRARIES )
endif()
target_link_libraries(frontend-imgui PUBLIC SDL2main SDL2 ${LIBRARIES} imgui nfd fmt)
target_link_libraries(frontend-imgui PUBLIC nlohmann_json::nlohmann_json SDL2main SDL2 ${LIBRARIES} imgui nfd fmt)

View File

@@ -4,8 +4,11 @@
#include <Core.hpp>
#include <utility>
#include <Audio.hpp>
#include <nlohmann/json.hpp>
#include <filesystem>
VkInstance instance{};
using json = nlohmann::json;
Window::Window(n64::Core& core) {
InitSDL();
@@ -142,9 +145,30 @@ ImDrawData* Window::Present(n64::Core& core) {
return ImGui::GetDrawData();
}
void Window::LoadROM(n64::Core& core, const std::string &path) {
if(!path.empty()) {
u32 crc = core.LoadROM(path);
std::ifstream gameDbFile("resources/game_db.json");
json gameDb = json::parse(gameDbFile);
auto entry = gameDb[fmt::format("{:08x}", crc)]["name"];
std::string name{};
if(!entry.empty()) {
name = entry.get<std::string>();
} else {
name = std::filesystem::path(path).stem().string();
}
windowTitle = "natsukashii - " + name;
SDL_SetWindowTitle(window, windowTitle.c_str());
}
}
void Window::Render(n64::Core& core) {
ImGui::PushFont(uiFont);
if(windowID == SDL_GetWindowID(SDL_GetMouseFocus())) {
static bool showSettings = false;
bool showMainMenuBar = windowID == SDL_GetWindowID(SDL_GetMouseFocus());
if(showMainMenuBar) {
ImGui::BeginMainMenuBar();
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open", "O")) {
@@ -152,7 +176,7 @@ void Window::Render(n64::Core& core) {
const nfdu8filteritem_t filter{"Nintendo 64 roms", "n64,z64,v64,N64,Z64,V64"};
nfdresult_t result = NFD_OpenDialog(&outpath, &filter, 1, nullptr);
if (result == NFD_OKAY) {
core.LoadROM(outpath);
LoadROM(core, outpath);
NFD_FreePath(outpath);
}
}
@@ -163,17 +187,51 @@ void Window::Render(n64::Core& core) {
}
if (ImGui::BeginMenu("Emulation")) {
if (ImGui::MenuItem("Reset")) {
core.Reset();
LoadROM(core, core.rom);
}
if (ImGui::MenuItem("Stop")) {
windowTitle = "natsukashii";
SDL_SetWindowTitle(window, windowTitle.c_str());
core.Stop();
}
if (ImGui::MenuItem(core.pause ? "Resume" : "Pause", nullptr, false, core.romLoaded)) {
core.TogglePause();
std::string paused = "| Paused";
if(core.pause) {
windowTitle += paused;
} else {
auto pausedPos = windowTitle.find_first_of(paused);
if(pausedPos != std::string::npos) {
windowTitle.erase(pausedPos, paused.length());
}
}
SDL_SetWindowTitle(window, windowTitle.c_str());
}
if (ImGui::MenuItem("Settings")) {
showSettings = true;
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
if(showSettings) {
ImGui::OpenPopup("Settings");
if(ImGui::BeginPopupModal("Settings", &showSettings)) {
ImGui::Checkbox("Lock channels", &lockVolume);
ImGui::SliderFloat("Volume L", &volumeL, 0, 1, "%.2f", ImGuiSliderFlags_NoInput);
if (!lockVolume) {
ImGui::SliderFloat("Volume R", &volumeR, 0, 1, "%.2f", ImGuiSliderFlags_NoInput);
} else {
ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, 0x11111111);
ImGui::BeginDisabled();
ImGui::SliderFloat("Volume R", &volumeR, 0, 1, "%.2f", ImGuiSliderFlags_NoInput);
ImGui::EndDisabled();
ImGui::PopStyleColor();
volumeR = volumeL;
}
ImGui::EndPopup();
}
}
ImGui::PopFont();
}

View File

@@ -15,8 +15,13 @@ struct Window {
[[nodiscard]] bool gotClosed(SDL_Event event);
ImFont *uiFont, *codeFont;
u32 windowID;
float volumeL = 0.5, volumeR = 0.5;
void LoadROM(n64::Core& core, const std::string& path);
private:
bool lockVolume = true;
bool showSettings = false;
SDL_Window* window;
std::string windowTitle;
void InitSDL();
void InitImgui();
void Render(n64::Core& core);

View File

@@ -14,29 +14,18 @@ void Core::Stop() {
mem.Reset();
pause = true;
romLoaded = false;
rom.clear();
}
void Core::Reset() {
cpu.Reset();
mem.Reset();
pause = true;
romLoaded = false;
if(!rom.empty()) {
LoadROM(rom);
}
}
void Core::LoadROM(const std::string& rom_) {
u32 Core::LoadROM(const std::string& rom_) {
rom = rom_;
cpu.Reset();
mem.Reset();
mem.LoadROM(rom);
pause = false;
romLoaded = true;
return mem.LoadROM(rom);
}
void Core::Run(Window& window) {
void Core::Run(Window& window, float volumeL, float volumeR) {
MMIO& mmio = mem.mmio;
Controller& controller = mmio.si.controller;
int cycles = 0;
@@ -54,7 +43,7 @@ void Core::Run(Window& window) {
cpu.Step(mem);
mmio.rsp.Step(mmio.mi, cpu.regs, mmio.rdp);
mmio.ai.Step(mem, cpu.regs, 1);
mmio.ai.Step(mem, cpu.regs, 1, volumeL, volumeR);
}
cycles -= mmio.vi.cyclesPerHalfline;
@@ -67,7 +56,7 @@ void Core::Run(Window& window) {
UpdateScreenParallelRdp(*this, window, GetVI());
int missedCycles = N64_CYCLES_PER_FRAME - frameCycles;
mmio.ai.Step(mem, cpu.regs, missedCycles);
mmio.ai.Step(mem, cpu.regs, missedCycles, volumeL, volumeR);
} else if(pause && romLoaded) {
UpdateScreenParallelRdp(*this, window, GetVI());
} else if(pause && !romLoaded) {

View File

@@ -10,9 +10,8 @@ struct Core {
~Core() { Stop(); }
Core();
void Stop();
void Reset();
void LoadROM(const std::string&);
void Run(Window&);
u32 LoadROM(const std::string&);
void Run(Window&, float volumeL, float volumeR);
void UpdateController(const u8*);
void TogglePause() { pause = !pause; }
VI& GetVI() { return mem.mmio.vi; }

View File

@@ -65,12 +65,12 @@ void InitAudio() {
}
}
void PushSample(s16 left, s16 right) {
s16 samples[2]{ left, right };
void PushSample(float left, float volumeL, float volumeR, float right) {
float samples[2]{ left * volumeL, right * volumeR };
int availableBytes = SDL_AudioStreamAvailable(audioStream);
if(availableBytes < BYTES_PER_HALF_SECOND) {
SDL_AudioStreamPut(audioStream, samples, 2 * sizeof(16));
SDL_AudioStreamPut(audioStream, samples, 2 * sizeof(float));
}
}
@@ -78,7 +78,7 @@ void AdjustSampleRate(int sampleRate) {
LockAudioMutex();
if(audioStream) SDL_FreeAudioStream(audioStream);
audioStream = SDL_NewAudioStream(AUDIO_S16SYS, 2, sampleRate, SYSTEM_SAMPLE_FORMAT, 2, AUDIO_SAMPLE_RATE);
audioStream = SDL_NewAudioStream(AUDIO_F32SYS, 2, sampleRate, SYSTEM_SAMPLE_FORMAT, 2, AUDIO_SAMPLE_RATE);
UnlockAudioMutex();
}

View File

@@ -2,7 +2,7 @@
#include "common.hpp"
namespace n64 {
void PushSample(s16, s16);
void PushSample(float, float, float, float);
void InitAudio();
void AdjustSampleRate(int);
}

View File

@@ -16,7 +16,7 @@ void Mem::Reset() {
mmio.Reset();
}
void Mem::LoadROM(const std::string& filename) {
u32 Mem::LoadROM(const std::string& filename) {
std::ifstream file(filename, std::ios::binary);
file.unsetf(std::ios::skipws);
@@ -35,8 +35,12 @@ void Mem::LoadROM(const std::string& filename) {
cart.insert(cart.begin(), std::istream_iterator<u8>(file), std::istream_iterator<u8>());
file.close();
util::SwapN64Rom(sizeAdjusted, cart.data());
u32 crc = 0;
util::SwapN64Rom(crc, sizeAdjusted, cart.data());
memcpy(mmio.rsp.dmem, cart.data(), 0x1000);
return crc;
}
template <bool tlb>

View File

@@ -11,7 +11,7 @@ struct Mem {
~Mem() = default;
Mem();
void Reset();
void LoadROM(const std::string&);
u32 LoadROM(const std::string&);
[[nodiscard]] auto GetRDRAM() -> u8* {
return mmio.rdp.dram.data();
}

View File

@@ -16,8 +16,7 @@ void RSP::Reset() {
nextPC = 0;
spDMASPAddr.raw = 0;
spDMADRAMAddr.raw = 0;
spDMARDLen.raw = 0;
spDMAWRLen.raw = 0;
spDMALen.raw = 0;
memset(dmem, 0, DMEM_SIZE);
memset(imem, 0, IMEM_SIZE);
memset(vpr, 0, 32 * sizeof(VPR));
@@ -44,8 +43,8 @@ auto RSP::Read(u32 addr) -> u32{
switch (addr) {
case 0x04040000: return spDMASPAddr.raw & 0xFFFFF8;
case 0x04040004: return spDMADRAMAddr.raw & 0x1FF8;
case 0x04040008: return spDMARDLen.raw;
case 0x0404000C: return spDMAWRLen.raw;
case 0x04040008:
case 0x0404000C: return spDMALen.raw;
case 0x04040010: return spStatus.raw;
case 0x04040018: return 0;
case 0x0404001C: return AcquireSemaphore();
@@ -61,20 +60,22 @@ inline void DMA(SPDMALen len, RSP& rsp, u8* dst, u8* src) {
length = (length + 0x7) & ~0x7;
u32 last_addr = rsp.spDMASPAddr.address + length;
if (last_addr > 0x1000) {
u32 overshoot = last_addr - 0x1000;
length -= overshoot;
}
u32 dram_address = rsp.spDMADRAMAddr.address & 0xFFFFF8;
u32 mem_address = rsp.spDMASPAddr.address & 0x1FF8;
u32 mem_address = rsp.spDMASPAddr.address & 0xFF8;
for (int i = 0; i < len.count + 1; i++) {
if(isDRAMdest) {
/*if(isDRAMdest) {
memcpy(&dst[dram_address], &src[mem_address], length);
} else {
memcpy(&dst[mem_address], &src[dram_address], length);
}*/
for(int j = 0; j < length; j++) {
if constexpr (isDRAMdest) {
dst[dram_address + j] = src[(mem_address + j) & 0xFFF];
} else {
dst[(mem_address + j) & 0xFFF] = src[dram_address + j];
}
}
int skip = i == len.count ? 0 : len.skip;
@@ -82,6 +83,9 @@ inline void DMA(SPDMALen len, RSP& rsp, u8* dst, u8* src) {
dram_address += (length + skip) & 0xFFFFF8;
mem_address += length;
}
rsp.spDMADRAMAddr.address = dram_address;
rsp.spDMASPAddr.address = mem_address;
}
void RSP::Write(Mem& mem, Registers& regs, u32 addr, u32 value) {
@@ -90,14 +94,14 @@ void RSP::Write(Mem& mem, Registers& regs, u32 addr, u32 value) {
case 0x04040000: spDMASPAddr.raw = value & 0x1FF8; break;
case 0x04040004: spDMADRAMAddr.raw = value & 0xFFFFF8; break;
case 0x04040008: {
spDMARDLen.raw = value;
DMA<false>(spDMARDLen, *this, spDMASPAddr.bank ? imem : dmem, mem.GetRDRAM());
spDMARDLen.raw = 0xFF8 | (spDMARDLen.skip << 20);
spDMALen.raw = value;
DMA<false>(spDMALen, *this, spDMASPAddr.bank ? imem : dmem, mem.GetRDRAM());
spDMALen.raw = 0xFF8 | (spDMALen.skip << 20);
} break;
case 0x0404000C: {
spDMAWRLen.raw = value;
DMA<true>(spDMAWRLen, *this, mem.GetRDRAM(), spDMASPAddr.bank ? imem : dmem);
spDMAWRLen.raw = 0xFF8 | (spDMAWRLen.skip << 20);
spDMALen.raw = value;
DMA<true>(spDMALen, *this, mem.GetRDRAM(), spDMASPAddr.bank ? imem : dmem);
spDMALen.raw = 0xFF8 | (spDMALen.skip << 20);
} break;
case 0x04040010: {
auto write = SPStatusWrite{.raw = value};

View File

@@ -3,7 +3,7 @@
#include <n64/core/RDP.hpp>
#include <n64/memory_regions.hpp>
#define RSP_BYTE(addr) (dmem[BYTE_ADDRESS(addr) & 0xfff])
#define RSP_BYTE(addr) (dmem[BYTE_ADDRESS(addr) & 0xFFF])
#define GET_RSP_HALF(addr) ((RSP_BYTE(addr) << 8) | RSP_BYTE((addr) + 1))
#define SET_RSP_HALF(addr, value) do { RSP_BYTE(addr) = ((value) >> 8) & 0xFF; RSP_BYTE((addr) + 1) = (value) & 0xFF;} while(0)
#define GET_RSP_WORD(addr) ((GET_RSP_HALF(addr) << 16) | GET_RSP_HALF((addr) + 2))
@@ -117,7 +117,7 @@ struct RSP {
u16 oldPC{}, pc{}, nextPC{};
SPDMASPAddr spDMASPAddr{};
SPDMADRAMAddr spDMADRAMAddr{};
SPDMALen spDMARDLen{}, spDMAWRLen{};
SPDMALen spDMALen{};
u8 dmem[DMEM_SIZE]{}, imem[IMEM_SIZE]{};
VPR vpr[32]{};
s32 gpr[32]{};

View File

@@ -72,7 +72,7 @@ void AI::Write(Mem& mem, Registers& regs, u32 addr, u32 val) {
}
}
void AI::Step(Mem& mem, Registers& regs, int cpuCycles) {
void AI::Step(Mem& mem, Registers& regs, int cpuCycles, float volumeL, float volumeR) {
cycles += cpuCycles;
while(cycles > dac.period) {
if (dmaCount == 0) {
@@ -85,7 +85,7 @@ void AI::Step(Mem& mem, Registers& regs, int cpuCycles) {
s16 left = (s16)(data >> 16);
s16 right = (s16)data;
PushSample(left, right);
PushSample((float)left / INT16_MAX, volumeL, volumeR, (float)right / INT16_MAX);
u32 address_lo = (dmaAddr[0] + 4) & 0x1fff;
dmaAddr[0] = (dmaAddr[0] & ~0x1fff) | address_lo;

View File

@@ -11,7 +11,7 @@ struct AI {
void Reset();
auto Read(u32) const -> u32;
void Write(Mem&, Registers&, u32, u32);
void Step(Mem&, Registers&, int);
void Step(Mem&, Registers&, int, float, float);
bool dmaEnable{};
u16 dacRate{};
u8 bitrate{};

View File

@@ -20,8 +20,8 @@ inline auto GetCop0Reg(RSP& rsp, RDP& rdp, u8 index) -> u32{
switch(index) {
case 0: return rsp.spDMASPAddr.raw;
case 1: return rsp.spDMADRAMAddr.raw;
case 2: return rsp.spDMARDLen.raw;
case 3: return rsp.spDMAWRLen.raw;
case 2:
case 3: return rsp.spDMALen.raw;
case 4: return rsp.spStatus.raw;
case 5: return rsp.spStatus.dmaFull;
case 6: return 0;
@@ -36,8 +36,8 @@ inline void SetCop0Reg(MI& mi, Registers& regs, RSP& rsp, RDP& rdp, u8 index, u3
switch(index) {
case 0: rsp.spDMASPAddr.raw = val; break;
case 1: rsp.spDMADRAMAddr.raw = val; break;
case 2: rsp.spDMARDLen.raw = val; break;
case 3: rsp.spDMAWRLen.raw = val; break;
case 2:
case 3: rsp.spDMALen.raw = val; break;
case 4: rsp.spStatus.raw = val; break;
case 7:
if(val == 0) {
@@ -227,12 +227,14 @@ void RSP::sqv(u32 instr) {
void RSP::sllv(u32 instr) {
u8 sa = gpr[RS(instr)] & 0x1F;
gpr[RD(instr)] = gpr[RT(instr)] << sa;
s32 rt = gpr[RT(instr)];
gpr[RD(instr)] = rt << sa;
}
void RSP::srlv(u32 instr) {
u8 sa = gpr[RS(instr)] & 0x1F;
gpr[RD(instr)] = (u32)gpr[RT(instr)] >> sa;
u32 rt = gpr[RT(instr)];
gpr[RD(instr)] = rt >> sa;
}
void RSP::srav(u32 instr) {

View File

@@ -131,23 +131,71 @@ inline void SwapBuffer16(size_t size, u8* data) {
}
}
inline u32 crc32(u32 crc, const u8 *buf, size_t len) {
static u32 table[256];
static int have_table = 0;
u32 rem;
u8 octet;
int i, j;
const u8 *p, *q;
if (have_table == 0) {
for (i = 0; i < 256; i++) {
rem = i;
for (j = 0; j < 8; j++) {
if (rem & 1) {
rem >>= 1;
rem ^= 0xedb88320;
} else
rem >>= 1;
}
table[i] = rem;
}
have_table = 1;
}
crc = ~crc;
q = buf + len;
for (p = buf; p < q; p++) {
octet = *p; /* Cast to unsigned octet. */
crc = (crc >> 8) ^ table[(crc & 0xff) ^ octet];
}
return ~crc;
}
enum RomTypes {
Z64 = 0x80371240,
N64 = 0x40123780,
V64 = 0x37804012
};
inline void SwapN64Rom(size_t size, u8* data) {
inline void SwapN64Rom(u32& crc, size_t size, u8* rom) {
RomTypes endianness;
memcpy(&endianness, data, 4);
memcpy(&endianness, rom, 4);
endianness = static_cast<RomTypes>(be32toh(endianness));
switch(endianness) {
case V64:
SwapBuffer32(size, data);
SwapBuffer16(size, data);
case RomTypes::V64: {
u8* temp = (u8*)calloc(size, 1);
memcpy(temp, rom, size);
SwapBuffer16(size, temp);
crc = crc32(0, temp, size);
free(temp);
SwapBuffer32(size, rom);
SwapBuffer16(size, rom);
} break;
case RomTypes::N64: {
u8* temp = (u8*)calloc(size, 1);
memcpy(temp, rom, size);
SwapBuffer32(size, temp);
crc = crc32(0, temp, size);
free(temp);
} break;
case RomTypes::Z64:
crc = crc32(0, rom, size);
SwapBuffer32(size, rom);
break;
case N64: break;
case Z64: SwapBuffer32(size, data); break;
default:
panic("Unrecognized rom format! Make sure this is a valid Nintendo 64 ROM dump!\n");
}