Initial commit

Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
Slendi 2024-06-19 22:26:46 +03:00
commit ce9464bfd0
7 changed files with 1032 additions and 0 deletions

14
.clang-format Normal file
View File

@ -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'
...

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
build
build-rel
.cache

48
CMakeLists.txt Normal file
View File

@ -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
)

25
callback.cpp Normal file
View File

@ -0,0 +1,25 @@
#include <pspkernel.h>
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;
}

3
callback.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
int setup_callbacks(void);

923
main.cpp Normal file
View File

@ -0,0 +1,923 @@
#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 <vector>
#include <unistd.h>
#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<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];
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<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,
};
void get_files(fs::path path, std::vector<fs::path> &list, std::vector<int> &sizes,
std::vector<bool> &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<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();
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<fs::path> file_list;
std::vector<int> file_sizes;
std::vector<bool> 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<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];
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();
}

15
util.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
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;