Initial commit
Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
commit
ce9464bfd0
14
.clang-format
Normal file
14
.clang-format
Normal 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
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
build
|
||||
build-rel
|
||||
.cache
|
||||
|
48
CMakeLists.txt
Normal file
48
CMakeLists.txt
Normal 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
25
callback.cpp
Normal 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
3
callback.h
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
int setup_callbacks(void);
|
923
main.cpp
Normal file
923
main.cpp
Normal 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();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user