Refactor asset.cpp into file.cpp, add file classes

This commit is contained in:
spicyjpeg 2023-06-14 23:58:07 +02:00
parent 284280689f
commit 95546643b9
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
26 changed files with 783 additions and 494 deletions

View File

@ -20,10 +20,10 @@ find_program(
add_executable(
cart_tool
src/asset.cpp
src/cart.cpp
src/cartdata.cpp
src/cartio.cpp
src/file.cpp
src/gpu.cpp
src/ide.cpp
src/io.cpp

14
CMakePresets.json Normal file → Executable file
View File

@ -9,7 +9,7 @@
{
"name": "debug",
"displayName": "Debug build",
"description": "Build the project with -Og and assertions enabled.",
"description": "Build the project with no optimization and assertions enabled.",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
@ -19,12 +19,22 @@
{
"name": "release",
"displayName": "Release build",
"description": "Build the project with -O2 and assertions disabled.",
"description": "Build the project with performance optimization and assertions stripped.",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "min-size-release",
"displayName": "Minimum size release build",
"description": "Build the project with size optimization and assertions stripped.",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "MinSizeRel"
}
}
]
}

View File

@ -33,4 +33,7 @@ cmake --preset debug
cmake --build ./build
```
Replace `debug` with `release` to build in release mode.
Replace `debug` with `release` to build in release mode or `min-size-release` to
optimize the executable for size. If MAME's `chdman` tool is installed and
listed in the `PATH` environment variable, a CHD image will be generated in
addition to the raw CD-ROM image.

View File

@ -13,6 +13,9 @@
"qrCodeWorker": {
"compress": "Compressing cartridge dump...",
"generate": "Generating QR code..."
},
"rebootWorker": {
"reboot": "Resetting system..."
}
},
@ -36,6 +39,10 @@
"name": "Dump cartridge as QR code",
"prompt": "Generate a QR code encoding the contents of the cartridge's EEPROM in compressed format. The code can be scanned and decoded to a raw dump."
},
"hddDump": {
"name": "Dump cartridge to hard drive",
"prompt": "Save the contents of the cartridge's EEPROM to a file on the IDE hard drive."
},
"hexdump": {
"name": "View cartridge hexdump",
"prompt": "Display the raw contents of the cartridge's EEPROM."
@ -116,6 +123,11 @@
"prompt": "Scan this code and paste the resulting string into the decodeDump.py script provided alongside this tool to obtain a dump of the cartridge. Press {START_BUTTON} to go back."
},
"SystemInfoScreen": {
"title": "System information",
"prompt": "Press {START_BUTTON} to go back."
},
"UnlockConfirmScreen": {
"title": "Warning",
"no": "No, go back",

2
cmake/setup.cmake Normal file → Executable file
View File

@ -67,7 +67,7 @@ target_compile_options(
-mdivide-breaks
,
# These options will be added if CMAKE_BUILD_TYPE is not set to Debug.
-O2
#-O3
#-flto
>
)

View File

@ -13,14 +13,18 @@ public:
void (CartActionsScreen::*target)(ui::Context &ctx);
};
static constexpr int _NUM_IDENTIFIED_ACTIONS = 6;
static constexpr int _NUM_UNIDENTIFIED_ACTIONS = 4;
static constexpr int _NUM_IDENTIFIED_ACTIONS = 7;
static constexpr int _NUM_UNIDENTIFIED_ACTIONS = 5;
static const Action _ACTIONS[]{
{
.name = "CartActionsScreen.qrDump.name"_h,
.prompt = "CartActionsScreen.qrDump.prompt"_h,
.target = &CartActionsScreen::qrDump
}, {
.name = "CartActionsScreen.hddDump.name"_h,
.prompt = "CartActionsScreen.hddDump.prompt"_h,
.target = &CartActionsScreen::hddDump
}, {
.name = "CartActionsScreen.hexdump.name"_h,
.prompt = "CartActionsScreen.hexdump.prompt"_h,
@ -53,8 +57,10 @@ void CartActionsScreen::qrDump(ui::Context &ctx) {
ctx.show(APP->_workerStatusScreen, false, true);
}
void CartActionsScreen::hddDump(ui::Context &ctx) {
}
void CartActionsScreen::hexdump(ui::Context &ctx) {
//ctx.show(APP->_hexdumpScreen, false, true);
}
void CartActionsScreen::reflash(ui::Context &ctx) {

View File

@ -2,7 +2,7 @@
#pragma once
#include <stddef.h>
#include "asset.hpp"
#include "gpu.hpp"
#include "uibase.hpp"
#include "uicommon.hpp"
#include "util.hpp"
@ -15,6 +15,7 @@ protected:
public:
void qrDump(ui::Context &ctx);
void hddDump(ui::Context &ctx);
void hexdump(ui::Context &ctx);
void reflash(ui::Context &ctx);
void erase(ui::Context &ctx);
@ -28,10 +29,10 @@ public:
class QRCodeScreen : public ui::ImageScreen {
public:
inline bool generateCode(const char *textInput) {
return asset::generateQRCode(_image, 960, 128, textInput);
return gpu::generateQRCode(_image, 960, 128, textInput);
}
inline bool generateCode(const uint8_t *binaryInput, size_t length) {
return asset::generateQRCode(_image, 960, 128, binaryInput, length);
return gpu::generateQRCode(_image, 960, 128, binaryInput, length);
}
void show(ui::Context &ctx, bool goBack = false);

View File

@ -3,10 +3,10 @@
#include <stdint.h>
#include "app/app.hpp"
#include "ps1/system.h"
#include "asset.hpp"
#include "cart.hpp"
#include "cartdata.hpp"
#include "cartio.hpp"
#include "file.hpp"
#include "io.hpp"
#include "uibase.hpp"
#include "util.hpp"
@ -23,7 +23,7 @@ void App::_unloadCartData(void) {
_parser = nullptr;
}
_db.unload();
_db.destroy();
_identified = nullptr;
}
@ -65,7 +65,7 @@ void App::_cartDetectWorker(void) {
_unloadCartData();
#ifndef NDEBUG
if (_loader->loadStruct(_dump, "data/test.dump")) {
if (_provider->loadStruct(_dump, "data/test.dump")) {
LOG("using dummy cart driver");
_driver = new cart::DummyDriver(_dump);
} else {
@ -87,7 +87,7 @@ void App::_cartDetectWorker(void) {
LOG("cart parser @ 0x%08x", _parser);
_workerStatus.update(2, 4, WSTR("App.cartDetectWorker.identifyGame"));
if (!_loader->loadAsset(_db, _CARTDB_PATHS[_dump.chipType])) {
if (!_provider->loadData(_db, _CARTDB_PATHS[_dump.chipType])) {
LOG("%s not found", _CARTDB_PATHS[_dump.chipType]);
goto _cartInitDone;
}
@ -104,18 +104,16 @@ _cartInitDone:
_workerStatus.update(3, 4, WSTR("App.cartDetectWorker.readDigitalIO"));
if (io::isDigitalIOPresent()) {
asset::Asset file;
bool ready;
util::Data bitstream;
bool ready;
if (!_loader->loadAsset(file, "data/fpga.bit")) {
LOG("failed to load data/fpga.bit");
if (!_provider->loadData(bitstream, "data/fpga.bit")) {
LOG("failed to load bitstream");
goto _initDone;
}
ready = io::loadBitstream(
reinterpret_cast<const uint8_t *>(file.ptr), file.length
);
file.unload();
ready = io::loadBitstream(bitstream.as<uint8_t>(), bitstream.length);
bitstream.destroy();
if (!ready) {
LOG("bitstream upload failed");
@ -208,17 +206,23 @@ void App::_interruptHandler(void) {
}
void App::run(
ui::Context &ctx, asset::AssetLoader &loader, asset::StringTable &strings
ui::Context &ctx, file::Provider &provider, file::StringTable &strings
) {
LOG("starting app @ 0x%08x", this);
_ctx = &ctx;
_loader = &loader;
_strings = &strings;
_ctx = &ctx;
_provider = &provider;
_strings = &strings;
ctx.screenData = this;
#ifdef NDEBUG
ctx.show(_warningScreen);
ctx.sounds[ui::SOUND_STARTUP].play();
#else
// Skip the warning screen in debug builds.
ctx.show(_buttonMappingScreen);
#endif
_setupWorker(&App::_dummyWorker);
_setupInterrupts();

View File

@ -6,10 +6,10 @@
#include "app/misc.hpp"
#include "app/unlock.hpp"
#include "ps1/system.h"
#include "asset.hpp"
#include "cart.hpp"
#include "cartdata.hpp"
#include "cartio.hpp"
#include "file.hpp"
#include "uibase.hpp"
/* Worker status class */
@ -82,9 +82,9 @@ private:
CartActionsScreen _cartActionsScreen;
QRCodeScreen _qrCodeScreen;
ui::Context *_ctx;
asset::AssetLoader *_loader;
asset::StringTable *_strings;
ui::Context *_ctx;
file::Provider *_provider;
file::StringTable *_strings;
cart::Dump _dump;
cart::CartDB _db;
@ -123,8 +123,7 @@ public:
}
void run(
ui::Context &ctx, asset::AssetLoader &loader,
asset::StringTable &strings
ui::Context &ctx, file::Provider &provider, file::StringTable &strings
);
};

View File

@ -39,11 +39,7 @@ void WarningScreen::show(ui::Context &ctx, bool goBack) {
_locked = true;
_numButtons = 1;
#ifdef NDEBUG
_cooldownTimer = ctx.time + ctx.gpuCtx.refreshRate * WARNING_COOLDOWN;
#else
_cooldownTimer = 0;
#endif
MessageScreen::show(ctx, goBack);

View File

@ -1,299 +0,0 @@
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "ps1/gpucmd.h"
#include "ps1/pcdrv.h"
#include "vendor/ff.h"
#include "vendor/miniz.h"
#include "vendor/qrcodegen.h"
#include "asset.hpp"
namespace asset {
/* Asset loader */
static constexpr uint32_t _ZIP_FLAGS =
MZ_ZIP_FLAG_CASE_SENSITIVE | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY;
bool AssetLoader::openMemory(const void *zipData, size_t length) {
//close();
mz_zip_zero_struct(&_zip);
// Sorting the central directory in a zip with a small number of files is
// just a waste of time.
if (!mz_zip_reader_init_mem(&_zip, zipData, length, _ZIP_FLAGS)) {
LOG("zip init error, code=%d", mz_zip_get_last_error(&_zip));
return false;
}
LOG("ptr=0x%08x, length=0x%x", zipData, length);
ready = true;
return true;
}
bool AssetLoader::openFAT(const char *path) {
auto error = f_open(&_fatFile, path, FA_READ | FA_OPEN_EXISTING);
if (error) {
LOG("FAT zip open error, code=%d", error);
return false;
}
size_t fileSize = f_size(&_fatFile);
_zip.m_pIO_opaque = &_fatFile;
_zip.m_pNeeds_keepalive = nullptr;
_zip.m_pRead = [](
void *opaque, uint64_t offset, void *data, size_t length
) -> size_t {
auto fatFile = reinterpret_cast<FIL *>(opaque);
auto error = f_lseek(fatFile, offset);
if (error) {
LOG("FAT zip seek error, code=%d", error);
return 0;
}
size_t actualLength;
error = f_read(fatFile, data, length, &actualLength);
if (error) {
LOG("FAT zip read error, code=%d", error);
return 0;
}
return actualLength;
};
if (!mz_zip_reader_init(&_zip, fileSize, _ZIP_FLAGS)) {
LOG("FAT zip init error, code=%d", mz_zip_get_last_error(&_zip));
return false;
}
LOG("length=0x%x", fileSize);
ready = true;
return true;
}
bool AssetLoader::openHost(const char *path) {
if (pcdrvInit() < 0)
return false;
_hostFile = pcdrvOpen(path, PCDRV_MODE_READ);
if (_hostFile < 0) {
LOG("host zip open error, code=%d", _hostFile);
return false;
}
int fileSize = pcdrvSeek(_hostFile, 0, PCDRV_SEEK_END);
if (fileSize < 0)
return false;
_zip.m_pIO_opaque = reinterpret_cast<void *>(_hostFile);
_zip.m_pNeeds_keepalive = nullptr;
_zip.m_pRead = [](
void *opaque, uint64_t offset, void *data, size_t length
) -> size_t {
int hostFile = reinterpret_cast<int>(opaque);
if (
pcdrvSeek(hostFile, static_cast<uint32_t>(offset), PCDRV_SEEK_SET)
!= int(offset)
)
return 0;
int actualLength = pcdrvRead(hostFile, data, length);
if (actualLength < 0)
return 0;
return actualLength;
};
if (!mz_zip_reader_init(&_zip, fileSize, _ZIP_FLAGS)) {
LOG("host zip init error, code=%d", mz_zip_get_last_error(&_zip));
return false;
}
LOG("length=0x%x", fileSize);
ready = true;
return true;
}
void AssetLoader::close(void) {
if (!ready)
return;
if (_hostFile >= 0)
pcdrvClose(_hostFile);
else
f_close(&_fatFile);
mz_zip_reader_end(&_zip);
ready = false;
}
size_t AssetLoader::loadAsset(Asset &output, const char *path) {
output.ptr = mz_zip_reader_extract_file_to_heap(&_zip, path, &output.length, 0);
if (!output.ptr)
return 0;
return output.length;
}
size_t AssetLoader::loadTIM(gpu::Image &output, const char *path) {
size_t size;
void *data = mz_zip_reader_extract_file_to_heap(&_zip, path, &size, 0);
if (!data)
return 0;
auto header = reinterpret_cast<const gpu::TIMHeader *>(data);
auto ptr = reinterpret_cast<const uint8_t *>(&header[1]);
if (!output.initFromTIMHeader(header)) {
mz_free(data);
return 0;
}
if (header->flags & (1 << 3)) {
auto clut = reinterpret_cast<const gpu::TIMSectionHeader *>(ptr);
gpu::upload(clut->vram, &clut[1], true);
ptr += clut->length;
}
auto image = reinterpret_cast<const gpu::TIMSectionHeader *>(ptr);
gpu::upload(image->vram, &image[1], true);
mz_free(data);
return size;
}
size_t AssetLoader::loadVAG(spu::Sound &output, const char *path) {
// Sounds should be decompressed and uploaded to the SPU one chunk at a
// time, but whatever.
size_t size;
void *data = mz_zip_reader_extract_file_to_heap(&_zip, path, &size, 0);
if (!data)
return 0;
auto header = reinterpret_cast<const spu::VAGHeader *>(data);
if (!output.initFromVAGHeader(header, spuOffset)) {
mz_free(data);
return 0;
}
spuOffset += spu::upload(
spuOffset, reinterpret_cast<const uint32_t *>(&header[1]),
size - sizeof(spu::VAGHeader), true
);
mz_free(data);
return size;
}
/* String table manager */
const char *StringTable::get(util::Hash id) const {
if (!ptr)
return "missingno";
auto blob = reinterpret_cast<const char *>(ptr);
auto table = reinterpret_cast<const StringTableEntry *>(ptr);
auto entry = &table[id % TABLE_BUCKET_COUNT];
if (entry->hash == id)
return &blob[entry->offset];
while (entry->chained) {
entry = &table[entry->chained];
if (entry->hash == id)
return &blob[entry->offset];
}
return "missingno";
}
size_t StringTable::format(
char *buffer, size_t length, util::Hash id, ...
) const {
va_list ap;
va_start(ap, id);
size_t outLength = vsnprintf(buffer, length, get(id), ap);
va_end(ap);
return outLength;
}
/* QR code encoder */
static void _loadQRCode(
gpu::Image &output, int x, int y, const uint32_t *qrCode
) {
int size = qrcodegen_getSize(qrCode);
gpu::RectWH rect;
// Generate a 16-color (only 2 colors used) palette and place it below the
// QR code in VRAM.
const uint32_t palette[8]{ 0x8000ffff };
rect.x = x;
rect.y = y + size;
rect.w = 16;
rect.h = 1;
gpu::upload(rect, palette, true);
rect.y = y;
rect.w = qrcodegen_getStride(qrCode) * 2;
rect.h = size;
gpu::upload(rect, &qrCode[1], true);
output.initFromVRAMRect(rect, GP0_COLOR_4BPP);
output.width = size;
output.palette = gp0_clut(x / 16, y + size);
LOG("loaded at (%d,%d), size=%d", x, y, size);
}
bool generateQRCode(
gpu::Image &output, int x, int y, const char *str, qrcodegen_Ecc ecc
) {
uint32_t qrCode[qrcodegen_BUFFER_LEN_MAX];
uint32_t tempBuffer[qrcodegen_BUFFER_LEN_MAX];
auto segment = qrcodegen_makeAlphanumeric(
str, reinterpret_cast<uint8_t *>(tempBuffer)
);
if (!qrcodegen_encodeSegments(&segment, 1, ecc, tempBuffer, qrCode)) {
LOG("QR encoding failed");
return false;
}
_loadQRCode(output, x, y, qrCode);
return true;
}
bool generateQRCode(
gpu::Image &output, int x, int y, const uint8_t *data, size_t length,
qrcodegen_Ecc ecc
) {
uint32_t qrCode[qrcodegen_BUFFER_LEN_MAX];
uint32_t tempBuffer[qrcodegen_BUFFER_LEN_MAX];
auto segment = qrcodegen_makeBytes(
data, length, reinterpret_cast<uint8_t *>(tempBuffer)
);
if (!qrcodegen_encodeSegments(&segment, 1, ecc, tempBuffer, qrCode)) {
LOG("QR encoding failed");
return false;
}
_loadQRCode(output, x, y, qrCode);
return true;
}
}

View File

@ -1,101 +0,0 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "vendor/ff.h"
#include "vendor/miniz.h"
#include "vendor/qrcodegen.h"
#include "gpu.hpp"
#include "spu.hpp"
#include "util.hpp"
namespace asset {
/* Asset loader (wrapper around a zip file) */
class Asset {
public:
void *ptr;
size_t length;
inline Asset(void)
: ptr(nullptr), length(0) {}
inline ~Asset(void) {
unload();
}
inline void unload(void) {
if (ptr) {
mz_free(ptr);
ptr = nullptr;
}
}
};
class AssetLoader {
private:
mz_zip_archive _zip;
FIL _fatFile;
int _hostFile;
public:
bool ready;
uint32_t spuOffset;
inline AssetLoader(uint32_t spuOffset = 0x1000)
: _hostFile(-1), ready(false), spuOffset(spuOffset) {}
inline ~AssetLoader(void) {
close();
}
template<class T> inline size_t loadStruct(T &output, const char *path) {
if (!mz_zip_reader_extract_file_to_mem(
&_zip, path, &output, sizeof(T), 0
))
return 0;
return sizeof(T);
}
bool openMemory(const void *zipData, size_t length);
bool openFAT(const char *path);
bool openHost(const char *path);
void close(void);
size_t loadAsset(Asset &output, const char *path);
size_t loadTIM(gpu::Image &output, const char *path);
size_t loadVAG(spu::Sound &output, const char *path);
};
/* String table manager */
static constexpr int TABLE_BUCKET_COUNT = 256;
struct [[gnu::packed]] StringTableEntry {
public:
uint32_t hash;
uint16_t offset, chained;
};
class StringTable : public Asset {
public:
inline const char *operator[](util::Hash id) const {
return get(id);
}
const char *get(util::Hash id) const;
size_t format(char *buffer, size_t length, util::Hash id, ...) const;
};
/* QR code encoder */
bool generateQRCode(
gpu::Image &output, int x, int y, const char *str,
qrcodegen_Ecc ecc = qrcodegen_Ecc_MEDIUM
);
bool generateQRCode(
gpu::Image &output, int x, int y, const uint8_t *data, size_t length,
qrcodegen_Ecc ecc = qrcodegen_Ecc_MEDIUM
);
}

View File

@ -1,8 +1,6 @@
#include <stddef.h>
#include <stdint.h>
#include "asset.hpp"
#include "cart.hpp"
#include "cartdata.hpp"

View File

@ -4,8 +4,8 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "asset.hpp"
#include "cart.hpp"
#include "util.hpp"
namespace cart {
@ -118,7 +118,7 @@ public:
}
};
class CartDB : public asset::Asset {
class CartDB : public util::Data {
public:
inline const DBEntry *operator[](int index) const {
return get(index);

371
src/file.cpp Normal file
View File

@ -0,0 +1,371 @@
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include "ps1/pcdrv.h"
#include "vendor/ff.h"
#include "vendor/miniz.h"
#include "file.hpp"
namespace file {
/* File classes */
size_t HostFile::read(void *output, size_t length) {
int actualLength = pcdrvRead(_fd, output, length);
if (actualLength < 0) {
LOG("PCDRV error, code=%d", actualLength);
return 0;
}
return size_t(actualLength);
}
size_t HostFile::write(const void *input, size_t length) {
int actualLength = pcdrvWrite(_fd, input, length);
if (actualLength < 0) {
LOG("PCDRV error, code=%d", actualLength);
return 0;
}
return size_t(actualLength);
}
uint64_t HostFile::seek(uint64_t offset) {
int actualOffset = pcdrvSeek(_fd, int(offset), PCDRV_SEEK_SET);
if (actualOffset < 0) {
LOG("PCDRV error, code=%d", actualOffset);
return 0;
}
return uint64_t(actualOffset);
}
uint64_t HostFile::tell(void) const {
int actualOffset = pcdrvSeek(_fd, 0, PCDRV_SEEK_CUR);
if (actualOffset < 0) {
LOG("PCDRV error, code=%d", actualOffset);
return 0;
}
return uint64_t(actualOffset);
}
void HostFile::close(void) {
pcdrvClose(_fd);
}
size_t FATFile::read(void *output, size_t length) {
size_t actualLength;
auto error = f_read(&_fd, output, length, &actualLength);
if (error) {
LOG("FAT error, code=%d", error);
return 0;
}
return uint64_t(actualLength);
}
size_t FATFile::write(const void *input, size_t length) {
size_t actualLength;
auto error = f_write(&_fd, input, length, &actualLength);
if (error) {
LOG("FAT error, code=%d", error);
return 0;
}
return uint64_t(actualLength);
}
uint64_t FATFile::seek(uint64_t offset) {
auto error = f_lseek(&_fd, offset);
if (error) {
LOG("FAT error, code=%d", error);
return 0;
}
return f_tell(&_fd);
}
uint64_t FATFile::tell(void) const {
return f_tell(&_fd);
}
void FATFile::close(void) {
f_close(&_fd);
}
/* File and asset provider classes */
uint32_t currentSPUOffset = spu::DUMMY_BLOCK_END;
size_t Provider::loadData(util::Data &output, const char *path) {
auto file = openFile(path);
if (!file)
return 0;
//assert(file.length <= SIZE_MAX);
if (!output.allocate(size_t(file->length)))
return 0;
size_t actualLength = file->read(output.ptr, output.length);
file->close();
return actualLength;
}
size_t Provider::loadData(void *output, size_t length, const char *path) {
auto file = openFile(path);
if (!file)
return 0;
//assert(file.length >= length);
size_t actualLength = file->read(output, length);
file->close();
return actualLength;
}
size_t Provider::loadTIM(gpu::Image &output, const char *path) {
util::Data data;
if (!loadData(data, path))
return 0;
auto header = data.as<const gpu::TIMHeader>();
auto section = reinterpret_cast<const uint8_t *>(&header[1]);
if (!output.initFromTIMHeader(header)) {
data.destroy();
return 0;
}
if (header->flags & (1 << 3)) {
auto clut = reinterpret_cast<const gpu::TIMSectionHeader *>(section);
gpu::upload(clut->vram, &clut[1], true);
section += clut->length;
}
auto image = reinterpret_cast<const gpu::TIMSectionHeader *>(section);
gpu::upload(image->vram, &image[1], true);
data.destroy();
return data.length;
}
size_t Provider::loadVAG(spu::Sound &output, const char *path) {
// Sounds should be decompressed and uploaded to the SPU one chunk at a
// time, but whatever.
util::Data data;
if (!loadData(data, path))
return 0;
auto header = data.as<const spu::VAGHeader>();
auto body = reinterpret_cast<const uint32_t *>(&header[1]);
if (!output.initFromVAGHeader(header, currentSPUOffset)) {
data.destroy();
return 0;
}
currentSPUOffset += spu::upload(
currentSPUOffset, body, data.length - sizeof(spu::VAGHeader), true
);
data.destroy();
return data.length;
}
bool HostProvider::init(void) {
int error = pcdrvInit();
if (error < 0) {
LOG("PCDRV error, code=%d", error);
return false;
}
return true;
}
File *HostProvider::openFile(const char *path, uint32_t flags) {
PCDRVOpenMode mode = PCDRV_MODE_READ;
if ((flags & (READ | WRITE)) == (READ | WRITE))
mode = PCDRV_MODE_READ_WRITE;
else if (flags & WRITE)
mode = PCDRV_MODE_WRITE;
int fd = pcdrvOpen(path, mode);
if (fd < 0) {
LOG("PCDRV error, code=%d", fd);
return nullptr;
}
auto file = new HostFile();
file->_fd = fd;
file->length = pcdrvSeek(fd, 0, PCDRV_SEEK_END);
pcdrvSeek(fd, 0, PCDRV_SEEK_SET);
return file;
}
bool FATProvider::init(const char *drive) {
auto error = f_mount(&_fs, drive, 1);
if (error) {
LOG("mount error, drive=%s, code=%d", drive, error);
return false;
}
f_chdrive(drive);
__builtin_strncpy(_drive, drive, sizeof(_drive));
LOG("mount ok, drive=%s", drive);
return true;
}
void FATProvider::close(void) {
auto error = f_unmount(_drive);
if (error)
LOG("unmount error, drive=%s, code=%d", _drive, error);
else
LOG("unmount ok, drive=%s", _drive);
}
File *FATProvider::openFile(const char *path, uint32_t flags) {
auto file = new FATFile();
auto error = f_open(&(file->_fd), path, uint8_t(flags));
if (error) {
LOG("FAT error, code=%d", error);
return nullptr;
}
file->length = f_size(&(file->_fd));
return file;
}
static constexpr uint32_t _ZIP_FLAGS = 0
| MZ_ZIP_FLAG_CASE_SENSITIVE
| MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY;
bool ZIPProvider::init(File *file) {
mz_zip_zero_struct(&_zip);
_file = file;
_zip.m_pIO_opaque = reinterpret_cast<void *>(file);
_zip.m_pNeeds_keepalive = nullptr;
_zip.m_pRead = [](
void *opaque, uint64_t offset, void *output, size_t length
) -> size_t {
auto _file = reinterpret_cast<File *>(opaque);
if (_file->seek(offset) != offset)
return 0;
return _file->read(output, length);
};
if (!mz_zip_reader_init(&_zip, file->length, _ZIP_FLAGS)) {
LOG("host zip init error, code=%d", mz_zip_get_last_error(&_zip));
return false;
}
LOG("file=0x%08x, length=0x%x", file, file->length);
return true;
}
bool ZIPProvider::init(const void *zipData, size_t length) {
mz_zip_zero_struct(&_zip);
_file = nullptr;
if (!mz_zip_reader_init_mem(&_zip, zipData, length, _ZIP_FLAGS)) {
LOG("ZIP error, code=%d", mz_zip_get_last_error(&_zip));
return false;
}
LOG("ptr=0x%08x, length=0x%x", zipData, length);
return true;
}
void ZIPProvider::close(void) {
mz_zip_reader_end(&_zip);
if (_file)
_file->close();
}
size_t ZIPProvider::loadData(util::Data &output, const char *path) {
output.destroy();
output.ptr = mz_zip_reader_extract_file_to_heap(
&_zip, path, &(output.length), 0
);
if (!output.ptr)
return 0;
return output.length;
}
size_t ZIPProvider::loadData(void *output, size_t length, const char *path) {
if (!mz_zip_reader_extract_file_to_mem(&_zip, path, output, length, 0))
return 0;
// FIXME: this may not reflect the file's actual length
return length;
}
/* String table parser */
static const char _ERROR_STRING[] = "missingno";
const char *StringTable::get(util::Hash id) const {
if (!ptr)
return _ERROR_STRING;
auto blob = reinterpret_cast<const char *>(ptr);
auto table = reinterpret_cast<const StringTableEntry *>(ptr);
auto entry = &table[id % TABLE_BUCKET_COUNT];
if (entry->hash == id)
return &blob[entry->offset];
while (entry->chained) {
entry = &table[entry->chained];
if (entry->hash == id)
return &blob[entry->offset];
}
return _ERROR_STRING;
}
size_t StringTable::format(
char *buffer, size_t length, util::Hash id, ...
) const {
va_list ap;
va_start(ap, id);
size_t outLength = vsnprintf(buffer, length, get(id), ap);
va_end(ap);
return outLength;
}
}

144
src/file.hpp Normal file
View File

@ -0,0 +1,144 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "vendor/ff.h"
#include "vendor/miniz.h"
#include "gpu.hpp"
#include "spu.hpp"
#include "util.hpp"
namespace file {
/* File classes */
// These are functionally equivalent to the FA_* flags used by FatFs.
enum FileModeFlag {
READ = 1 << 0,
WRITE = 1 << 1,
FORCE_CREATE = 1 << 3, // Create file if missing, truncate if it exists
ALLOW_CREATE = 1 << 4 // Create file if missing
};
class File {
public:
uint64_t length;
inline ~File(void) {
close();
}
virtual size_t read(void *output, size_t length) { return 0; }
virtual size_t write(const void *input, size_t length) { return 0; }
virtual uint64_t seek(uint64_t offset) { return 0; }
virtual uint64_t tell(void) const { return 0; }
virtual void close(void) {}
};
class HostFile : public File {
friend class HostProvider;
private:
int _fd;
public:
size_t read(void *output, size_t length);
size_t write(const void *input, size_t length);
uint64_t seek(uint64_t offset);
uint64_t tell(void) const;
void close(void);
};
class FATFile : public File {
friend class FATProvider;
private:
FIL _fd;
public:
size_t read(void *output, size_t length);
size_t write(const void *input, size_t length);
uint64_t seek(uint64_t offset);
uint64_t tell(void) const;
void close(void);
};
/* File and asset provider classes */
extern uint32_t currentSPUOffset;
class Provider {
public:
inline ~Provider(void) {
close();
}
template<class T> inline size_t loadStruct(T &output, const char *path) {
return loadData(&output, sizeof(output), path);
}
virtual void close(void) {}
virtual File *openFile(const char *path) { return nullptr; }
virtual size_t loadData(util::Data &output, const char *path);
virtual size_t loadData(void *output, size_t length, const char *path);
size_t loadTIM(gpu::Image &output, const char *path);
size_t loadVAG(spu::Sound &output, const char *path);
};
class HostProvider : public Provider {
public:
bool init(void);
File *openFile(const char *path, uint32_t flags);
};
class FATProvider : public Provider {
private:
FATFS _fs;
char _drive[16];
public:
bool init(const char *drive);
void close(void);
File *openFile(const char *path, uint32_t flags);
};
// This implementation only supports loading an entire file at once.
class ZIPProvider : public Provider {
private:
mz_zip_archive _zip;
File *_file;
public:
bool init(File *file);
bool init(const void *zipData, size_t length);
void close(void);
size_t loadData(util::Data &output, const char *path);
size_t loadData(void *output, size_t length, const char *path);
};
/* String table parser */
static constexpr int TABLE_BUCKET_COUNT = 256;
struct [[gnu::packed]] StringTableEntry {
public:
uint32_t hash;
uint16_t offset, chained;
};
class StringTable : public util::Data {
public:
inline const char *operator[](util::Hash id) const {
return get(id);
}
const char *get(util::Hash id) const;
size_t format(char *buffer, size_t length, util::Hash id, ...) const;
};
}

View File

@ -5,6 +5,7 @@
#include "ps1/gpucmd.h"
#include "ps1/registers.h"
#include "ps1/system.h"
#include "vendor/qrcodegen.h"
#include "gpu.hpp"
#include "util.hpp"
@ -430,4 +431,69 @@ _break:
return (width > maxWidth) ? width : maxWidth;
}
/* QR code encoder */
static void _loadQRCode(Image &output, int x, int y, const uint32_t *qrCode) {
int size = qrcodegen_getSize(qrCode);
RectWH rect;
// Generate a 16-color (only 2 colors used) palette and place it below the
// QR code in VRAM.
const uint32_t palette[8]{ 0x8000ffff };
rect.x = x;
rect.y = y + size;
rect.w = 16;
rect.h = 1;
upload(rect, palette, true);
rect.y = y;
rect.w = qrcodegen_getStride(qrCode) * 2;
rect.h = size;
upload(rect, &qrCode[1], true);
output.initFromVRAMRect(rect, GP0_COLOR_4BPP);
output.width = size;
output.palette = gp0_clut(x / 16, y + size);
LOG("loaded at (%d,%d), size=%d", x, y, size);
}
bool generateQRCode(
Image &output, int x, int y, const char *str, qrcodegen_Ecc ecc
) {
uint32_t qrCode[qrcodegen_BUFFER_LEN_MAX];
uint32_t tempBuffer[qrcodegen_BUFFER_LEN_MAX];
auto segment = qrcodegen_makeAlphanumeric(
str, reinterpret_cast<uint8_t *>(tempBuffer)
);
if (!qrcodegen_encodeSegments(&segment, 1, ecc, tempBuffer, qrCode)) {
LOG("QR encoding failed");
return false;
}
_loadQRCode(output, x, y, qrCode);
return true;
}
bool generateQRCode(
Image &output, int x, int y, const uint8_t *data, size_t length,
qrcodegen_Ecc ecc
) {
uint32_t qrCode[qrcodegen_BUFFER_LEN_MAX];
uint32_t tempBuffer[qrcodegen_BUFFER_LEN_MAX];
auto segment = qrcodegen_makeBytes(
data, length, reinterpret_cast<uint8_t *>(tempBuffer)
);
if (!qrcodegen_encodeSegments(&segment, 1, ecc, tempBuffer, qrCode)) {
LOG("QR encoding failed");
return false;
}
_loadQRCode(output, x, y, qrCode);
return true;
}
}

View File

@ -5,6 +5,7 @@
#include <stdint.h>
#include "ps1/gpucmd.h"
#include "ps1/registers.h"
#include "vendor/qrcodegen.h"
namespace gpu {
@ -197,4 +198,15 @@ public:
int getStringWidth(const char *str, bool breakOnSpace = false) const;
};
/* QR code encoder */
bool generateQRCode(
Image &output, int x, int y, const char *str,
qrcodegen_Ecc ecc = qrcodegen_Ecc_MEDIUM
);
bool generateQRCode(
Image &output, int x, int y, const uint8_t *data, size_t length,
qrcodegen_Ecc ecc = qrcodegen_Ecc_MEDIUM
);
}

View File

@ -1,12 +1,12 @@
#include <stddef.h>
#include <stdint.h>
#include "ide.hpp"
#include "io.hpp"
#include "util.hpp"
#include "ps1/registers.h"
#include "ps1/system.h"
#include "vendor/diskio.h"
#include "ide.hpp"
#include "io.hpp"
#include "util.hpp"
namespace ide {

View File

@ -4,9 +4,8 @@
#include "app/app.hpp"
#include "ps1/gpucmd.h"
#include "ps1/system.h"
#include "vendor/ff.h"
#include "asset.hpp"
#include "defs.hpp"
#include "file.hpp"
#include "gpu.hpp"
#include "io.hpp"
#include "spu.hpp"
@ -16,6 +15,16 @@
extern "C" const uint8_t _resources[];
extern "C" const size_t _resourcesSize;
static const char _DEFAULT_RESOURCE_ZIP_PATH[] = "/cartToolResources.zip";
static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{
"assets/sound/startup.vag", // ui::SOUND_STARTUP
"assets/sounds/move.vag", // ui::SOUND_MOVE
"assets/sounds/enter.vag", // ui::SOUND_ENTER
"assets/sounds/exit.vag", // ui::SOUND_EXIT
"assets/sounds/click.vag" // ui::SOUND_CLICK
};
int main(int argc, const char **argv) {
installExceptionHandler();
gpu::init();
@ -24,12 +33,10 @@ int main(int argc, const char **argv) {
int width = 320, height = 240;
const char *resPath = _DEFAULT_RESOURCE_ZIP_PATH;
const void *resPtr = nullptr;
size_t resLength = 0;
char mountPath[16];
strcpy(mountPath, "1:");
#ifdef ENABLE_ARGV
for (; argc > 0; argc--) {
auto arg = *(argv++);
@ -51,10 +58,6 @@ int main(int argc, const char **argv) {
util::logger.enableSyslog = true;
break;
case "mount"_h:
__builtin_strncpy(mountPath, &arg[6], sizeof(mountPath));
break;
case "screen.width"_h:
width = int(strtol(&arg[13], nullptr, 0));
break;
@ -63,8 +66,12 @@ int main(int argc, const char **argv) {
height = int(strtol(&arg[14], nullptr, 0));
break;
// Allow the default assets to be overridden by passing a pointer to
// an in-memory ZIP file as a command-line argument.
// Allow the default assets to be overridden by passing a path or a
// pointer to an in-memory ZIP file as a command-line argument.
case "resources.path"_h:
resPath = &arg[15];
break;
case "resources.ptr"_h:
resPtr = reinterpret_cast<const void *>(
strtol(&arg[14], nullptr, 16)
@ -89,25 +96,39 @@ int main(int argc, const char **argv) {
io::clearWatchdog();
FATFS fat;
file::FATProvider fileProvider;
auto error = f_mount(&fat, mountPath, true);
if (error)
LOG("FAT init error, code=%d", error);
// Attempt to initialize the secondary drive first, then in case of failure
// try to initialize the primary drive instead.
if (fileProvider.init("1:"))
goto _fileInitDone;
if (fileProvider.init("0:"))
goto _fileInitDone;
_fileInitDone:
io::clearWatchdog();
asset::AssetLoader loader;
// Load the resource archive, first from memory if a pointer was given and
// then from the HDD. If both attempts fail, fall back to the archive
// embedded into the executable.
file::ZIPProvider resourceProvider;
file::File *zipFile;
if (resPtr && resLength)
loader.openMemory(resPtr, resLength);
if (!loader.ready)
loader.openFAT("1:/cart_tool/resources.zip");
//if (!loader.ready)
//loader.openHost("resources.zip");
if (!loader.ready)
loader.openMemory(_resources, _resourcesSize);
if (resPtr && resLength) {
if (resourceProvider.init(resPtr, resLength))
goto _resourceInitDone;
}
zipFile = fileProvider.openFile(resPath, file::READ);
if (zipFile) {
if (resourceProvider.init(zipFile))
goto _resourceInitDone;
}
resourceProvider.init(_resources, _resourcesSize);
_resourceInitDone:
io::clearWatchdog();
gpu::Context gpuCtx(GP1_MODE_NTSC, width, height, height > 256);
@ -116,13 +137,21 @@ int main(int argc, const char **argv) {
ui::TiledBackground background;
ui::LogOverlay overlay(util::logger);
asset::StringTable strings;
file::StringTable strings;
if (
!loader.loadTIM(background.tile, "assets/textures/background.tim") ||
!loader.loadTIM(uiCtx.font.image, "assets/textures/font.tim") ||
!loader.loadStruct(uiCtx.font.metrics, "assets/textures/font.metrics") ||
!loader.loadAsset(strings, "assets/app.strings")
!resourceProvider.loadTIM(
background.tile, "assets/textures/background.tim"
) ||
!resourceProvider.loadTIM(
uiCtx.font.image, "assets/textures/font.tim"
) ||
!resourceProvider.loadStruct(
uiCtx.font.metrics, "assets/textures/font.metrics"
) ||
!resourceProvider.loadData(
strings, "assets/app.strings"
)
) {
LOG("required assets not found, exiting");
return 1;
@ -130,11 +159,10 @@ int main(int argc, const char **argv) {
io::clearWatchdog();
loader.loadVAG(uiCtx.sounds[ui::SOUND_STARTUP], "assets/sounds/startup.vag");
loader.loadVAG(uiCtx.sounds[ui::SOUND_MOVE], "assets/sounds/move.vag");
loader.loadVAG(uiCtx.sounds[ui::SOUND_ENTER], "assets/sounds/enter.vag");
loader.loadVAG(uiCtx.sounds[ui::SOUND_EXIT], "assets/sounds/exit.vag");
loader.loadVAG(uiCtx.sounds[ui::SOUND_CLICK], "assets/sounds/click.vag");
for (int i = 0; i < ui::NUM_UI_SOUNDS; i++)
resourceProvider.loadVAG(uiCtx.sounds[i], _UI_SOUND_PATHS[i]);
io::clearWatchdog();
background.text = "v" VERSION_STRING;
uiCtx.setBackgroundLayer(background);
@ -147,6 +175,6 @@ int main(int argc, const char **argv) {
io::setMiscOutput(io::MISC_SPU_ENABLE, true);
io::clearWatchdog();
app.run(uiCtx, loader, strings);
app.run(uiCtx, resourceProvider, strings);
return 0;
}

View File

@ -300,14 +300,16 @@ typedef enum {
/* GPU */
typedef enum {
GP1_STAT_MODE_PAL = 1 << 20,
GP1_STAT_DISP_BLANK = 1 << 23,
GP1_STAT_IRQ = 1 << 24,
GP1_STAT_DREQ = 1 << 25,
GP1_STAT_CMD_READY = 1 << 26,
GP1_STAT_READ_READY = 1 << 27,
GP1_STAT_WRITE_READY = 1 << 28,
GP1_STAT_FIELD_ODD = 1 << 31
GP1_STAT_MODE_BITMASK = 1 << 20,
GP1_STAT_MODE_NTSC = 0 << 20,
GP1_STAT_MODE_PAL = 1 << 20,
GP1_STAT_DISP_BLANK = 1 << 23,
GP1_STAT_IRQ = 1 << 24,
GP1_STAT_DREQ = 1 << 25,
GP1_STAT_CMD_READY = 1 << 26,
GP1_STAT_READ_READY = 1 << 27,
GP1_STAT_WRITE_READY = 1 << 28,
GP1_STAT_FIELD_ODD = 1 << 31
} GP1StatusFlag;
#define GPU_GP0 _MMIO32(IO_BASE | 0x1810)

View File

@ -94,8 +94,8 @@ static inline uint32_t setInterruptMask(uint32_t mask) {
*
* @param thread
* @param func
* @param stack Pointer to last 8 bytes in the stack
* @param arg Optional argument to entry point
* @param stack Pointer to last 8 bytes in the stack
*/
static inline void initThread(
Thread *thread, ArgFunction func, void *arg, void *stack
@ -130,7 +130,8 @@ void installExceptionHandler(void);
* - it must return quickly, as IRQs fired while the exception handler is
* running may otherwise be missed.
*
* Interrupts must be re-enabled manually after setting a new handler.
* Interrupts must be re-enabled manually using setInterruptMask() after setting
* a new handler.
*
* @param func
* @param arg Optional argument to be passed to handler

View File

@ -13,8 +13,6 @@ static constexpr int _DMA_CHUNK_SIZE = 8;
static constexpr int _DMA_TIMEOUT = 10000;
static constexpr int _STATUS_TIMEOUT = 1000;
static constexpr uint32_t _DUMMY_BLOCK_ADDR = 0x1000;
static bool _waitForStatus(uint16_t mask, uint16_t value) {
for (int timeout = _STATUS_TIMEOUT; timeout > 0; timeout--) {
if ((SPU_STAT & mask) == value)
@ -51,7 +49,7 @@ void init(void) {
// Place a dummy (silent) looping block at the beginning of SPU RAM.
SPU_DMA_CTRL = 4;
SPU_ADDR = _DUMMY_BLOCK_ADDR / 8;
SPU_ADDR = DUMMY_BLOCK_OFFSET / 8;
SPU_DATA = 0x0500;
for (int i = 7; i > 0; i--)
@ -96,7 +94,7 @@ void resetAllChannels(void) {
SPU_CH_VOL_L(ch) = 0;
SPU_CH_VOL_R(ch) = 0;
SPU_CH_FREQ(ch) = 0x1000;
SPU_CH_ADDR(ch) = _DUMMY_BLOCK_ADDR / 8;
SPU_CH_ADDR(ch) = DUMMY_BLOCK_OFFSET / 8;
}
SPU_FLAG_FM1 = 0;

View File

@ -7,6 +7,9 @@
namespace spu {
static constexpr uint32_t DUMMY_BLOCK_OFFSET = 0x1000;
static constexpr uint32_t DUMMY_BLOCK_END = 0x1010;
/* Basic API */
static inline void setVolume(int16_t master, int16_t reverb = 0) {

View File

@ -4,6 +4,7 @@
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include "ps1/system.h"
namespace util {
@ -82,6 +83,40 @@ template<typename T> static constexpr inline Hash hash(
Hash hash(const char *str, char terminator = 0);
Hash hash(const uint8_t *data, size_t length);
/* Simple "smart" pointer */
class Data {
public:
void *ptr;
size_t length;
inline Data(void)
: ptr(nullptr), length(0) {}
inline ~Data(void) {
destroy();
}
template<typename T> inline T *as(void) {
return reinterpret_cast<T *>(ptr);
}
inline void *allocate(size_t _length) {
if (ptr)
free(ptr);
ptr = malloc(_length);
length = _length;
return ptr;
}
inline void destroy(void) {
if (ptr) {
free(ptr);
ptr = nullptr;
}
}
};
/* Simple ring buffer */
template<typename T, size_t N> class RingBuffer {

View File

@ -20,7 +20,7 @@
#define FF_LFN_UNICODE 0
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
#define FF_FS_RPATH 0
#define FF_FS_RPATH 1
#define FF_VOLUMES 2
#define FF_STR_VOLUME_ID 0