initial commit
This commit is contained in:
47
src/frontend/include/gui.hpp
Normal file
47
src/frontend/include/gui.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <core.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_sdl.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <nfd.hpp>
|
||||
#include <atomic>
|
||||
#include <pthread.h>
|
||||
#include <ctime>
|
||||
#include <gui/logger.hpp>
|
||||
#include <gui/opengl_context.hpp>
|
||||
|
||||
static const ImVec4 colors_disasm[3] = {ImVec4(1.0, 0.000, 0.0, 1.0), // RED
|
||||
ImVec4(1.0, 0.619, 0.0, 1.0), // ORANGE
|
||||
ImVec4(1.0, 0.988, 0.0, 1.0)}; // YELLOW
|
||||
static const ImVec4 clear_color = {0.45f, 0.55f, 0.60f, 1.00f};
|
||||
|
||||
struct Gui {
|
||||
ImGuiContext* ctx;
|
||||
bool show_disasm = true, show_regs = true;
|
||||
bool show_metrics = false, show_logs = true;
|
||||
bool pause_on_fatal = true;
|
||||
float log_pos_y = 0;
|
||||
std::string old_message = "NULL";
|
||||
message_type old_message_type = INFO;
|
||||
nfdchar_t* rom_file;
|
||||
bool rom_loaded = false, running = true;
|
||||
pthread_t emu_thread;
|
||||
Logger logger;
|
||||
clock_t delta;
|
||||
double fps = 60.0, frametime = 16.0;
|
||||
std::atomic_bool emu_quit = false;
|
||||
core_t core;
|
||||
OpenGLContext context;
|
||||
|
||||
Gui(const char* title);
|
||||
~Gui();
|
||||
void MainLoop();
|
||||
void DestroyGui();
|
||||
void OpenFile();
|
||||
void MainMenubar();
|
||||
void DebuggerWindow();
|
||||
void RegistersView();
|
||||
void Reset();
|
||||
void Stop();
|
||||
void Start();
|
||||
};
|
||||
15
src/frontend/include/gui/logger.hpp
Normal file
15
src/frontend/include/gui/logger.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <log.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_logger.h>
|
||||
#include <string>
|
||||
|
||||
static const std::string message_type_strings[3] = {"[INFO]", "[WARNING]", "[FATAL]"};
|
||||
|
||||
struct Gui;
|
||||
|
||||
struct Logger {
|
||||
Logger();
|
||||
void LogWindow(Gui* gui);
|
||||
ImGui::Logger logger;
|
||||
};
|
||||
29
src/frontend/include/gui/opengl_context.hpp
Normal file
29
src/frontend/include/gui/opengl_context.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
#include <SDL_opengl.h>
|
||||
#include <core.h>
|
||||
#include <imgui.h>
|
||||
|
||||
struct Gui;
|
||||
|
||||
struct GLData {
|
||||
u32 old_w, old_h;
|
||||
u8 old_format = 14;
|
||||
int glFormat = GL_UNSIGNED_SHORT_5_5_5_1;
|
||||
u8 depth = 2;
|
||||
};
|
||||
|
||||
struct OpenGLContext {
|
||||
GLData gl_data;
|
||||
SDL_Window* window;
|
||||
unsigned int id; // OpenGL framebuffer texture ID
|
||||
u8* framebuffer;
|
||||
SDL_GLContext gl_context;
|
||||
OpenGLContext(const char* title, const char* m_glsl_version);
|
||||
void UpdateTexture(core_t* core);
|
||||
void MainWindow(Gui* gui, core_t* core);
|
||||
void Update(ImGuiIO& io);
|
||||
void Frame();
|
||||
~OpenGLContext();
|
||||
};
|
||||
216
src/frontend/src/gui.cpp
Normal file
216
src/frontend/src/gui.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#include <core.h>
|
||||
#include <gui.hpp>
|
||||
#include <utils.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
#define GLSL_VERSION "#version 130"
|
||||
|
||||
INLINE void* core_callback(void* args) {
|
||||
Gui* gui = (Gui*)args;
|
||||
while(!atomic_load(&gui->emu_quit)) {
|
||||
clock_t begin = clock();
|
||||
run_frame(&gui->core);
|
||||
clock_t end = clock();
|
||||
gui->delta += end - begin;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Gui::~Gui() {
|
||||
free(rom_file);
|
||||
emu_quit = true;
|
||||
pthread_join(emu_thread, NULL);
|
||||
destroy_core(&core);
|
||||
NFD_Quit();
|
||||
}
|
||||
|
||||
Gui::Gui(const char* title) : context(title, GLSL_VERSION) {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
style.WindowRounding = 10;
|
||||
|
||||
const char* glsl_version = GLSL_VERSION;
|
||||
|
||||
ImGui_ImplSDL2_InitForOpenGL(context.window, context.gl_context);
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
io.Fonts->AddFontFromFileTTF("resources/FiraCode-VariableFont_wght.ttf", 16);
|
||||
|
||||
init_core(&core);
|
||||
|
||||
pthread_create(&emu_thread, NULL, core_callback, (void*)this);
|
||||
|
||||
NFD_Init();
|
||||
}
|
||||
|
||||
void Gui::MainLoop() {
|
||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||
unsigned int frames = 0;
|
||||
while(running) {
|
||||
SDL_Event event;
|
||||
while(SDL_PollEvent(&event)) {
|
||||
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||
|
||||
switch(event.type) {
|
||||
case SDL_QUIT: running = false; break;
|
||||
case SDL_WINDOWEVENT:
|
||||
if(event.window.event == SDL_WINDOWEVENT_CLOSE) {
|
||||
running = false;
|
||||
}
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
switch(event.key.keysym.sym) {
|
||||
case SDLK_o: OpenFile(); break;
|
||||
case SDLK_p:
|
||||
if(rom_loaded) {
|
||||
core.running = !core.running;
|
||||
}
|
||||
break;
|
||||
case SDLK_ESCAPE: running = false; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
context.Frame();
|
||||
|
||||
if(show_metrics) ImGui::ShowMetricsWindow(&show_metrics);
|
||||
DebuggerWindow();
|
||||
|
||||
context.MainWindow(this, &core);
|
||||
|
||||
frames++;
|
||||
|
||||
if(clock_to_ms(delta) > 1000.0) {
|
||||
fps = (double)frames * 0.5 + fps *0.5;
|
||||
frames = 0;
|
||||
delta -= CLOCKS_PER_SEC;
|
||||
frametime = 1000.0 / ((fps == 0) ? 0.001 : fps);
|
||||
}
|
||||
|
||||
context.Update(io);
|
||||
}
|
||||
}
|
||||
|
||||
void Gui::MainMenubar() {
|
||||
ImVec2 window_size = ImGui::GetWindowSize();
|
||||
if(ImGui::BeginMenuBar()) {
|
||||
if(ImGui::BeginMenu("File")) {
|
||||
if(ImGui::MenuItem("Open", "O")) {
|
||||
OpenFile();
|
||||
}
|
||||
|
||||
if(ImGui::MenuItem("Exit", "Esc")) {
|
||||
running = false;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if(ImGui::BeginMenu("Emulation")) {
|
||||
std::string pause_text = "Pause";
|
||||
if(!core.running && rom_loaded) {
|
||||
pause_text = "Resume";
|
||||
}
|
||||
|
||||
if(ImGui::MenuItem(pause_text.c_str(), "P", false, rom_loaded)) {
|
||||
core.running = !core.running;
|
||||
if(core.running) {
|
||||
core.stepping = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(ImGui::MenuItem("Stop", NULL, false, rom_loaded)) {
|
||||
Stop();
|
||||
}
|
||||
|
||||
if(ImGui::MenuItem("Reset", NULL, false, rom_loaded)) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if(ImGui::BeginMenu("Settings")) {
|
||||
ImGui::MenuItem("Show disassembly", NULL, &show_disasm, true);
|
||||
ImGui::MenuItem("Show register watch", NULL, &show_regs, true);
|
||||
ImGui::MenuItem("Show logs", NULL, &show_logs, true);
|
||||
ImGui::MenuItem("Show metrics", NULL, &show_metrics, true);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImVec2 close_button_size = ImGui::CalcTextSize("[X]");
|
||||
char fps_text[255];
|
||||
sprintf(fps_text, rom_loaded ? "[ %.2f fps ][ %.2f ms ]" : "[ NaN fps ][ NaN ms ]", fps, frametime);
|
||||
ImVec2 fps_size = ImGui::CalcTextSize(fps_text);
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImGui::SameLine(window_size.x - close_button_size.x - fps_size.x - style.ItemInnerSpacing.x * 4 - 12, -1);
|
||||
ImGui::Text("%s", fps_text);
|
||||
ImGui::SameLine(window_size.x - close_button_size.x - style.ItemInnerSpacing.x * 2 - 12, -1);
|
||||
|
||||
if(ImGui::BeginMenu("[X]")) {
|
||||
running = false;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
void Gui::RegistersView() {
|
||||
registers_t* regs = &core.cpu.regs;
|
||||
ImGui::Begin("Registers view", &show_regs, 0);
|
||||
for(int i = 0; i < 32; i+=4) {
|
||||
ImGui::Text("%s: %016lX %s: %016lX %s: %016lX %s: %016lX", regs_str[i], regs->gpr[i], regs_str[i + 1], regs->gpr[i + 1], regs_str[i + 2], regs->gpr[i + 2], regs_str[i + 3], regs->gpr[i + 3]);
|
||||
}
|
||||
ImGui::Separator();
|
||||
s64 pipe[3] = {regs->old_pc, regs->pc, regs->next_pc};
|
||||
for(int i = 0; i < 3; i++) {
|
||||
ImGui::Text("pipe[%d]: %016lX", i, pipe[i]);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void Gui::DebuggerWindow() {
|
||||
if(show_regs) RegistersView();
|
||||
if(show_logs) logger.LogWindow(this);
|
||||
}
|
||||
|
||||
void Gui::OpenFile() {
|
||||
nfdfilteritem_t filter = { "Nintendo 64 roms", "n64,z64,v64,N64,Z64,V64" };
|
||||
nfdresult_t result = NFD_OpenDialog(&rom_file, &filter, 1, EMU_DIR);
|
||||
if(result == NFD_OKAY) {
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Gui::Start() {
|
||||
rom_loaded = load_rom(&core.mem, rom_file);
|
||||
emu_quit = !rom_loaded;
|
||||
core.running = rom_loaded;
|
||||
if(rom_loaded) {
|
||||
pthread_create(&emu_thread, NULL, core_callback, (void*)this);
|
||||
}
|
||||
}
|
||||
|
||||
void Gui::Reset() {
|
||||
Stop();
|
||||
Start();
|
||||
}
|
||||
|
||||
void Gui::Stop() {
|
||||
emu_quit = true;
|
||||
pthread_join(emu_thread, NULL);
|
||||
init_core(&core);
|
||||
rom_loaded = false;
|
||||
core.running = false;
|
||||
}
|
||||
27
src/frontend/src/gui/logger.cpp
Normal file
27
src/frontend/src/gui/logger.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <gui/logger.hpp>
|
||||
#include <gui.hpp>
|
||||
|
||||
Logger::Logger() {
|
||||
logger.InfoStr = message_type_strings[0].c_str();
|
||||
logger.WarnStr = message_type_strings[1].c_str();
|
||||
logger.ErrorStr = message_type_strings[2].c_str();
|
||||
}
|
||||
|
||||
void Logger::LogWindow(Gui* gui) {
|
||||
std::string final_message{};
|
||||
if(last_message != nullptr && strcmp(last_message, "") && strcmp(last_message, gui->old_message.c_str())) {
|
||||
if(last_message_type == FATAL) {
|
||||
if(gui->pause_on_fatal) {
|
||||
gui->core.running = false;
|
||||
} else {
|
||||
gui->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
gui->old_message = std::string(last_message);
|
||||
gui->old_message_type = last_message_type;
|
||||
final_message = message_type_strings[last_message_type] + " " + gui->old_message;
|
||||
logger.AddLog("%s", final_message.c_str());
|
||||
}
|
||||
logger.Draw("Logs", &gui->show_logs);
|
||||
}
|
||||
146
src/frontend/src/gui/opengl_context.cpp
Normal file
146
src/frontend/src/gui/opengl_context.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
#include <gui/opengl_context.hpp>
|
||||
#include <log.h>
|
||||
#include <gui.hpp>
|
||||
|
||||
OpenGLContext::~OpenGLContext() {
|
||||
free(framebuffer);
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
SDL_GL_DeleteContext(gl_context);
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
OpenGLContext::OpenGLContext(const char* title, const char* m_glsl_version) {
|
||||
if(SDL_Init(SDL_INIT_VIDEO) != 0) {
|
||||
logfatal("Error: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
const char* glsl_version = m_glsl_version;
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
|
||||
SDL_DisplayMode mode;
|
||||
SDL_GetCurrentDisplayMode(0, &mode);
|
||||
int w = mode.w - (mode.w / 4), h = mode.h - (mode.h / 4);
|
||||
|
||||
window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
||||
gl_context = SDL_GL_CreateContext(window);
|
||||
SDL_GL_MakeCurrent(window, gl_context);
|
||||
SDL_GL_SetSwapInterval(0); // Enable vsync
|
||||
|
||||
framebuffer = (u8*)malloc(320 * 240 * 4);
|
||||
memset(framebuffer, 0x000000ff, 320 * 240 * 4);
|
||||
|
||||
glGenTextures(1, &id);
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 320, 240, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, framebuffer);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
}
|
||||
|
||||
ImVec2 image_size;
|
||||
|
||||
INLINE void resize_callback(ImGuiSizeCallbackData* data) {
|
||||
ImVec2 window_size = ImGui::GetWindowSize();
|
||||
float x = window_size.x - 15, y = window_size.y - 15;
|
||||
float current_aspect_ratio = x / y;
|
||||
|
||||
if(N64_ASPECT_RATIO > current_aspect_ratio) {
|
||||
y = x / (N64_ASPECT_RATIO);
|
||||
} else {
|
||||
x = y * (N64_ASPECT_RATIO);
|
||||
}
|
||||
|
||||
image_size.x = x;
|
||||
image_size.y = y - 30;
|
||||
}
|
||||
|
||||
void OpenGLContext::UpdateTexture(core_t* core) {
|
||||
u32 w = core->mem.mmio.vi.width, h = 0.75 * w;
|
||||
u32 origin = core->mem.mmio.vi.origin & 0xFFFFFF;
|
||||
u8 format = core->mem.mmio.vi.status.format;
|
||||
bool reconstruct_texture = false;
|
||||
bool res_changed = gl_data.old_w != w || gl_data.old_h != h;
|
||||
bool format_changed = gl_data.old_format != format;
|
||||
|
||||
if(res_changed) {
|
||||
gl_data.old_w = w;
|
||||
gl_data.old_h = h;
|
||||
|
||||
reconstruct_texture = true;
|
||||
}
|
||||
|
||||
if(format_changed) {
|
||||
gl_data.old_format = format;
|
||||
if(format == f5553) {
|
||||
gl_data.glFormat = GL_UNSIGNED_SHORT_5_5_5_1;
|
||||
gl_data.depth = 2;
|
||||
} else if (format == f8888) {
|
||||
gl_data.glFormat = GL_UNSIGNED_INT_8_8_8_8;
|
||||
gl_data.depth = 4;
|
||||
}
|
||||
|
||||
reconstruct_texture = true;
|
||||
}
|
||||
|
||||
if(reconstruct_texture) {
|
||||
framebuffer = (u8*)realloc(framebuffer, w * h * gl_data.depth);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, gl_data.glFormat, framebuffer);
|
||||
}
|
||||
|
||||
if(format == f8888) {
|
||||
framebuffer[4] = 0xff;
|
||||
memcpy(framebuffer, &core->mem.rdram[origin & RDRAM_DSIZE], w * h * gl_data.depth);
|
||||
for(int i = 0; i < w * h * gl_data.depth; i += gl_data.depth) {
|
||||
framebuffer[i + 4] |= 0xff;
|
||||
}
|
||||
} else {
|
||||
framebuffer[1] |= 1;
|
||||
for(int i = 0; i < w * h * gl_data.depth; i += gl_data.depth) {
|
||||
framebuffer[i] = core->mem.rdram[HALF_ADDR(origin + i & RDRAM_DSIZE)];
|
||||
framebuffer[i + 1] = core->mem.rdram[HALF_ADDR(origin + 1 + i & RDRAM_DSIZE)] | (1 << 16);
|
||||
}
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, gl_data.glFormat, framebuffer);
|
||||
}
|
||||
|
||||
void OpenGLContext::MainWindow(Gui* gui, core_t *core) {
|
||||
ImGui::SetNextWindowSizeConstraints((ImVec2){0, 0}, (ImVec2){__FLT_MAX__, __FLT_MAX__}, resize_callback, NULL);
|
||||
ImGui::Begin("Display", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking);
|
||||
gui->MainMenubar();
|
||||
UpdateTexture(core);
|
||||
ImVec2 window_size = ImGui::GetWindowSize();
|
||||
ImVec2 result = {static_cast<float>((window_size.x - image_size.x) * 0.5), static_cast<float>((window_size.y - image_size.y + 15) * 0.5)};
|
||||
ImGui::SetCursorPos(result);
|
||||
ImGui::Image((ImTextureID)((intptr_t)id), image_size, (ImVec2){0, 0}, (ImVec2){1, 1}, (ImVec4){1, 1, 1, 1}, (ImVec4){0, 0, 0, 0});
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void OpenGLContext::Frame() {
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void OpenGLContext::Update(ImGuiIO& io) {
|
||||
ImGui::Render();
|
||||
|
||||
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
|
||||
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
7
src/main.cpp
Normal file
7
src/main.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include <gui.hpp>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
Gui gui("懐かしい");
|
||||
gui.MainLoop();
|
||||
return 0;
|
||||
}
|
||||
29
src/natsukashii/include/common.h
Normal file
29
src/natsukashii/include/common.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
|
||||
typedef int8_t s8;
|
||||
typedef int16_t s16;
|
||||
typedef int32_t s32;
|
||||
|
||||
#define PACKED __attribute__((__packed__))
|
||||
#define INLINE static inline __attribute__((always_inline))
|
||||
|
||||
#define GB_ASPECT_RATIO (float)10 / 9
|
||||
#define BOOTROM_SIZE 0x100
|
||||
#define BOOTROM_DSIZE (BOOTROM_SIZE - 1)
|
||||
#define EXTRAM_SIZE 0x2000
|
||||
#define EXTRAM_DSIZE (EXTRAM_SIZE - 1)
|
||||
#define WRAM_SIZE 0x2000
|
||||
#define WRAM_DSIZE (WRAM_SIZE - 1)
|
||||
#define HRAM_SIZE 0x7f
|
||||
#define HRAM_DSIZE (HRAM_SIZE - 1)
|
||||
#define ROM_SIZE_MIN 0x8000
|
||||
#define ROM_DSIZE_MIN (ROM_SIZE_MIN - 1)
|
||||
#ifndef EMU_DIR
|
||||
#define EMU_DIR ""
|
||||
#endif
|
||||
21
src/natsukashii/include/core.h
Normal file
21
src/natsukashii/include/core.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <core/cpu.h>
|
||||
#include <core/mem.h>
|
||||
|
||||
typedef struct core_t {
|
||||
cpu_t cpu;
|
||||
mem_t mem;
|
||||
u32 break_addr;
|
||||
bool running, stepping;
|
||||
} core_t;
|
||||
|
||||
void init_core(core_t* core);
|
||||
void destroy_core(core_t* core);
|
||||
void run_frame(core_t* core);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
21
src/natsukashii/include/core/cpu.h
Normal file
21
src/natsukashii/include/core/cpu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <mem.h>
|
||||
#include <registers.h>
|
||||
|
||||
typedef struct {
|
||||
bool halt, skip, ime, ei;
|
||||
u8 cycles, opcode;
|
||||
registers_t registers;
|
||||
} cpu_t;
|
||||
|
||||
void init_cpu(cpu_t* cpu, bool skip);
|
||||
void destroy_cpu(cpu_t *cpu);
|
||||
void handle_interrupts(cpu_t* cpu);
|
||||
|
||||
INLINE void reset(cpu_t* cpu) {
|
||||
bool skip = cpu->skip;
|
||||
destroy_cpu(cpu);
|
||||
init_cpu(cpu, skip);
|
||||
}
|
||||
|
||||
u8 step_cpu(cpu_t* cpu);
|
||||
113
src/natsukashii/include/core/cpu/registers.h
Normal file
113
src/natsukashii/include/core/cpu/registers.h
Normal file
@@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
#include <utils.h>
|
||||
#include <mem.h>
|
||||
|
||||
typedef struct {
|
||||
REGS_UNION(a, f);
|
||||
REGS_UNION(b, c);
|
||||
REGS_UNION(d, e);
|
||||
REGS_UNION(h, l);
|
||||
|
||||
u16 sp, pc;
|
||||
} registers_t;
|
||||
|
||||
INLINE bool get_cond(registers_t* regs, u8 opcode) {
|
||||
if (opcode & 1) {
|
||||
return true;
|
||||
}
|
||||
u8 bits = (opcode >> 3) & 3;
|
||||
switch (bits) {
|
||||
case 0: return !regs->f.z;
|
||||
case 1: return regs->f.z;
|
||||
case 2: return !regs->f.c;
|
||||
case 3: return regs->f.c;
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void reg_push(registers_t* regs, mem_t* mem, u16 val) {
|
||||
regs->sp -= 2;
|
||||
write16(mem, regs->sp, val);
|
||||
}
|
||||
|
||||
INLINE u16 reg_pop(registers_t* regs, mem_t* mem) {
|
||||
u16 val = read16(mem, regs->sp);
|
||||
regs->sp += 2;
|
||||
return val;
|
||||
}
|
||||
|
||||
INLINE u8 read_reg8(registers_t* regs, mem_t* mem, u8 bits) {
|
||||
switch (bits) {
|
||||
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 read8(mem, regs->hl);
|
||||
case 7: return regs->a;
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void write_reg8(registers_t* regs, mem_t* mem, u8 bits, u8 val) {
|
||||
switch (bits) {
|
||||
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: write8(mem, regs->hl, val); break;
|
||||
case 7: regs->a = val; break;
|
||||
}
|
||||
}
|
||||
|
||||
INLINE u16 read_reg16(registers_t* regs, int group, u8 bits) {
|
||||
if (group == 1) {
|
||||
switch (bits) {
|
||||
case 0: return regs.bc;
|
||||
case 1: return regs.de;
|
||||
case 2: return regs.hl;
|
||||
case 3: return regs.sp;
|
||||
}
|
||||
} else if (group == 2) {
|
||||
switch (bits) {
|
||||
case 0: return regs.bc;
|
||||
case 1: return regs.de;
|
||||
case 2: case 3: return regs.hl;
|
||||
}
|
||||
} else if (group == 3) {
|
||||
switch (bits) {
|
||||
case 0: return regs.bc;
|
||||
case 1: return regs.de;
|
||||
case 2: return regs.hl;
|
||||
case 3: return regs.af;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void write_reg16(registers_t* regs, int group, u8 bits, u16 value) {
|
||||
if (group == 1) {
|
||||
switch (bits) {
|
||||
case 0: regs.bc = value; break;
|
||||
case 1: regs.de = value; break;
|
||||
case 2: regs.hl = value; break;
|
||||
case 3: regs.sp = value; break;
|
||||
}
|
||||
} else if (group == 2) {
|
||||
switch (bits) {
|
||||
case 0: regs.bc = value; break;
|
||||
case 1: regs.de = value; break;
|
||||
case 2: case 3: regs.hl = value; break;
|
||||
}
|
||||
} else if (group == 3) {
|
||||
switch (bits) {
|
||||
case 0: regs.bc = value; break;
|
||||
case 1: regs.de = value; break;
|
||||
case 2: regs.hl = value; break;
|
||||
case 3:
|
||||
regs->a = value >> 8;
|
||||
regs->f = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/natsukashii/include/core/mem.h
Normal file
38
src/natsukashii/include/core/mem.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include <common.h>
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
struct {
|
||||
unsigned:2;
|
||||
unsigned select_btn:1;
|
||||
unsigned select_dpad:1;
|
||||
unsigned btn_start_down:1;
|
||||
unsigned btn_select_up:1;
|
||||
unsigned btn_a_left:1;
|
||||
unsigned btn_b_right:1;
|
||||
} PACKED;
|
||||
u8 raw;
|
||||
};
|
||||
|
||||
bool dpad, button;
|
||||
} joyp_t;
|
||||
|
||||
INLINE void joyp_write(joyp_t* joyp, u8 val) {
|
||||
joyp->raw = val;
|
||||
joyp->raw |= 0b11000000;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
u8 bootrom, intf;
|
||||
u8 nr41, nr42, nr43, nr44, nr50, nr51, nr52;
|
||||
joyp_t joyp;
|
||||
} io_t;
|
||||
|
||||
typedef struct {
|
||||
u8 ie;
|
||||
io_t io;
|
||||
u8 bootrom[BOOTROM_SIZE];
|
||||
u8 wram[WRAM_SIZE];
|
||||
u8 hram[HRAM_SIZE];
|
||||
} mem_t;
|
||||
9
src/natsukashii/include/utils.h
Normal file
9
src/natsukashii/include/utils.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#define REGS_UNION(f, s) \
|
||||
union { \
|
||||
struct { \
|
||||
u8 ##s; \
|
||||
u8 ##f; \
|
||||
} PACKED; \
|
||||
u16 ##fs; \
|
||||
};
|
||||
Reference in New Issue
Block a user