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