997 lines
24 KiB
C++
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();
|
|
}
|