#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(); }