mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-01-22 19:52:05 +01:00
Refactor asset.cpp into file.cpp, add file classes
This commit is contained in:
parent
284280689f
commit
95546643b9
@ -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
14
CMakePresets.json
Normal file → Executable 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
2
cmake/setup.cmake
Normal file → Executable 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
|
||||
>
|
||||
)
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
299
src/asset.cpp
299
src/asset.cpp
@ -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;
|
||||
}
|
||||
|
||||
}
|
101
src/asset.hpp
101
src/asset.hpp
@ -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
|
||||
);
|
||||
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "asset.hpp"
|
||||
#include "cart.hpp"
|
||||
#include "cartdata.hpp"
|
||||
|
||||
|
@ -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
371
src/file.cpp
Normal 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
144
src/file.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
66
src/gpu.cpp
66
src/gpu.cpp
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
12
src/gpu.hpp
12
src/gpu.hpp
@ -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
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
98
src/main.cpp
98
src/main.cpp
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
35
src/util.hpp
35
src/util.hpp
@ -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 {
|
||||
|
2
src/vendor/vendorconfig.h
vendored
2
src/vendor/vendorconfig.h
vendored
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user