psp-chip8/main.cpp
Slendi c5acf56209 Add config
Signed-off-by: Slendi <slendi@socopon.com>
2024-06-20 19:05:36 +03:00

997 lines
24 KiB
C++

#include <cstdlib>
#include <pspaudio.h>
#include <pspaudiolib.h>
#include <pspctrl.h>
#include <pspdisplay.h>
#include <pspkernel.h>
#include <psppower.h>
#include <pspgu.h>
#include <pspgum.h>
#include <chrono>
#include <filesystem>
#include <format>
#include <fstream>
#include <iostream>
#include <stack>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
#include <unistd.h>
#include "callback.h"
#include "util.h"
#include "toml.hpp"
namespace fs = std::filesystem;
using namespace std::string_literals;
using namespace std::string_view_literals;
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,
};
struct KeyMap {
std::string name;
int keys_normal[4 * 4] = { 0 };
int keys_shift[4 * 4] = { 0 };
};
struct Config {
Config(toml::table const &table);
std::vector<KeyMap> mappings;
};
std::unordered_map<std::string, int> key_map = {
{ "Cross", PSP_CTRL_CROSS },
{ "Triangle", PSP_CTRL_TRIANGLE },
{ "Circle", PSP_CTRL_CIRCLE },
{ "Square", PSP_CTRL_SQUARE },
{ "Up", PSP_CTRL_UP },
{ "Down", PSP_CTRL_DOWN },
{ "Left", PSP_CTRL_LEFT },
{ "Right", PSP_CTRL_RIGHT },
{ "Select", PSP_CTRL_SELECT },
};
std::unordered_map<std::string_view, int> indexes = {
{ "KEY_1"sv, KEY_1 },
{ "KEY_2"sv, KEY_2 },
{ "KEY_3"sv, KEY_3 },
{ "KEY_C"sv, KEY_C },
{ "KEY_4"sv, KEY_4 },
{ "KEY_5"sv, KEY_5 },
{ "KEY_6"sv, KEY_6 },
{ "KEY_D"sv, KEY_D },
{ "KEY_7"sv, KEY_7 },
{ "KEY_8"sv, KEY_8 },
{ "KEY_9"sv, KEY_9 },
{ "KEY_E"sv, KEY_E },
{ "KEY_A"sv, KEY_A },
{ "KEY_0"sv, KEY_0 },
{ "KEY_B"sv, KEY_B },
{ "KEY_F"sv, KEY_F },
};
void parse_keys(toml::table const &table, int *keys) {
for (auto const &[key, value] : table) {
if (value.is_string()) {
std::string value_str = *value.value<std::string>();
auto it = key_map.find(value_str);
if (it != key_map.end()) {
auto it_key = indexes.find(key.str());
if (it_key != indexes.end())
keys[it_key->second] = it->second;
}
}
}
}
Config::Config(toml::table const &table) {
if (auto keymaps = table["keymap"].as_array()) {
int i = 1;
for (auto const &el : *keymaps) {
if (!el.is_table()) {
std::cerr << "Error: Element is not a table" << std::endl;
continue;
}
auto const &map_table = *el.as_table();
KeyMap map;
if (auto name = map_table["name"].value<std::string>())
map.name = *name;
else
map.name = std::string("Unnamed") + std::to_string(i++);
if (auto normal = map_table["normal"].as_table())
parse_keys(*normal, map.keys_normal);
if (auto shifted = map_table["shifted"].as_table())
parse_keys(*shifted, map.keys_shift);
mappings.push_back(map);
}
}
}
class Keypad {
public:
std::vector<KeyMap> mappings;
void update(void);
bool keys[4 * 4] = { 0 };
bool start() const { return m_start; }
bool second() const { return m_second; }
std::string const &map_name() const { return mappings[m_map].name; }
private:
SceCtrlData m_pad;
bool m_start = false;
bool m_second = false;
size_t m_map = 0;
bool m_prev_rtrigger = false;
};
void Keypad::update(void) {
sceCtrlReadBufferPositive(&m_pad, 1);
for (auto &key : keys)
key = false;
if (!m_prev_rtrigger && (m_pad.Buttons & PSP_CTRL_RTRIGGER))
m_map = (m_map + 1) % mappings.size();
if (!m_pad.Buttons || mappings.empty())
goto end;
m_second = m_pad.Buttons & PSP_CTRL_LTRIGGER;
for (int i = 0; i < 4 * 4; i++) {
if (m_second)
keys[i] = m_pad.Buttons & mappings[m_map].keys_shift[i];
else
keys[i] = m_pad.Buttons & mappings[m_map].keys_normal[i];
}
end:
m_prev_rtrigger = m_pad.Buttons & PSP_CTRL_RTRIGGER;
m_start = m_pad.Buttons & PSP_CTRL_START;
}
struct CHIP8 {
byte memory[0x1000];
std::vector<u16> 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<byte>((instr[0] & 0xf0) >> 4),
static_cast<byte>((instr[0] & 0x0f)),
static_cast<byte>((instr[1] & 0xf0) >> 4),
static_cast<byte>((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] ? 1 : 0;
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[Y] > this->registers[X] ? 1 : 0;
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<byte>(this->registers[X] / 100),
static_cast<byte>((this->registers[X] / 10) % 10),
static_cast<byte>(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<std::pair<byte *, int>> 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<int>(size));
return std::nullopt;
}
enum class Screen {
RomSelect,
Emulator,
};
struct FileInfo {
fs::path path;
int size;
bool is_dir;
};
void get_files(fs::path path, std::vector<FileInfo> &list) {
list.clear();
list.push_back(FileInfo { fs::path { ".." }, 0, true });
for (auto const &entry : fs::directory_iterator(path)) {
bool dir = entry.is_directory();
int fs = 0;
if (entry.is_regular_file()) {
fs = static_cast<int>(fs::file_size(entry.path()));
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 (!dir) {
continue;
}
if (entry.path().filename().string() == "CMakeFiles")
continue;
list.push_back(FileInfo { entry.path().filename(), fs, dir });
}
std::sort(list.begin(), list.end(), [](FileInfo const &a, FileInfo const &b) {
if (a.is_dir != b.is_dir)
return a.is_dir > b.is_dir;
return a.path.string() < b.path.string();
});
}
fs::path resolve_path(fs::path const &path) {
std::stack<fs::path> 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();
Config cfg(toml::parse_file("config.toml"));
g_chip8.keypad.mappings = cfg.mappings;
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<FileInfo> file_list;
fs::path path { "." };
ssize_t menu_y = 0;
get_files(path, file_list);
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<float> 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].path;
if (fs::is_directory(newp)) {
path = resolve_path(newp);
get_files(path, file_list);
} 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("=");
static ssize_t scroll_off = 0;
if (menu_y - scroll_off >= MAX_SCREEN_Y - 3)
scroll_off++;
if (menu_y < scroll_off)
scroll_off--;
if (scroll_off < 0)
scroll_off = 0;
for (ssize_t i = scroll_off; i < file_list.size() && i < scroll_off + MAX_SCREEN_Y - 3;
i++) {
auto &entry = file_list[i];
auto &path = entry.path;
if (i == menu_y)
pspDebugScreenSetTextColor(0xFF2b7311);
else {
if (entry.is_dir)
pspDebugScreenSetTextColor(0xFF750e0b);
else
pspDebugScreenSetTextColor(0xFFFFFFFF);
}
if (entry.is_dir)
pspDebugScreenPrintf("%-68s", path.c_str());
else
pspDebugScreenPrintf("%-63s%5d", path.c_str(), entry.size);
}
pspDebugScreenSetXY(0, MAX_SCREEN_Y - 1);
pspDebugScreenSetTextColor(0xFF222222);
for (int i = 0; i < MAX_SCREEN_X; i++)
pspDebugScreenPrintf("=");
pspDebugScreenSetTextColor(0xFFFFFFFF);
pspDebugScreenPrintf(
"Select(X), Move(UP/DOWN), DecHold(LT), StepSkips(/\\), Delay([])");
} else {
pspDebugScreenPrintf("%sMessage: %s Map: %s SK: %d D: %dms File: %s",
g_chip8.halted ? "HALTED " : "", g_chip8.message.c_str(),
g_chip8.keypad.map_name().c_str(), g_step_skips, g_artificial_delay,
filename.c_str());
}
sceDisplayWaitVblank();
fbp0 = sceGuSwapBuffers();
}
sceGuTerm();
sceKernelExitGame();
}