From ce9464bfd0f4ea29acafeedf3eddf62bf146808a Mon Sep 17 00:00:00 2001 From: Slendi Date: Wed, 19 Jun 2024 22:26:46 +0300 Subject: [PATCH] Initial commit Signed-off-by: Slendi --- .clang-format | 14 + .gitignore | 4 + CMakeLists.txt | 48 +++ callback.cpp | 25 ++ callback.h | 3 + main.cpp | 923 +++++++++++++++++++++++++++++++++++++++++++++++++ util.h | 15 + 7 files changed, 1032 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 callback.cpp create mode 100644 callback.h create mode 100644 main.cpp create mode 100644 util.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8e0cd24 --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +--- +BasedOnStyle: WebKit +AlignConsecutiveMacros: 'true' +AlignConsecutiveDeclarations: 'true' +AlwaysBreakTemplateDeclarations: 'Yes' +BreakBeforeBraces: Attach +ConstructorInitializerIndentWidth: '2' +TabWidth: '4' +UseTab: ForIndentation +QualifierAlignment: Right +PointerAlignment: Right +ColumnLimit: 100 +InsertNewlineAtEOF: 'true' +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b63d872 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +build-rel +.cache + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e448c47 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 3.5) + +project(psp-chip8) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic") + +set(SOURCES + callback.cpp + main.cpp +) + +add_executable(${PROJECT_NAME} ${SOURCES}) + +target_compile_definitions(${PROJECT_NAME} PRIVATE __GLIBC_USE=0) + +target_link_libraries(${PROJECT_NAME} PRIVATE + pspgum + pspgu + pspge + pspaudio + pspaudiolib + pspvram + pspdisplay + pspdebug + pspctrl + psppower + + m +) + +# Create an EBOOT.PBP file +create_pbp_file( + TARGET ${PROJECT_NAME} + ICON_PATH NULL + BACKGROUND_PATH NULL + PREVIEW_PATH NULL + TITLE ${PROJECT_NAME} + VERSION 01.00 +) + +add_custom_target( + run + COMMAND PPSSPPSDL ./EBOOT.PBP + DEPENDS EBOOT.PBP +) + diff --git a/callback.cpp b/callback.cpp new file mode 100644 index 0000000..f6e08ba --- /dev/null +++ b/callback.cpp @@ -0,0 +1,25 @@ +#include + +int exit_callback(int arg1, int arg2, void *common) { + sceKernelExitGame(); + return 0; +} + +int callback_thread(SceSize args, void *argp) { + int cbid; + + cbid = sceKernelCreateCallback("Exit Callback", (SceKernelCallbackFunction)exit_callback, NULL); + sceKernelRegisterExitCallback(cbid); + sceKernelSleepThreadCB(); + + return 0; +} + +int setup_callbacks(void) { + int thid = 0; + + thid = sceKernelCreateThread("update_thread", callback_thread, 0x11, 0xFA0, 0, 0); + if (thid >= 0) + sceKernelStartThread(thid, 0, 0); + return thid; +} diff --git a/callback.h b/callback.h new file mode 100644 index 0000000..2022df7 --- /dev/null +++ b/callback.h @@ -0,0 +1,3 @@ +#pragma once + +int setup_callbacks(void); diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..f67d138 --- /dev/null +++ b/main.cpp @@ -0,0 +1,923 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "callback.h" +#include "util.h" + +namespace fs = std::filesystem; + +PSP_MODULE_INFO("psp-chip8", 0, 1, 1); +PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU); + +static unsigned int __attribute__((aligned(16))) list[262144]; + +#define BUF_WIDTH (512) +#define SCR_WIDTH (480) +#define SCR_HEIGHT (272) + +struct Vertex { + u32 color; + float x, y, z; +}; + +struct TexVertex { + float u, v; + u32 color; + float x, y, z; +}; + +static unsigned int static_offset = 0; + +static unsigned int get_memory_size(unsigned int width, unsigned int height, unsigned int psm) { + switch (psm) { + case GU_PSM_T4: + return (width * height) >> 1; + + case GU_PSM_T8: + return width * height; + + case GU_PSM_5650: + case GU_PSM_5551: + case GU_PSM_4444: + case GU_PSM_T16: + return 2 * width * height; + + case GU_PSM_8888: + case GU_PSM_T32: + return 4 * width * height; + + default: + return 0; + } +} + +void *get_static_vram_buffer(unsigned int width, unsigned int height, unsigned int psm) { + unsigned int mem_sz = get_memory_size(width, height, psm); + void *result = (void *)static_offset; + static_offset += mem_sz; + + return result; +} + +void *get_static_vram_texture(unsigned int width, unsigned int height, unsigned int psm) { + void *result = get_static_vram_buffer(width, height, psm); + return (void *)(((unsigned int)result) + ((unsigned int)sceGeEdramGetAddr())); +} + +void render_texture(float x, float y, float w, float h, u32 buffer[], int tw, int th) { + sceGuTexMode(GU_PSM_8888, 0, 0, 0); + sceGuTexImage(0, tw, th, tw, buffer); + sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGBA); + sceGuTexFilter(GU_NEAREST, GU_NEAREST); + + TexVertex *vertices = (TexVertex *)sceGuGetMemory(2 * sizeof(TexVertex)); + vertices[0] = { 0, 0, 0, x, y, 0.0f }; + vertices[1] = { 1, 1, 0, x + w, y + h, 0.0f }; + + sceGumDrawArray(GU_SPRITES, + GU_TEXTURE_32BITF | GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_3D, 2, 0, vertices); +} + +void render_rectangle(float x, float y, float w, float h, u32 color) { + ScePspFVector3 pos = { x, y, 0.0f }; + + sceGumPushMatrix(); + sceGumTranslate(&pos); + + Vertex __attribute__((aligned(16))) vertices[1 * 6] = { + { color, 0.0f, 0.0f, 0.0f }, // TL + { color, 0.0f, h, 0.0f }, // BL + { color, w, 0.0f, 0.0f }, // TR + + { color, w, 0.0f, 0.0f }, // TR + { color, 0.0f, h, 0.0f }, // BL + { color, w, h, 0.0f }, // BR + }; + + sceGumDrawArray( + GU_TRIANGLES, GU_COLOR_8888 | GU_VERTEX_32BITF | GU_TRANSFORM_3D, 1 * 6, 0, vertices); + sceGumPopMatrix(); +} + +constexpr int const CHIP_W = 64; +constexpr int const CHIP_H = 32; + +enum Key { + KEY_0, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_A, + KEY_B, + KEY_C, + KEY_D, + KEY_E, + KEY_F, +}; + +class Keypad { +public: + enum class Map { + Rogue, + WASD, + END, + }; + + void update(void); + + bool keys[4 * 4] = { 0 }; + + bool start() const { return m_start; } + bool second() const { return m_second; } + Map map() const { return m_map; } + +private: + SceCtrlData m_pad; + + bool m_start = false; + bool m_second = false; + + Map m_map = Map::Rogue; + bool m_prev_rtrigger = false; +}; + +char const *KeypadMapCString(Keypad::Map map) { + switch (map) { + case Keypad::Map::Rogue: + return "Rogue"; + case Keypad::Map::WASD: + return "WASD"; + case Keypad::Map::END: + return "Invalid"; + } + return "UNKNOWN"; +} + +void Keypad::update(void) { + sceCtrlReadBufferPositive(&m_pad, 1); + + for (auto &key : keys) + key = false; + + if (!m_pad.Buttons) + return; + + if (!m_prev_rtrigger && (m_pad.Buttons & PSP_CTRL_RTRIGGER)) { + int c = (int)m_map; + c++; + c %= (int)Map::END; + m_map = (Map)c; + } + + if (m_map == Map::Rogue) { + if (m_pad.Buttons & PSP_CTRL_LTRIGGER) { + keys[KEY_A] = m_pad.Buttons & PSP_CTRL_LEFT; + keys[KEY_B] = m_pad.Buttons & PSP_CTRL_RIGHT; + keys[KEY_C] = m_pad.Buttons & PSP_CTRL_SQUARE; + keys[KEY_D] = m_pad.Buttons & PSP_CTRL_TRIANGLE; + keys[KEY_E] = m_pad.Buttons & PSP_CTRL_CROSS; + keys[KEY_F] = m_pad.Buttons & PSP_CTRL_CIRCLE; + m_second = true; + } else { + keys[KEY_1] = m_pad.Buttons & PSP_CTRL_CROSS; + keys[KEY_2] = m_pad.Buttons & PSP_CTRL_UP; + keys[KEY_3] = m_pad.Buttons & PSP_CTRL_TRIANGLE; + keys[KEY_4] = m_pad.Buttons & PSP_CTRL_LEFT; + keys[KEY_5] = m_pad.Buttons & PSP_CTRL_CIRCLE; + keys[KEY_6] = m_pad.Buttons & PSP_CTRL_RIGHT; + keys[KEY_7] = m_pad.Buttons & PSP_CTRL_SQUARE; + keys[KEY_8] = m_pad.Buttons & PSP_CTRL_DOWN; + keys[KEY_9] = m_pad.Buttons & PSP_CTRL_SELECT; + m_second = false; + } + } else if (m_map == Map::WASD) { + if (m_pad.Buttons & PSP_CTRL_LTRIGGER) { + m_second = true; + } else { + keys[KEY_1] = m_pad.Buttons & PSP_CTRL_CROSS; + keys[KEY_2] = m_pad.Buttons & PSP_CTRL_TRIANGLE; + keys[KEY_3] = m_pad.Buttons & PSP_CTRL_CIRCLE; + keys[KEY_5] = m_pad.Buttons & PSP_CTRL_UP; + keys[KEY_7] = m_pad.Buttons & PSP_CTRL_LEFT; + keys[KEY_8] = m_pad.Buttons & PSP_CTRL_DOWN; + keys[KEY_9] = m_pad.Buttons & PSP_CTRL_RIGHT; + m_second = false; + } + } + + m_prev_rtrigger = m_pad.Buttons & PSP_CTRL_RTRIGGER; + + m_start = m_pad.Buttons & PSP_CTRL_START; +} + +struct CHIP8 { + byte memory[0x1000]; + std::vector stack; + byte display[CHIP_W * CHIP_H] = { 0 }; + byte registers[16] = { 0 }; + Keypad keypad; + + std::string message; + + u16 pc = 0x200; + u16 i = 0; + u8 delay_timer = 0, sound_timer = 0; + bool halted = true; + + CHIP8(); + + void init(); + void load(byte buffer[], int size); + void step(); +}; + +u32 *g_fb = nullptr; + +void CHIP8::load(byte buffer[], int size) { + init(); + for (int i = 0; i < size; i++) + this->memory[0x200 + i] = buffer[i]; + this->halted = false; +} + +void update_fb(CHIP8 *chip) { + if (!g_fb) + return; + + for (int y = 0; y < CHIP_H; y++) { + for (int x = 0; x < CHIP_W; x++) { + u32 color = 0xff020202; + if (chip->display[x + y * CHIP_W]) + color = 0xffaaaaaa; + g_fb[x + y * CHIP_W] = color; + } + } + + sceKernelDcacheWritebackInvalidateRange(g_fb, CHIP_W * CHIP_H * 4); +} + +void CHIP8::step() { + if (halted) + return; + + byte instr[2] { + this->memory[this->pc++], + this->memory[this->pc++], + }; + u16 whole = instr[0] << 8 | instr[1]; + + byte nibbles[4] { + static_cast((instr[0] & 0xf0) >> 4), + static_cast((instr[0] & 0x0f)), + static_cast((instr[1] & 0xf0) >> 4), + static_cast((instr[1] & 0x0f)), + }; + + auto X = (whole & 0x0F00) >> 8; + auto Y = (whole & 0x00F0) >> 4; + auto N = nibbles[3]; + auto NN = instr[1]; + u16 NNN = (nibbles[1] << 8) | (nibbles[2] << 4) | (nibbles[3]); + + switch (nibbles[0]) { + case 0x0: + if (instr[1] == 0xE0) { // CLS - Clear the display. + for (auto &i : display) + i = false; + + update_fb(this); + } else if (instr[1] == 0xEE) { // RET - Return from a subroutine. + this->pc = stack.back(); + stack.pop_back(); + } else { + // Ignore. + } + break; + case 0x1: // JP addr - Jump to location nnn. + this->pc = NNN; + break; + case 0x2: // CALL addr - Call subroutine at nnn. + stack.push_back(this->pc); + this->pc = NNN; + break; + case 0x3: // SE Vx, byte - Skip next instruction if Vx = kk. + if (this->registers[X] == NN) + this->pc += 2; + break; + case 0x4: // SNE Vx, byte - Skip next instruction if Vx != kk. + if (this->registers[X] != NN) + this->pc += 2; + break; + case 0x5: + if (nibbles[3] == 0) { // SE Vx, Vy - Skip next instruction if Vx = Vy. + if (this->registers[X] == this->registers[Y]) + this->pc += 2; + } else { + this->halted = true; + this->pc -= 2; + this->message = std::format("Invalid instruction: 0x{:x}", whole); + } + break; + case 0x6: // LD Vx, byte - Set Vx = kk. + this->registers[X] = NN; + break; + case 0x7: // ADD Vx, byte - Set Vx = Vx + kk. + this->registers[X] += NN; + break; + case 0x8: + switch (nibbles[3]) { + case 0x0: // LD Vx, Vy - Set Vx = Vy. + this->registers[X] = this->registers[Y]; + break; + case 0x1: // OR Vx, Vy - Set Vx = Vx OR Vy. + this->registers[X] |= this->registers[Y]; + this->registers[0xF] = 0; + break; + case 0x2: // AND Vx, Vy Set Vx = Vx AND Vy. + this->registers[X] &= this->registers[Y]; + this->registers[0xF] = 0; + break; + case 0x3: // XOR Vx, Vy Set Vx = Vx XOR Vy. + this->registers[X] ^= this->registers[Y]; + this->registers[0xF] = 0; + break; + case 0x4: { + // ADD Vx, Vy Set Vx = Vx + Vy, set VF = carry. + unsigned sum = (unsigned)(this->registers[X]) + (unsigned)(this->registers[Y]); + this->registers[X] = sum & 0xff; + this->registers[0xF] = 0; + if (sum > 0xFF) + this->registers[0xF] = 1; + break; + } + case 0x5: { // SUB Vx, Vy Set Vx = Vx - Vy, set VF = NOT borrow. + bool not_borrow = this->registers[X] > this->registers[Y]; + this->registers[X] -= this->registers[Y]; + this->registers[0xF] = not_borrow; + break; + } + case 0x6: { // SHR Vx {, Vy} Set Vx = Vx SHR 1. + bool not_borrow = this->registers[X] & 1; + this->registers[X] = this->registers[Y] >> 1; + this->registers[0xf] = not_borrow; + break; + } + case 0x7: { // SUBN Vx, Vy Set Vx = Vy - Vx, set VF = NOT borrow. + bool not_borrow = this->registers[X] < this->registers[Y]; + this->registers[X] = this->registers[Y] - this->registers[X]; + this->registers[0xF] = not_borrow; + break; + } + case 0xE: // SHL Vx {, Vy} Set Vx = Vx SHL 1. + uint8_t vf = this->registers[X] >> 7; + this->registers[X] = this->registers[Y] << 1; + this->registers[0xF] = vf; + break; + } + break; + case 0x9: + if (nibbles[3] == 0) { // SNE Vx, Vy - Skip next instruction if Vx != Vy. + if (this->registers[X] != this->registers[Y]) + this->pc += 2; + } else { + this->halted = true; + this->pc -= 2; + this->message = std::format("Invalid instruction: 0x{:x}", whole); + } + break; + case 0xA: // LD I, addr - Set I = nnn. + this->i = NNN; + break; + case 0xB: // JP V0, addr - Jump to location nnn + V0. + this->pc = this->registers[0] + NNN; + break; + case 0xC: // RND Vx, byte - Set Vx = random byte AND kk. + this->registers[X] = (rand() % 0x100) & NN; + break; + case 0xD: { + // DRW Vx, Vy, nibble - Display n-byte sprite starting at memory location I at + // (Vx, Vy), set VF = collision. + + byte sx = this->registers[X] % CHIP_W; + byte sy = this->registers[Y] % CHIP_H; + this->registers[0xF] = 0; + + for (int y = 0; y < N; y++) { + if (sy + y >= CHIP_H) + break; + + byte row = this->memory[this->i + y]; + for (int x = 0; x < 8; x++) { + if (sx + x >= CHIP_W) + break; + + if ((row & (0x80 >> x)) == 0) + continue; + + if (this->display[(sx + x) % CHIP_W + ((sy + y) % CHIP_H) * CHIP_W]) + this->registers[0xF] = 1; + + this->display[(sx + x) % CHIP_W + ((sy + y) % CHIP_H) * CHIP_W] ^= 1; + } + } + + update_fb(this); // Ensure this function is correctly updating the frame buffer + + break; + } + case 0xE: + if (instr[1] == 0x9e) { + // SKP Vx - Skip next instruction if key with the value of Vx is pressed. + if (this->keypad.keys[this->registers[X]]) + this->pc += 2; + } else if (instr[1] == 0xa1) { + // SKNP Vx - Skip next instruction if key with the value of Vx is not pressed. + if (!this->keypad.keys[this->registers[X]]) + this->pc += 2; + } else { + this->halted = true; + this->pc -= 2; + this->message = std::format("Invalid instruction: 0x{:x}", whole); + } + break; + case 0xF: + switch (instr[1]) { + case 0x07: // LD Vx, DT - Set Vx = delay timer value. + this->registers[X] = this->delay_timer; + break; + case 0x0a: { + // LD Vx, K - Wait for a key press, store the value of the key in Vx. + + int idx = 0; + bool found = false; + for (auto key : this->keypad.keys) { + if (key) { + found = true; + break; + } + idx++; + } + + if (!found) { + this->pc -= 2; + this->message = "Awaiting input..."; + } else { + this->registers[X] = idx; + this->message = ""; + } + + break; + } + case 0x15: // LD DT, Vx - Set delay timer = Vx. + this->delay_timer = this->registers[X]; + break; + case 0x18: // LD ST, Vx - Set sound timer = Vx. + this->sound_timer = this->registers[X]; + break; + case 0x1e: // ADD I, Vx - Set I = I + Vx. + this->i += this->registers[X]; + break; + case 0x29: // LD F, Vx - Set I = location of sprite for digit Vx. + this->i = 0x50 + this->registers[X] * 5; + break; + case 0x33: { + // LD B, Vx - Store BCD representation of Vx in memory locations I, I+1, and I+2. + byte digits[] { + static_cast(this->registers[X] / 100), + static_cast((this->registers[X] / 10) % 10), + static_cast(this->registers[X] % 10), + }; + for (int i = 0; i < 3; i++) + this->memory[this->i + i] = digits[i]; + break; + } + case 0x55: // LD [I], Vx - Store registers V0 through Vx in memory starting at location I. + for (int i = 0; i <= X; i++) + this->memory[this->i++] = this->registers[i]; + break; + case 0x65: // LD Vx, [I] - Read registers V0 through Vx from memory starting at location I. + for (int i = 0; i <= X; i++) + this->registers[i] = this->memory[this->i++]; + break; + } + break; + default: + this->halted = true; + this->pc -= 2; + this->message = std::format("Unknown instruction: {}", nibbles[0]); + } +} + +CHIP8::CHIP8() { init(); } + +void update_fb(CHIP8 *chip); + +void CHIP8::init() { + for (auto &b : this->memory) + b = 0; + + for (auto &b : this->registers) + b = 0; + + for (auto &b : this->display) + b = 0; + + update_fb(this); + + this->stack.clear(); + this->pc = 0x200; + this->i = 0; + this->delay_timer = this->sound_timer = 0; + + byte charset[] = { + 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 + 0x20, 0x60, 0x20, 0x20, 0x70, // 1 + 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 + 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 + 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 + 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 + 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 + 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 + 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 + 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 + 0xF0, 0x90, 0xF0, 0x90, 0x90, // A + 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B + 0xF0, 0x80, 0x80, 0x80, 0xF0, // C + 0xE0, 0x90, 0x90, 0x90, 0xE0, // D + 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E + 0xF0, 0x80, 0xF0, 0x80, 0x80, // F + }; + + int i = 0x50; + for (auto const b : charset) + this->memory[i++] = b; + + this->halted = true; +} + +std::optional> load_binary_file(std::string path) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file) + return std::nullopt; + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + auto buffer = new byte[size]; + if (file.read((char *)buffer, size)) + return std::make_pair(buffer, static_cast(size)); + return std::nullopt; +} + +enum class Screen { + RomSelect, + Emulator, +}; + +void get_files(fs::path path, std::vector &list, std::vector &sizes, + std::vector &is_dir) { + list.clear(); + sizes.clear(); + is_dir.clear(); + + list.push_back(fs::path { ".." }); + sizes.push_back(0); + is_dir.push_back(true); + for (auto const &entry : fs::directory_iterator(path)) { + bool dir = true; + int fs = 0; + if (entry.is_regular_file()) { + fs = fs::file_size(entry.path()); + dir = false; + if (fs > (0x1000 - 0x200)) + continue; + if (entry.path().filename().string() == "PARAM.SFO") + continue; + if (entry.path().filename().string() == "compile_commands.json") + continue; + if (entry.path().filename().string() == "cmake_install.cmake") + continue; + } else if (!entry.is_directory()) + continue; + + if (entry.path().filename().string() == "CMakeFiles") + continue; + + is_dir.push_back(dir); + list.push_back(entry.path().filename().string()); + sizes.push_back(fs); + } +} + +fs::path resolve_path(fs::path const &path) { + std::stack components; + + for (auto const &part : path) { + if (part == "..") { + if (!components.empty()) + components.pop(); + } else { + components.push(part); + } + } + + fs::path resolved_path; + while (!components.empty()) { + resolved_path = components.top() / resolved_path; + components.pop(); + } + + if (resolved_path.empty()) + resolved_path = fs::path { "./" }; + + return resolved_path; +} + +CHIP8 g_chip8; + +int g_step_skips = 20; +int g_artificial_delay = 1; + +int chip_thread(SceSize args, void *argp) { + (void)args; + (void)argp; + for (;;) { + g_chip8.keypad.update(); + + for (int i = 0; i < g_step_skips; i++) + g_chip8.step(); + + std::this_thread::sleep_for(std::chrono::milliseconds(g_artificial_delay)); + } +} + +int main(void) { + srand(time(NULL)); + + pspDebugScreenInit(); + setup_callbacks(); + + scePowerUnlock(0); + scePowerSetClockFrequency(333, 333, 166); + + g_fb = (u32 *)get_static_vram_texture(CHIP_W, CHIP_H, GU_PSM_8888); + for (int x = 0; x < CHIP_W; x++) + for (int y = 0; y < CHIP_H; y++) + g_fb[x + CHIP_W * y] = 0xff0000ff; + + void *fbp0 = get_static_vram_buffer(BUF_WIDTH, SCR_HEIGHT, GU_PSM_8888); + void *fbp1 = get_static_vram_buffer(BUF_WIDTH, SCR_HEIGHT, GU_PSM_8888); + void *zbp = get_static_vram_buffer(BUF_WIDTH, SCR_HEIGHT, GU_PSM_4444); + + sceGuInit(); + + sceGuStart(GU_DIRECT, list); + sceGuDrawBuffer(GU_PSM_8888, fbp0, BUF_WIDTH); + sceGuDispBuffer(SCR_WIDTH, SCR_HEIGHT, fbp1, BUF_WIDTH); + sceGuDepthBuffer(zbp, BUF_WIDTH); + sceGuOffset(2048 - (SCR_WIDTH / 2), 2048 - (SCR_HEIGHT / 2)); + sceGuViewport(2048, 2048, SCR_WIDTH, SCR_HEIGHT); + sceGuDepthRange(65535, 0); + sceGuScissor(0, 0, SCR_WIDTH, SCR_HEIGHT); + sceGuEnable(GU_SCISSOR_TEST); + sceGuAlphaFunc(GU_GREATER, 0, 0xff); + sceGuEnable(GU_ALPHA_TEST); + sceGuFrontFace(GU_CW); + sceGuShadeModel(GU_SMOOTH); + sceGuEnable(GU_CULL_FACE); + sceGuEnable(GU_TEXTURE_2D); + sceGuTexFunc(GU_TFX_MODULATE, GU_TCC_RGBA); + sceGuTexFilter(GU_NEAREST, GU_NEAREST); + sceGuTexWrap(GU_CLAMP, GU_CLAMP); + sceGuFinish(); + sceGuSync(0, 0); + + sceDisplayWaitVblankStart(); + sceGuDisplay(1); + + sceCtrlSetSamplingCycle(0); + sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG); + + std::string filename; + + auto screen = Screen::RomSelect; + + auto last = std::chrono::high_resolution_clock::now(); + + std::vector file_list; + std::vector file_sizes; + std::vector file_is_dir; + fs::path path { "." }; + ssize_t menu_y = 0; + + get_files(path, file_list, file_sizes, file_is_dir); + + SceUID chip_thread_id + = sceKernelCreateThread("chip_thread", &chip_thread, 0x18, 0x10000, 0, nullptr); + sceKernelStartThread(chip_thread_id, 0, nullptr); + + float t = 0; + for (;;) { + auto ctime = std::chrono::high_resolution_clock::now(); + + std::chrono::duration delta_time = ctime - last; + last = ctime; + + float dt = delta_time.count(); + t += dt; + + if (screen == Screen::Emulator) { + static bool prev_start = false; + if (g_chip8.keypad.start()) { + if (g_chip8.keypad.second()) { + if (!prev_start) + g_chip8.halted = !g_chip8.halted; + } else { + g_chip8.halted = true; + screen = Screen::RomSelect; + } + } + prev_start = g_chip8.keypad.start(); + } + + constexpr int MAX_SCREEN_X = 68; + constexpr int MAX_SCREEN_Y = 33; + + if (t >= 1 / 60.0) { + if (g_chip8.delay_timer) + g_chip8.delay_timer--; + t -= 1 / 60.0; + } + + if (screen == Screen::RomSelect) { + SceCtrlData pad; + static SceCtrlData prev_pad = { 0, 0, 0, 0, { 0, 0, 0, 0, 0, 0 } }; + + sceCtrlPeekBufferPositive(&pad, 1); + + static bool is_fast_scrolling = false; + static float t_input_repeat = 0.0f; + + t_input_repeat += dt; + + bool first_tap_up = (pad.Buttons & PSP_CTRL_UP) && !(prev_pad.Buttons & PSP_CTRL_UP); + bool first_tap_down + = (pad.Buttons & PSP_CTRL_DOWN) && !(prev_pad.Buttons & PSP_CTRL_DOWN); + bool first_tap_triangle + = (pad.Buttons & PSP_CTRL_TRIANGLE) && !(prev_pad.Buttons & PSP_CTRL_TRIANGLE); + bool first_tap_square + = (pad.Buttons & PSP_CTRL_SQUARE) && !(prev_pad.Buttons & PSP_CTRL_SQUARE); + bool first_tap + = first_tap_up || first_tap_down || first_tap_triangle || first_tap_square; + + if (first_tap) + t_input_repeat = 0.0f; + + if (!is_fast_scrolling && t_input_repeat > 0.2f + && (pad.Buttons + & (PSP_CTRL_DOWN | PSP_CTRL_UP | PSP_CTRL_SQUARE | PSP_CTRL_TRIANGLE))) + is_fast_scrolling = true; + + if (!(pad.Buttons + & (PSP_CTRL_SQUARE | PSP_CTRL_TRIANGLE | PSP_CTRL_DOWN | PSP_CTRL_UP))) + is_fast_scrolling = false; + + if (is_fast_scrolling || first_tap) { + t_input_repeat = 0.0f; + + if (pad.Buttons & PSP_CTRL_DOWN) { + menu_y++; + if (menu_y >= file_list.size()) + menu_y = file_list.size() - 1; + } + if (pad.Buttons & PSP_CTRL_UP) { + menu_y--; + if (menu_y < 0) + menu_y = 0; + } + if (pad.Buttons & PSP_CTRL_TRIANGLE) { + g_step_skips += (pad.Buttons & PSP_CTRL_LTRIGGER) ? 1 : -1; + if (g_step_skips < 1) + g_step_skips = 1; + } + if (pad.Buttons & PSP_CTRL_SQUARE) { + g_artificial_delay += (pad.Buttons & PSP_CTRL_LTRIGGER) ? 1 : -1; + if (g_artificial_delay < 1) + g_artificial_delay = 1; + } + } + + if ((pad.Buttons & PSP_CTRL_CROSS) && !(prev_pad.Buttons & PSP_CTRL_CROSS)) { + fs::path newp = path / file_list[menu_y]; + + if (fs::is_directory(newp)) { + path = resolve_path(newp); + get_files(path, file_list, file_sizes, file_is_dir); + } else { + auto data = load_binary_file(newp.c_str()); + if (data.has_value()) { + auto [rom, rom_len] = data.value(); + filename = newp.filename().string(); + screen = Screen::Emulator; + g_chip8.load(rom, rom_len); + g_chip8.message = ""; + } + } + + menu_y = 0; + } + + prev_pad = pad; + } + + sceGuStart(GU_DIRECT, list); + + sceGuClearColor(0xff000000); + sceGuClear(GU_COLOR_BUFFER_BIT); + + sceGumMatrixMode(GU_PROJECTION); + sceGumLoadIdentity(); + sceGumOrtho(0, 480, 272, 0, -1, 1); + + sceGumMatrixMode(GU_VIEW); + sceGumLoadIdentity(); + + sceGumMatrixMode(GU_MODEL); + sceGumLoadIdentity(); + + if (screen == Screen::Emulator) { + constexpr int SCALE = 7; + constexpr int SX = SCR_WIDTH / 2 - CHIP_W * SCALE / 2; + constexpr int SY = SCR_HEIGHT / 2 - CHIP_H * SCALE / 2; + + render_texture(SX, SY, CHIP_W * SCALE, CHIP_H * SCALE, g_fb, CHIP_W, CHIP_H); + } + + sceGuFinish(); + sceGuSync(0, 0); + + pspDebugScreenSetOffset((int)fbp0); + pspDebugScreenSetXY(0, 0); + if (screen == Screen::RomSelect) { + pspDebugScreenSetTextColor(0xFFFFFFFF); + pspDebugScreenPrintf("SK: %d D: %dms Current path: %s\n", g_step_skips, + g_artificial_delay, path.c_str()); + pspDebugScreenSetTextColor(0xFF222222); + for (int i = 0; i < MAX_SCREEN_X; i++) + pspDebugScreenPrintf("="); + ssize_t i = 0; + for (auto &entry : file_list) { + if (i == menu_y) + pspDebugScreenSetTextColor(0xFF113311); + else + pspDebugScreenSetTextColor(0xFFFFFFFF); + + if (file_is_dir[i]) + pspDebugScreenPrintf("D %-66s\n", entry.c_str()); + else + pspDebugScreenPrintf("F %-46s%20d\n", entry.c_str(), file_sizes[i]); + + i++; + } + pspDebugScreenSetTextColor(0xFFFFFFFF); + } else { + pspDebugScreenPrintf("%sMessage: %s Map: %s SK: %d D: %dms File: %s", + g_chip8.halted ? "HALTED " : "", g_chip8.message.c_str(), + KeypadMapCString(g_chip8.keypad.map()), g_step_skips, g_artificial_delay, + filename.c_str()); + } + + sceDisplayWaitVblank(); + fbp0 = sceGuSwapBuffers(); + } + + sceGuTerm(); + + sceKernelExitGame(); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..11dab21 --- /dev/null +++ b/util.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +using i8 = int8_t; +using i16 = int16_t; +using i32 = int32_t; +using i64 = int64_t; + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; + +using byte = u8;