mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-03-01 07:20:42 +01:00
Add BS loader, audio stream thread, refactor workers
This commit is contained in:
parent
a747dabd76
commit
fc95fb5dfc
@ -101,18 +101,19 @@ set(
|
||||
src/main/app/app.cpp
|
||||
src/main/app/cartactions.cpp
|
||||
src/main/app/cartunlock.cpp
|
||||
src/main/app/cartworkers.cpp
|
||||
src/main/app/main.cpp
|
||||
src/main/app/misc.cpp
|
||||
src/main/app/miscworkers.cpp
|
||||
src/main/app/modals.cpp
|
||||
src/main/app/romactions.cpp
|
||||
src/main/app/romworkers.cpp
|
||||
src/main/app/tests.cpp
|
||||
src/main/app/threads.cpp
|
||||
src/main/cart/cart.cpp
|
||||
src/main/cart/cartdata.cpp
|
||||
src/main/cart/cartio.cpp
|
||||
src/main/cart/zs01.cpp
|
||||
src/main/workers/cartworkers.cpp
|
||||
src/main/workers/miscworkers.cpp
|
||||
src/main/workers/romworkers.cpp
|
||||
src/main/main.cpp
|
||||
src/main/uibase.cpp
|
||||
src/main/uicommon.cpp
|
||||
|
@ -28,7 +28,7 @@ extern "C" const size_t _resourceArchiveLength;
|
||||
static char _ptrArg[]{ "resource.ptr=xxxxxxxx\0" };
|
||||
static char _lengthArg[]{ "resource.length=xxxxxxxx\0" };
|
||||
|
||||
struct [[gnu::packed]] ZIPFileHeader {
|
||||
class [[gnu::packed]] ZIPFileHeader {
|
||||
public:
|
||||
uint32_t magic;
|
||||
uint16_t version, flags, compType;
|
||||
|
@ -14,6 +14,7 @@
|
||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@ -22,6 +23,9 @@
|
||||
#include "common/util/hash.hpp"
|
||||
#include "common/util/templates.hpp"
|
||||
#include "common/gpu.hpp"
|
||||
#include "common/mdec.hpp"
|
||||
#include "common/spu.hpp"
|
||||
#include "ps1/registers.h"
|
||||
|
||||
namespace fs {
|
||||
|
||||
@ -63,6 +67,7 @@ size_t Provider::loadData(util::Data &output, const char *path) {
|
||||
return 0;
|
||||
|
||||
assert(file->size <= SIZE_MAX);
|
||||
|
||||
if (!output.allocate(size_t(file->size))) {
|
||||
file->close();
|
||||
delete file;
|
||||
@ -83,6 +88,7 @@ size_t Provider::loadData(void *output, size_t length, const char *path) {
|
||||
return 0;
|
||||
|
||||
assert(file->size >= length);
|
||||
|
||||
size_t actualLength = file->read(output, length);
|
||||
|
||||
file->close();
|
||||
@ -105,32 +111,71 @@ size_t Provider::saveData(const void *input, size_t length, const char *path) {
|
||||
|
||||
size_t Provider::loadTIM(gpu::Image &output, const char *path) {
|
||||
util::Data data;
|
||||
size_t loadLength = 0;
|
||||
|
||||
if (!loadData(data, path))
|
||||
return 0;
|
||||
|
||||
auto header = data.as<const gpu::TIMHeader>();
|
||||
auto section = reinterpret_cast<const uint8_t *>(&header[1]);
|
||||
auto header = data.as<const gpu::TIMHeader>();
|
||||
|
||||
if (!output.initFromTIMHeader(header)) {
|
||||
data.destroy();
|
||||
return 0;
|
||||
if (output.initFromTIMHeader(*header)) {
|
||||
auto image = header->getImage();
|
||||
auto clut = header->getCLUT();
|
||||
|
||||
if (clut)
|
||||
loadLength += gpu::upload(clut->vram, clut->getData(), true);
|
||||
|
||||
loadLength += gpu::upload(image->vram, image->getData(), true);
|
||||
}
|
||||
|
||||
size_t uploadLength = 0;
|
||||
|
||||
if (header->flags & (1 << 3)) {
|
||||
auto clut = reinterpret_cast<const gpu::TIMSectionHeader *>(section);
|
||||
uploadLength += gpu::upload(clut->vram, &clut[1], true);
|
||||
|
||||
section += clut->length;
|
||||
}
|
||||
|
||||
auto image = reinterpret_cast<const gpu::TIMSectionHeader *>(section);
|
||||
uploadLength += gpu::upload(image->vram, &image[1], true);
|
||||
|
||||
data.destroy();
|
||||
return uploadLength;
|
||||
return loadLength;
|
||||
}
|
||||
|
||||
size_t Provider::loadBS(
|
||||
gpu::Image &output, const gpu::RectWH &rect, const char *path
|
||||
) {
|
||||
util::Data data;
|
||||
size_t loadLength = 0;
|
||||
|
||||
if (!loadData(data, path))
|
||||
return 0;
|
||||
|
||||
size_t bsLength = data.as<const mdec::BSHeader>()->getUncompLength();
|
||||
|
||||
mdec::BSDecompressor decompressor;
|
||||
util::Data buffer;
|
||||
|
||||
if (buffer.allocate(bsLength)) {
|
||||
auto bsPtr = buffer.as<uint32_t>();
|
||||
size_t sliceLength = (16 * rect.h) * 2;
|
||||
|
||||
if (!decompressor.decompress(bsPtr, data.ptr, bsLength)) {
|
||||
// Reuse the file's buffer to store vertical slices received from
|
||||
// the MDEC as they are uploaded to VRAM.
|
||||
data.allocate(sliceLength);
|
||||
mdec::feedBS(bsPtr, MDEC_CMD_FLAG_FORMAT_16BPP, false);
|
||||
|
||||
gpu::RectWH slice;
|
||||
|
||||
slice.x = rect.x;
|
||||
slice.y = rect.y;
|
||||
slice.w = 16;
|
||||
slice.h = rect.h;
|
||||
|
||||
for (int i = rect.w; i > 0; i -= 16) {
|
||||
mdec::receive(data.ptr, sliceLength, true);
|
||||
|
||||
loadLength += gpu::upload(slice, data.ptr, true);
|
||||
slice.x += 16;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.destroy();
|
||||
}
|
||||
|
||||
data.destroy();
|
||||
return loadLength;
|
||||
}
|
||||
|
||||
size_t Provider::loadVAG(
|
||||
@ -139,26 +184,24 @@ size_t Provider::loadVAG(
|
||||
// Sounds should be decompressed and uploaded to the SPU one chunk at a
|
||||
// time, but whatever.
|
||||
util::Data data;
|
||||
size_t loadLength = 0;
|
||||
|
||||
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, offset)) {
|
||||
data.destroy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto uploadLength =
|
||||
spu::upload(offset, body, data.length - sizeof(spu::VAGHeader), true);
|
||||
if (output.initFromVAGHeader(*header, offset))
|
||||
loadLength = spu::upload(
|
||||
offset, header->getData(), data.length - sizeof(spu::VAGHeader),
|
||||
true
|
||||
);
|
||||
|
||||
data.destroy();
|
||||
return uploadLength;
|
||||
return loadLength;
|
||||
}
|
||||
|
||||
struct [[gnu::packed]] BMPHeader {
|
||||
class [[gnu::packed]] BMPHeader {
|
||||
public:
|
||||
uint16_t magic;
|
||||
uint32_t fileLength;
|
||||
@ -186,7 +229,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
size_t Provider::saveVRAMBMP(gpu::RectWH &rect, const char *path) {
|
||||
size_t Provider::saveVRAMBMP(const gpu::RectWH &rect, const char *path) {
|
||||
auto file = openFile(path, WRITE | ALLOW_CREATE);
|
||||
|
||||
if (!file)
|
||||
@ -205,11 +248,11 @@ size_t Provider::saveVRAMBMP(gpu::RectWH &rect, const char *path) {
|
||||
gpu::RectWH slice;
|
||||
|
||||
slice.x = rect.x;
|
||||
slice.y = rect.y + rect.h - 1;
|
||||
slice.w = rect.w;
|
||||
slice.h = 1;
|
||||
|
||||
for (int y = rect.y + rect.h - 1; y >= rect.y; y--) {
|
||||
slice.y = y;
|
||||
for (; slice.y >= rect.y; slice.y--) {
|
||||
auto lineLength = gpu::download(slice, buffer.ptr, true);
|
||||
|
||||
// BMP stores channels in BGR order as opposed to RGB, so the red
|
||||
|
@ -151,8 +151,9 @@ public:
|
||||
virtual size_t saveData(const void *input, size_t length, const char *path);
|
||||
|
||||
size_t loadTIM(gpu::Image &output, const char *path);
|
||||
size_t loadBS(gpu::Image &output, const gpu::RectWH &rect, const char *path);
|
||||
size_t loadVAG(spu::Sound &output, uint32_t offset, const char *path);
|
||||
size_t saveVRAMBMP(gpu::RectWH &rect, const char *path);
|
||||
size_t saveVRAMBMP(const gpu::RectWH &rect, const char *path);
|
||||
};
|
||||
|
||||
/* String table parser */
|
||||
|
@ -355,22 +355,17 @@ void Image::initFromVRAMRect(
|
||||
texpage = gp0_page(rect.x / 64, rect.y / 256, blendMode, colorDepth);
|
||||
}
|
||||
|
||||
bool Image::initFromTIMHeader(const TIMHeader *header, BlendMode blendMode) {
|
||||
if (header->magic != 0x10)
|
||||
bool Image::initFromTIMHeader(const TIMHeader &header, BlendMode blendMode) {
|
||||
if (!header.validateMagic())
|
||||
return false;
|
||||
|
||||
auto ptr = reinterpret_cast<const uint8_t *>(&header[1]);
|
||||
|
||||
if (header->flags & (1 << 3)) {
|
||||
auto clut = reinterpret_cast<const TIMSectionHeader *>(ptr);
|
||||
auto image = header.getImage();
|
||||
auto clut = header.getCLUT();
|
||||
|
||||
if (clut)
|
||||
palette = gp0_clut(clut->vram.x / 16, clut->vram.y);
|
||||
ptr += clut->length;
|
||||
}
|
||||
|
||||
auto image = reinterpret_cast<const TIMSectionHeader *>(ptr);
|
||||
|
||||
initFromVRAMRect(image->vram, ColorDepth(header->flags & 3), blendMode);
|
||||
initFromVRAMRect(image->vram, header.getColorDepth(), blendMode);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -66,8 +66,8 @@ static inline void enableDisplay(bool enable) {
|
||||
GPU_GP1 = gp1_dispBlank(!enable);
|
||||
}
|
||||
|
||||
size_t upload(const RectWH &rect, const void *data, bool wait);
|
||||
size_t download(const RectWH &rect, void *data, bool wait);
|
||||
size_t upload(const RectWH &rect, const void *data, bool wait = false);
|
||||
size_t download(const RectWH &rect, void *data, bool wait = false);
|
||||
|
||||
/* Rendering context */
|
||||
|
||||
@ -181,15 +181,46 @@ public:
|
||||
|
||||
/* Image class */
|
||||
|
||||
struct TIMHeader {
|
||||
public:
|
||||
uint32_t magic, flags;
|
||||
};
|
||||
|
||||
struct TIMSectionHeader {
|
||||
class TIMSectionHeader {
|
||||
public:
|
||||
uint32_t length;
|
||||
RectWH vram;
|
||||
|
||||
inline const void *getData(void) const {
|
||||
return this + 1;
|
||||
}
|
||||
inline const TIMSectionHeader *getNextSection(void) const {
|
||||
return reinterpret_cast<const TIMSectionHeader *>(
|
||||
reinterpret_cast<uintptr_t>(this) + length
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
class TIMHeader {
|
||||
public:
|
||||
uint32_t magic, flags;
|
||||
|
||||
inline bool validateMagic(void) const {
|
||||
return (magic == 0x10) && (getColorDepth() <= GP0_COLOR_16BPP);
|
||||
}
|
||||
inline ColorDepth getColorDepth(void) const {
|
||||
return ColorDepth(flags & 7);
|
||||
}
|
||||
|
||||
inline const TIMSectionHeader *getImage(void) const {
|
||||
auto image = reinterpret_cast<const TIMSectionHeader *>(this + 1);
|
||||
|
||||
if (flags & (1 << 3))
|
||||
image = image->getNextSection();
|
||||
|
||||
return image;
|
||||
}
|
||||
inline const TIMSectionHeader *getCLUT(void) const {
|
||||
if (flags & (1 << 3))
|
||||
return reinterpret_cast<const TIMSectionHeader *>(this + 1);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class Image {
|
||||
@ -205,7 +236,7 @@ public:
|
||||
BlendMode blendMode = GP0_BLEND_SEMITRANS
|
||||
);
|
||||
bool initFromTIMHeader(
|
||||
const TIMHeader *header, BlendMode blendMode = GP0_BLEND_SEMITRANS
|
||||
const TIMHeader &header, BlendMode blendMode = GP0_BLEND_SEMITRANS
|
||||
);
|
||||
void drawScaled(
|
||||
Context &ctx, int x, int y, int w, int h, bool blend = false
|
||||
|
@ -39,7 +39,7 @@ public:
|
||||
int8_t baselineOffset;
|
||||
};
|
||||
|
||||
struct FontMetricsEntry {
|
||||
class FontMetricsEntry {
|
||||
public:
|
||||
uint32_t codePoint;
|
||||
CharacterSize size;
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <stdio.h>
|
||||
#include "common/util/misc.hpp"
|
||||
#include "common/util/string.hpp"
|
||||
#include "common/util/templates.hpp"
|
||||
#include "common/io.hpp"
|
||||
#include "ps1/registers.h"
|
||||
#include "ps1/registers573.h"
|
||||
@ -35,12 +36,6 @@ static constexpr int _IDE_RESET_ASSERT_DELAY = 5000;
|
||||
static constexpr int _IDE_RESET_CLEAR_DELAY = 50000;
|
||||
|
||||
void init(void) {
|
||||
// Remapping the base address is required in order for IDE DMA to work
|
||||
// properly, as the BIU will output it over the address lines during a DMA
|
||||
// transfer. It does not affect non-DMA access since the BIU will replace
|
||||
// the bottommost N bits, where N is the number of address lines used, with
|
||||
// the respective CPU address bits.
|
||||
BIU_DEV0_ADDR = reinterpret_cast<uint32_t>(SYS573_IDE_CS0_BASE) & 0x1fffffff;
|
||||
BIU_DEV0_CTRL = 0
|
||||
| (7 << 0) // Write delay
|
||||
| (4 << 4) // Read delay
|
||||
@ -84,6 +79,65 @@ void resetIDEDevices(void) {
|
||||
delayMicroseconds(_IDE_RESET_CLEAR_DELAY);
|
||||
}
|
||||
|
||||
/* System bus DMA */
|
||||
|
||||
static constexpr int _DMA_TIMEOUT = 100000;
|
||||
|
||||
size_t doDMARead(volatile void *source, void *data, size_t length, bool wait) {
|
||||
length = (length + 3) / 4;
|
||||
|
||||
util::assertAligned<uint32_t>(data);
|
||||
|
||||
if (!waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT))
|
||||
return 0;
|
||||
|
||||
// The BIU will output the base address set through this register over the
|
||||
// address lines during a DMA transfer. This does not affect non-DMA access
|
||||
// as the BIU will realign the address by masking off the bottommost N bits
|
||||
// (where N is the number of address lines used) and replace them with the
|
||||
// respective CPU address bits.
|
||||
BIU_DEV0_ADDR = reinterpret_cast<uint32_t>(source) & 0x1fffffff;
|
||||
|
||||
DMA_MADR(DMA_PIO) = reinterpret_cast<uint32_t>(data);
|
||||
DMA_BCR (DMA_PIO) = length;
|
||||
DMA_CHCR(DMA_PIO) = 0
|
||||
| DMA_CHCR_READ
|
||||
| DMA_CHCR_MODE_BURST
|
||||
| DMA_CHCR_ENABLE
|
||||
| DMA_CHCR_TRIGGER;
|
||||
|
||||
if (wait)
|
||||
waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT);
|
||||
|
||||
return length * 4;
|
||||
}
|
||||
|
||||
size_t doDMAWrite(
|
||||
volatile void *dest, const void *data, size_t length, bool wait
|
||||
) {
|
||||
length = (length + 3) / 4;
|
||||
|
||||
util::assertAligned<uint32_t>(data);
|
||||
|
||||
if (!waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT))
|
||||
return 0;
|
||||
|
||||
BIU_DEV0_ADDR = reinterpret_cast<uint32_t>(dest) & 0x1fffffff;
|
||||
|
||||
DMA_MADR(DMA_PIO) = reinterpret_cast<uint32_t>(data);
|
||||
DMA_BCR (DMA_PIO) = length;
|
||||
DMA_CHCR(DMA_PIO) = 0
|
||||
| DMA_CHCR_WRITE
|
||||
| DMA_CHCR_MODE_BURST
|
||||
| DMA_CHCR_ENABLE
|
||||
| DMA_CHCR_TRIGGER;
|
||||
|
||||
if (wait)
|
||||
waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT);
|
||||
|
||||
return length * 4;
|
||||
}
|
||||
|
||||
/* JAMMA and RTC functions */
|
||||
|
||||
uint32_t getJAMMAInputs(void) {
|
||||
|
@ -161,6 +161,15 @@ static inline void setMiscOutput(MiscOutputPin pin, bool value) {
|
||||
void init(void);
|
||||
void resetIDEDevices(void);
|
||||
|
||||
/* System bus DMA */
|
||||
|
||||
size_t doDMARead(
|
||||
volatile void *source, void *data, size_t length, bool wait = false
|
||||
);
|
||||
size_t doDMAWrite(
|
||||
volatile void *dest, const void *data, size_t length, bool wait = false
|
||||
);
|
||||
|
||||
/* JAMMA and RTC functions */
|
||||
|
||||
uint32_t getJAMMAInputs(void);
|
||||
|
@ -62,8 +62,9 @@ static const uint8_t _BS_QUANT_TABLE[]{
|
||||
|
||||
/* Basic API */
|
||||
|
||||
static constexpr int _DMA_CHUNK_SIZE = 32;
|
||||
static constexpr int _DMA_TIMEOUT = 100000;
|
||||
static constexpr int _DMA_CHUNK_SIZE_IN = 32;
|
||||
static constexpr int _DMA_CHUNK_SIZE_OUT = 32;
|
||||
static constexpr int _DMA_TIMEOUT = 100000;
|
||||
|
||||
void init(void) {
|
||||
MDEC1 = MDEC_CTRL_RESET;
|
||||
@ -82,16 +83,16 @@ size_t feed(const void *data, size_t length, bool wait) {
|
||||
|
||||
util::assertAligned<uint32_t>(data);
|
||||
#if 0
|
||||
assert(!(length % _DMA_CHUNK_SIZE));
|
||||
assert(!(length % _DMA_CHUNK_SIZE_IN));
|
||||
#else
|
||||
length = (length + _DMA_CHUNK_SIZE - 1) / _DMA_CHUNK_SIZE;
|
||||
length = (length + _DMA_CHUNK_SIZE_IN - 1) / _DMA_CHUNK_SIZE_IN;
|
||||
#endif
|
||||
|
||||
if (!waitForDMATransfer(DMA_MDEC_IN, _DMA_TIMEOUT))
|
||||
return 0;
|
||||
|
||||
DMA_MADR(DMA_MDEC_IN) = reinterpret_cast<uint32_t>(data);
|
||||
DMA_BCR (DMA_MDEC_IN) = util::concat4(_DMA_CHUNK_SIZE, length);
|
||||
DMA_BCR (DMA_MDEC_IN) = util::concat4(_DMA_CHUNK_SIZE_IN, length);
|
||||
DMA_CHCR(DMA_MDEC_IN) = 0
|
||||
| DMA_CHCR_WRITE
|
||||
| DMA_CHCR_MODE_SLICE
|
||||
@ -100,7 +101,33 @@ size_t feed(const void *data, size_t length, bool wait) {
|
||||
if (wait)
|
||||
waitForDMATransfer(DMA_MDEC_IN, _DMA_TIMEOUT);
|
||||
|
||||
return length * _DMA_CHUNK_SIZE * 4;
|
||||
return length * _DMA_CHUNK_SIZE_IN * 4;
|
||||
}
|
||||
|
||||
size_t receive(void *data, size_t length, bool wait) {
|
||||
length /= 4;
|
||||
|
||||
util::assertAligned<uint32_t>(data);
|
||||
#if 0
|
||||
assert(!(length % _DMA_CHUNK_SIZE_OUT));
|
||||
#else
|
||||
length = (length + _DMA_CHUNK_SIZE_OUT - 1) / _DMA_CHUNK_SIZE_OUT;
|
||||
#endif
|
||||
|
||||
if (!waitForDMATransfer(DMA_MDEC_OUT, _DMA_TIMEOUT))
|
||||
return 0;
|
||||
|
||||
DMA_MADR(DMA_MDEC_OUT) = reinterpret_cast<uint32_t>(data);
|
||||
DMA_BCR (DMA_MDEC_OUT) = util::concat4(_DMA_CHUNK_SIZE_OUT, length);
|
||||
DMA_CHCR(DMA_MDEC_OUT) = 0
|
||||
| DMA_CHCR_READ
|
||||
| DMA_CHCR_MODE_SLICE
|
||||
| DMA_CHCR_ENABLE;
|
||||
|
||||
if (wait)
|
||||
waitForDMATransfer(DMA_MDEC_OUT, _DMA_TIMEOUT);
|
||||
|
||||
return length * _DMA_CHUNK_SIZE_OUT * 4;
|
||||
}
|
||||
|
||||
/* MDEC bitstream decompressor */
|
||||
|
@ -25,13 +25,16 @@ namespace mdec {
|
||||
/* Basic API */
|
||||
|
||||
void init(void);
|
||||
size_t feed(const void *data, size_t length, bool wait);
|
||||
size_t feed(const void *data, size_t length, bool wait = false);
|
||||
size_t receive(void *data, size_t length, bool wait = false);
|
||||
|
||||
static inline bool isIdle(void) {
|
||||
return !(MDEC1 & MDEC_STAT_BUSY);
|
||||
}
|
||||
|
||||
static inline size_t feedBS(const uint32_t *data, uint32_t flags, bool wait) {
|
||||
static inline size_t feedBS(
|
||||
const uint32_t *data, uint32_t flags, bool wait = false
|
||||
) {
|
||||
size_t length = data[0] & MDEC_CMD_FLAG_LENGTH_BITMASK;
|
||||
|
||||
MDEC0 = MDEC_CMD_DECODE | length | flags;
|
||||
@ -40,12 +43,19 @@ static inline size_t feedBS(const uint32_t *data, uint32_t flags, bool wait) {
|
||||
|
||||
/* MDEC bitstream decompressor */
|
||||
|
||||
struct BSHeader {
|
||||
class BSHeader {
|
||||
public:
|
||||
uint16_t outputLength;
|
||||
uint16_t mdecCommand;
|
||||
uint16_t quantScale;
|
||||
uint16_t version;
|
||||
|
||||
inline size_t getUncompLength(void) const {
|
||||
// DMA feeds data to the MDEC in 32-word chunks so the uncompressed
|
||||
// length has to be rounded accordingly. Additionally, the decompressor
|
||||
// generates a 4-byte header containing the command to send to the MDEC.
|
||||
return (outputLength + 4 + 127) / 128;
|
||||
}
|
||||
};
|
||||
|
||||
enum BSDecompressorError {
|
||||
@ -55,7 +65,7 @@ enum BSDecompressorError {
|
||||
};
|
||||
|
||||
extern "C" BSDecompressorError _bsDecompressorStart(
|
||||
void *_this, uint32_t *output, size_t outputLength, const uint32_t *input
|
||||
void *_this, uint32_t *output, size_t outputLength, const void *input
|
||||
);
|
||||
extern "C" BSDecompressorError _bsDecompressorResume(
|
||||
void *_this, uint32_t *output, size_t outputLength
|
||||
@ -63,7 +73,7 @@ extern "C" BSDecompressorError _bsDecompressorResume(
|
||||
|
||||
class BSDecompressor {
|
||||
protected:
|
||||
const uint32_t *_input;
|
||||
const void *_input;
|
||||
|
||||
uint32_t _bits, _nextBits;
|
||||
size_t _remaining;
|
||||
@ -76,7 +86,7 @@ protected:
|
||||
|
||||
public:
|
||||
inline BSDecompressorError decompress(
|
||||
uint32_t *output, const uint32_t *input, size_t outputLength
|
||||
uint32_t *output, const void *input, size_t outputLength
|
||||
) {
|
||||
return _bsDecompressorStart(this, output, outputLength, input);
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ void init(void) {
|
||||
SPU_MASTER_VOL_R = 0;
|
||||
SPU_REVERB_VOL_L = 0;
|
||||
SPU_REVERB_VOL_R = 0;
|
||||
SPU_REVERB_ADDR = 0xfffe;
|
||||
SPU_REVERB_ADDR = SPU_RAM_END / 8;
|
||||
|
||||
SPU_FLAG_FM1 = 0;
|
||||
SPU_FLAG_FM2 = 0;
|
||||
@ -88,6 +88,8 @@ void init(void) {
|
||||
}
|
||||
|
||||
Channel getFreeChannel(void) {
|
||||
util::CriticalSection sec;
|
||||
|
||||
#if 0
|
||||
// The status flag gets set when a channel stops or loops for the first
|
||||
// time rather than when it actually goes silent (so it will be set early
|
||||
@ -111,6 +113,8 @@ Channel getFreeChannel(void) {
|
||||
}
|
||||
|
||||
ChannelMask getFreeChannels(int count) {
|
||||
util::CriticalSection sec;
|
||||
|
||||
ChannelMask mask = 0;
|
||||
|
||||
for (Channel ch = 0; ch < NUM_CHANNELS; ch++) {
|
||||
@ -130,6 +134,9 @@ ChannelMask getFreeChannels(int count) {
|
||||
void stopChannels(ChannelMask mask) {
|
||||
mask &= ALL_CHANNELS;
|
||||
|
||||
SPU_FLAG_OFF1 = mask & 0xffff;
|
||||
SPU_FLAG_OFF2 = mask >> 16;
|
||||
|
||||
for (Channel ch = 0; mask; ch++, mask >>= 1) {
|
||||
if (!(mask & 1))
|
||||
continue;
|
||||
@ -140,10 +147,8 @@ void stopChannels(ChannelMask mask) {
|
||||
SPU_CH_ADDR(ch) = DUMMY_BLOCK_OFFSET / 8;
|
||||
}
|
||||
|
||||
SPU_FLAG_OFF1 = mask & 0xffff;
|
||||
SPU_FLAG_OFF2 = mask >> 16;
|
||||
SPU_FLAG_ON1 = mask & 0xffff;
|
||||
SPU_FLAG_ON2 = mask >> 16;
|
||||
SPU_FLAG_ON1 = mask & 0xffff;
|
||||
SPU_FLAG_ON2 = mask >> 16;
|
||||
}
|
||||
|
||||
size_t upload(uint32_t offset, const void *data, size_t length, bool wait) {
|
||||
@ -223,16 +228,13 @@ size_t download(uint32_t offset, void *data, size_t length, bool wait) {
|
||||
Sound::Sound(void)
|
||||
: offset(0), sampleRate(0), length(0) {}
|
||||
|
||||
bool Sound::initFromVAGHeader(const VAGHeader *header, uint32_t _offset) {
|
||||
if (header->magic != util::concat4('V', 'A', 'G', 'p'))
|
||||
return false;
|
||||
if (header->channels > 1)
|
||||
bool Sound::initFromVAGHeader(const VAGHeader &header, uint32_t _offset) {
|
||||
if (!header.validateMagic())
|
||||
return false;
|
||||
|
||||
offset = _offset;
|
||||
sampleRate = (__builtin_bswap32(header->sampleRate) << 12) / 44100;
|
||||
length = __builtin_bswap32(header->length) / 8;
|
||||
|
||||
sampleRate = header.getSPUSampleRate();
|
||||
length = header.getSPULength();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -315,21 +317,20 @@ channels(0) {
|
||||
}
|
||||
|
||||
bool Stream::initFromVAGHeader(
|
||||
const VAGHeader *header, uint32_t _offset, size_t _numChunks
|
||||
const VAGHeader &header, uint32_t _offset, size_t _numChunks
|
||||
) {
|
||||
if (isPlaying())
|
||||
return false;
|
||||
if (header->magic != util::concat4('V', 'A', 'G', 'i'))
|
||||
return false;
|
||||
if (!header->interleave)
|
||||
if (!header.validateInterleavedMagic())
|
||||
return false;
|
||||
|
||||
resetBuffer();
|
||||
|
||||
offset = _offset;
|
||||
interleave = header->interleave;
|
||||
interleave = header.interleave;
|
||||
numChunks = _numChunks;
|
||||
sampleRate = (__builtin_bswap32(header->sampleRate) << 12) / 44100;
|
||||
channels = header->channels ? header->channels : 2;
|
||||
|
||||
sampleRate = header.getSPUSampleRate();
|
||||
channels = header.channels ? header.channels : 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -380,12 +381,13 @@ ChannelMask Stream::start(uint16_t left, uint16_t right, ChannelMask mask) {
|
||||
void Stream::stop(void) {
|
||||
util::CriticalSection sec;
|
||||
|
||||
if (isPlaying()) {
|
||||
SPU_CTRL &= ~SPU_CTRL_IRQ_ENABLE;
|
||||
_channelMask = 0;
|
||||
if (!isPlaying())
|
||||
return;
|
||||
|
||||
stopChannels(_channelMask);
|
||||
}
|
||||
SPU_CTRL &= ~SPU_CTRL_IRQ_ENABLE;
|
||||
|
||||
stopChannels(_channelMask);
|
||||
_channelMask = 0;
|
||||
|
||||
flushWriteQueue();
|
||||
}
|
||||
@ -402,30 +404,30 @@ void Stream::handleInterrupt(void) {
|
||||
_configureIRQ();
|
||||
}
|
||||
|
||||
size_t Stream::feed(const void *data, size_t count) {
|
||||
size_t Stream::feed(const void *data, size_t length) {
|
||||
util::CriticalSection sec;
|
||||
|
||||
auto ptr = reinterpret_cast<uintptr_t>(data);
|
||||
auto chunkLength = getChunkLength();
|
||||
count = util::min(count, getFreeChunkCount());
|
||||
|
||||
for (auto i = count; i; i--) {
|
||||
length = util::min(length, getFreeChunkCount() * chunkLength);
|
||||
|
||||
for (int i = length; i >= int(chunkLength); i -= chunkLength) {
|
||||
upload(
|
||||
_getChunkOffset(_tail), reinterpret_cast<const void *>(ptr),
|
||||
chunkLength, true
|
||||
);
|
||||
|
||||
_tail = (_tail + 1) % numChunks;
|
||||
ptr += chunkLength;
|
||||
_tail = (_tail + 1) % numChunks;
|
||||
_bufferedChunks++;
|
||||
}
|
||||
|
||||
_bufferedChunks += count;
|
||||
|
||||
if (isPlaying())
|
||||
_configureIRQ();
|
||||
|
||||
flushWriteQueue();
|
||||
return count;
|
||||
return length;
|
||||
}
|
||||
|
||||
void Stream::resetBuffer(void) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "common/util/templates.hpp"
|
||||
#include "ps1/registers.h"
|
||||
|
||||
namespace spu {
|
||||
@ -31,8 +32,9 @@ enum LoopFlag : uint8_t {
|
||||
LOOP_START = 1 << 2
|
||||
};
|
||||
|
||||
static constexpr uint32_t DUMMY_BLOCK_OFFSET = 0x1000;
|
||||
static constexpr uint32_t DUMMY_BLOCK_END = 0x1010;
|
||||
static constexpr uint32_t DUMMY_BLOCK_OFFSET = 0x01000;
|
||||
static constexpr uint32_t DUMMY_BLOCK_END = 0x01010;
|
||||
static constexpr uint32_t SPU_RAM_END = 0x7fff0;
|
||||
|
||||
static constexpr int NUM_CHANNELS = 24;
|
||||
static constexpr uint16_t MAX_VOLUME = 0x3fff;
|
||||
@ -65,18 +67,37 @@ static inline void stopChannel(Channel ch) {
|
||||
stopChannels(1 << ch);
|
||||
}
|
||||
|
||||
size_t upload(uint32_t offset, const void *data, size_t length, bool wait);
|
||||
size_t download(uint32_t offset, void *data, size_t length, bool wait);
|
||||
size_t upload(
|
||||
uint32_t offset, const void *data, size_t length, bool wait = false
|
||||
);
|
||||
size_t download(uint32_t offset, void *data, size_t length, bool wait = false);
|
||||
|
||||
/* Sound class */
|
||||
|
||||
static constexpr size_t INTERLEAVED_VAG_BODY_OFFSET = 2048;
|
||||
|
||||
struct VAGHeader {
|
||||
class VAGHeader {
|
||||
public:
|
||||
uint32_t magic, version, interleave, length, sampleRate;
|
||||
uint16_t _reserved[5], channels;
|
||||
char name[16];
|
||||
|
||||
inline bool validateMagic(void) const {
|
||||
return (magic == util::concat4('V', 'A', 'G', 'p')) && (channels <= 1);
|
||||
}
|
||||
inline bool validateInterleavedMagic(void) const {
|
||||
return (magic == util::concat4('V', 'A', 'G', 'i')) && interleave;
|
||||
}
|
||||
|
||||
inline uint16_t getSPUSampleRate(void) const {
|
||||
return (__builtin_bswap32(sampleRate) << 12) / 44100;
|
||||
}
|
||||
inline uint16_t getSPULength(void) const {
|
||||
return __builtin_bswap32(length) / 8;
|
||||
}
|
||||
inline const void *getData(void) const {
|
||||
return this + 1;
|
||||
}
|
||||
};
|
||||
|
||||
class Sound {
|
||||
@ -91,7 +112,7 @@ public:
|
||||
}
|
||||
|
||||
Sound(void);
|
||||
bool initFromVAGHeader(const VAGHeader *header, uint32_t _offset);
|
||||
bool initFromVAGHeader(const VAGHeader &header, uint32_t _offset);
|
||||
Channel play(uint16_t left, uint16_t right, Channel ch) const;
|
||||
};
|
||||
|
||||
@ -125,6 +146,12 @@ public:
|
||||
|
||||
return (_channelMask != 0);
|
||||
}
|
||||
inline bool isUnderrun(void) const {
|
||||
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
||||
|
||||
return !_bufferedChunks;
|
||||
}
|
||||
|
||||
inline size_t getChunkLength(void) const {
|
||||
return size_t(interleave) * size_t(channels);
|
||||
}
|
||||
@ -132,19 +159,19 @@ public:
|
||||
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
||||
|
||||
// The currently playing chunk cannot be overwritten.
|
||||
size_t playingChunk = isPlaying() ? 1 : 0;
|
||||
size_t playingChunk = _channelMask ? 1 : 0;
|
||||
return numChunks - (_bufferedChunks + playingChunk);
|
||||
}
|
||||
|
||||
Stream(void);
|
||||
bool initFromVAGHeader(
|
||||
const VAGHeader *header, uint32_t _offset, size_t _numChunks
|
||||
const VAGHeader &header, uint32_t _offset, size_t _numChunks
|
||||
);
|
||||
ChannelMask start(uint16_t left, uint16_t right, ChannelMask mask);
|
||||
void stop(void);
|
||||
void handleInterrupt(void);
|
||||
|
||||
size_t feed(const void *data, size_t count);
|
||||
size_t feed(const void *data, size_t length);
|
||||
void resetBuffer(void);
|
||||
};
|
||||
|
||||
|
@ -103,9 +103,9 @@ DeviceError ATADevice::_transfer(
|
||||
return error;
|
||||
|
||||
if (write)
|
||||
_writePIO(reinterpret_cast<const void *>(ptr), _SECTOR_LENGTH);
|
||||
_writeData(reinterpret_cast<const void *>(ptr), _SECTOR_LENGTH);
|
||||
else
|
||||
_readPIO(reinterpret_cast<void *>(ptr), _SECTOR_LENGTH);
|
||||
_readData(reinterpret_cast<void *>(ptr), _SECTOR_LENGTH);
|
||||
}
|
||||
|
||||
lba += chunkLength;
|
||||
@ -131,7 +131,7 @@ DeviceError ATADevice::enumerate(void) {
|
||||
if (_waitForDRQ(_DETECT_TIMEOUT))
|
||||
return NO_DRIVE;
|
||||
|
||||
_readPIO(&block, sizeof(IDEIdentifyBlock));
|
||||
_readData(&block, sizeof(IDEIdentifyBlock));
|
||||
|
||||
if (!block.validateChecksum())
|
||||
return CHECKSUM_MISMATCH;
|
||||
|
@ -131,7 +131,7 @@ DeviceError ATAPIDevice::_requestSense(void) {
|
||||
error = _waitForDRQ(_REQ_SENSE_TIMEOUT, true);
|
||||
}
|
||||
if (!error) {
|
||||
_writePIO(&packet, _getPacketLength());
|
||||
_writeData(&packet, _getPacketLength());
|
||||
|
||||
error = _waitForDRQ(_REQ_SENSE_TIMEOUT, true);
|
||||
}
|
||||
@ -141,7 +141,7 @@ DeviceError ATAPIDevice::_requestSense(void) {
|
||||
if (!error) {
|
||||
size_t length = _getCylinder();
|
||||
|
||||
_readPIO(&lastSenseData, length);
|
||||
_readData(&lastSenseData, length);
|
||||
LOG_STORAGE("data ok, length=0x%x", length);
|
||||
} else {
|
||||
// If the request sense command fails, fall back to reading the sense
|
||||
@ -185,7 +185,7 @@ DeviceError ATAPIDevice::_issuePacket(
|
||||
error = _waitForDRQ();
|
||||
}
|
||||
if (!error) {
|
||||
_writePIO(&packet, _getPacketLength());
|
||||
_writeData(&packet, _getPacketLength());
|
||||
|
||||
error = dataLength
|
||||
? _waitForDRQ()
|
||||
@ -228,7 +228,7 @@ DeviceError ATAPIDevice::enumerate(void) {
|
||||
if (_waitForDRQ(_DETECT_TIMEOUT))
|
||||
return NO_DRIVE;
|
||||
|
||||
_readPIO(&block, sizeof(IDEIdentifyBlock));
|
||||
_readData(&block, sizeof(IDEIdentifyBlock));
|
||||
|
||||
if (!block.validateChecksum())
|
||||
return CHECKSUM_MISMATCH;
|
||||
@ -298,7 +298,7 @@ DeviceError ATAPIDevice::read(void *data, uint64_t lba, size_t count) {
|
||||
|
||||
size_t chunkLength = _getCylinder();
|
||||
|
||||
_readPIO(reinterpret_cast<void *>(ptr), chunkLength);
|
||||
_readData(reinterpret_cast<void *>(ptr), chunkLength);
|
||||
ptr += chunkLength;
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "common/storage/atapi.hpp"
|
||||
#include "common/storage/device.hpp"
|
||||
#include "common/util/log.hpp"
|
||||
#include "common/io.hpp"
|
||||
#include "ps1/registers573.h"
|
||||
#include "ps1/system.h"
|
||||
|
||||
@ -98,11 +99,11 @@ int IDEIdentifyBlock::getHighestPIOMode(void) const {
|
||||
|
||||
/* IDE data transfers */
|
||||
|
||||
static constexpr int _DMA_TIMEOUT = 10000;
|
||||
|
||||
void IDEDevice::_readPIO(void *data, size_t length) const {
|
||||
length++;
|
||||
length /= 2;
|
||||
void IDEDevice::_readData(void *data, size_t length) const {
|
||||
#if 0
|
||||
io::doDMARead(&SYS573_IDE_CS0_BASE[CS0_DATA], data, length);
|
||||
#else
|
||||
length = (length + 1) / 2;
|
||||
|
||||
util::assertAligned<uint16_t>(data);
|
||||
|
||||
@ -110,11 +111,14 @@ void IDEDevice::_readPIO(void *data, size_t length) const {
|
||||
|
||||
for (; length > 0; length--)
|
||||
*(ptr++) = SYS573_IDE_CS0_BASE[CS0_DATA];
|
||||
#endif
|
||||
}
|
||||
|
||||
void IDEDevice::_writePIO(const void *data, size_t length) const {
|
||||
length++;
|
||||
length /= 2;
|
||||
void IDEDevice::_writeData(const void *data, size_t length) const {
|
||||
#if 0
|
||||
io::doDMAWrite(&SYS573_IDE_CS0_BASE[CS0_DATA], data, length);
|
||||
#else
|
||||
length = (length + 1) / 2;
|
||||
|
||||
util::assertAligned<uint16_t>(data);
|
||||
|
||||
@ -122,40 +126,7 @@ void IDEDevice::_writePIO(const void *data, size_t length) const {
|
||||
|
||||
for (; length > 0; length--)
|
||||
SYS573_IDE_CS0_BASE[CS0_DATA] = *(ptr++);
|
||||
}
|
||||
|
||||
bool IDEDevice::_readDMA(void *data, size_t length) const {
|
||||
util::assertAligned<uint32_t>(data);
|
||||
|
||||
if (!waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT))
|
||||
return false;
|
||||
|
||||
DMA_MADR(DMA_PIO) = reinterpret_cast<uint32_t>(data);
|
||||
DMA_BCR (DMA_PIO) = (length + 3) / 4;
|
||||
DMA_CHCR(DMA_PIO) = 0
|
||||
| DMA_CHCR_READ
|
||||
| DMA_CHCR_MODE_BURST
|
||||
| DMA_CHCR_ENABLE
|
||||
| DMA_CHCR_TRIGGER;
|
||||
|
||||
return waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT);
|
||||
}
|
||||
|
||||
bool IDEDevice::_writeDMA(const void *data, size_t length) const {
|
||||
util::assertAligned<uint32_t>(data);
|
||||
|
||||
if (!waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT))
|
||||
return false;
|
||||
|
||||
DMA_MADR(DMA_PIO) = reinterpret_cast<uint32_t>(data);
|
||||
DMA_BCR (DMA_PIO) = (length + 3) / 4;
|
||||
DMA_CHCR(DMA_PIO) = 0
|
||||
| DMA_CHCR_WRITE
|
||||
| DMA_CHCR_MODE_BURST
|
||||
| DMA_CHCR_ENABLE
|
||||
| DMA_CHCR_TRIGGER;
|
||||
|
||||
return waitForDMATransfer(DMA_PIO, _DMA_TIMEOUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* IDE status and error polling */
|
||||
|
@ -271,10 +271,8 @@ protected:
|
||||
return util::concat2(_get(CS0_CYLINDER_L), _get(CS0_CYLINDER_H));
|
||||
}
|
||||
|
||||
void _readPIO(void *data, size_t length) const;
|
||||
void _writePIO(const void *data, size_t length) const;
|
||||
bool _readDMA(void *data, size_t length) const;
|
||||
bool _writeDMA(const void *data, size_t length) const;
|
||||
void _readData(void *data, size_t length) const;
|
||||
void _writeData(const void *data, size_t length) const;
|
||||
|
||||
DeviceError _setup(const IDEIdentifyBlock &block);
|
||||
DeviceError _waitForIdle(
|
||||
|
@ -82,10 +82,6 @@ template<typename T> static constexpr inline size_t countOf(T &array) {
|
||||
return sizeof(array) / sizeof(array[0]);
|
||||
}
|
||||
|
||||
template<typename T, typename X> static inline T forcedCast(X item) {
|
||||
return reinterpret_cast<T>(reinterpret_cast<void *>(item));
|
||||
}
|
||||
|
||||
static constexpr inline uint16_t concat2(uint8_t low, uint8_t high) {
|
||||
return low | (high << 8);
|
||||
}
|
||||
|
@ -14,7 +14,9 @@
|
||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "common/fs/fat.hpp"
|
||||
#include "common/fs/file.hpp"
|
||||
@ -30,7 +32,9 @@
|
||||
#include "common/io.hpp"
|
||||
#include "common/spu.hpp"
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/app/threads.hpp"
|
||||
#include "main/cart/cart.hpp"
|
||||
#include "main/workers/miscworkers.hpp"
|
||||
#include "main/uibase.hpp"
|
||||
#include "ps1/system.h"
|
||||
|
||||
@ -201,7 +205,7 @@ void FileIOManager::closeResourceFile(void) {
|
||||
|
||||
/* App class */
|
||||
|
||||
static constexpr size_t _WORKER_STACK_SIZE = 0x20000;
|
||||
static constexpr size_t _WORKER_STACK_SIZE = 0x8000;
|
||||
|
||||
static constexpr int _SPLASH_SCREEN_TIMEOUT = 5;
|
||||
|
||||
@ -239,18 +243,42 @@ void App::_unloadCartData(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void App::_setupInterrupts(void) {
|
||||
setInterruptHandler(
|
||||
util::forcedCast<ArgFunction>(&App::_interruptHandler), this
|
||||
);
|
||||
void App::_updateOverlays(void) {
|
||||
// Date and time overlay
|
||||
static char dateString[24];
|
||||
util::Date date;
|
||||
|
||||
IRQ_MASK = 0
|
||||
| (1 << IRQ_VSYNC)
|
||||
| (1 << IRQ_SPU)
|
||||
| (1 << IRQ_PIO);
|
||||
enableInterrupts();
|
||||
io::getRTCTime(date);
|
||||
date.toString(dateString);
|
||||
|
||||
_textOverlay.leftText = dateString;
|
||||
|
||||
// Splash screen overlay
|
||||
int timeout = _ctx.gpuCtx.refreshRate * _SPLASH_SCREEN_TIMEOUT;
|
||||
|
||||
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
||||
|
||||
if ((_workerStatus.status != WORKER_BUSY) || (_ctx.time > timeout))
|
||||
_splashOverlay.hide(_ctx);
|
||||
|
||||
#ifdef ENABLE_LOG_BUFFER
|
||||
// Log overlay
|
||||
if (
|
||||
_ctx.buttons.released(ui::BTN_DEBUG) &&
|
||||
!_ctx.buttons.longReleased(ui::BTN_DEBUG)
|
||||
)
|
||||
_logOverlay.toggle(_ctx);
|
||||
#endif
|
||||
|
||||
// Screenshot overlay
|
||||
if (_ctx.buttons.longPressed(ui::BTN_DEBUG)) {
|
||||
if (_takeScreenshot())
|
||||
_screenshotOverlay.animate(_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/* App filesystem functions */
|
||||
|
||||
static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{
|
||||
"assets/sounds/startup.vag", // ui::SOUND_STARTUP
|
||||
"assets/sounds/about.vag", // ui::SOUND_ABOUT_SCREEN
|
||||
@ -275,7 +303,7 @@ void App::_loadResources(void) {
|
||||
uint32_t spuOffset = spu::DUMMY_BLOCK_END;
|
||||
|
||||
for (int i = 0; i < ui::NUM_UI_SOUNDS; i++)
|
||||
res.loadVAG(_ctx.sounds[i], spuOffset, _UI_SOUND_PATHS[i]);
|
||||
spuOffset += res.loadVAG(_ctx.sounds[i], spuOffset, _UI_SOUND_PATHS[i]);
|
||||
}
|
||||
|
||||
bool App::_createDataDirectory(void) {
|
||||
@ -334,54 +362,74 @@ bool App::_takeScreenshot(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void App::_updateOverlays(void) {
|
||||
// Date and time overlay
|
||||
static char dateString[24];
|
||||
util::Date date;
|
||||
/* App callbacks */
|
||||
|
||||
io::getRTCTime(date);
|
||||
date.toString(dateString);
|
||||
void _appInterruptHandler(void *arg0, void *arg1) {
|
||||
auto app = reinterpret_cast<App *>(arg0);
|
||||
|
||||
_textOverlay.leftText = dateString;
|
||||
if (acknowledgeInterrupt(IRQ_VSYNC)) {
|
||||
app->_ctx.tick();
|
||||
|
||||
// Splash screen overlay
|
||||
int timeout = _ctx.gpuCtx.refreshRate * _SPLASH_SCREEN_TIMEOUT;
|
||||
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
||||
|
||||
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
||||
if (app->_workerStatus.status != WORKER_REBOOT)
|
||||
io::clearWatchdog();
|
||||
if (
|
||||
gpu::isIdle() &&
|
||||
(app->_workerStatus.status != WORKER_BUSY_SUSPEND)
|
||||
)
|
||||
switchThread(nullptr);
|
||||
}
|
||||
|
||||
if ((_workerStatus.status != WORKER_BUSY) || (_ctx.time > timeout))
|
||||
_splashOverlay.hide(_ctx);
|
||||
if (acknowledgeInterrupt(IRQ_SPU))
|
||||
app->_audioStream.handleInterrupt();
|
||||
|
||||
#ifdef ENABLE_LOG_BUFFER
|
||||
// Log overlay
|
||||
if (
|
||||
_ctx.buttons.released(ui::BTN_DEBUG) &&
|
||||
!_ctx.buttons.longReleased(ui::BTN_DEBUG)
|
||||
)
|
||||
_logOverlay.toggle(_ctx);
|
||||
#endif
|
||||
|
||||
// Screenshot overlay
|
||||
if (_ctx.buttons.longPressed(ui::BTN_DEBUG)) {
|
||||
if (_takeScreenshot())
|
||||
_screenshotOverlay.animate(_ctx);
|
||||
if (acknowledgeInterrupt(IRQ_PIO)) {
|
||||
for (auto dev : app->_fileIO.ideDevices) {
|
||||
if (dev)
|
||||
dev->handleInterrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _workerMain(void *arg0, void *arg1) {
|
||||
auto app = reinterpret_cast<App *>(arg0);
|
||||
auto func = reinterpret_cast<bool (*)(App &)>(arg1);
|
||||
|
||||
if (func)
|
||||
func(*app);
|
||||
|
||||
app->_workerStatus.setStatus(WORKER_DONE);
|
||||
|
||||
// Do nothing while waiting for vblank once the task is done.
|
||||
for (;;)
|
||||
__asm__ volatile("");
|
||||
}
|
||||
|
||||
void App::_setupInterrupts(void) {
|
||||
setInterruptHandler(&_appInterruptHandler, this, nullptr);
|
||||
|
||||
IRQ_MASK = 0
|
||||
| (1 << IRQ_VSYNC)
|
||||
| (1 << IRQ_SPU)
|
||||
| (1 << IRQ_PIO);
|
||||
enableInterrupts();
|
||||
}
|
||||
|
||||
void App::_runWorker(
|
||||
bool (App::*func)(void), ui::Screen &next, bool goBack, bool playSound
|
||||
bool (*func)(App &app), ui::Screen &next, bool goBack, bool playSound
|
||||
) {
|
||||
{
|
||||
util::CriticalSection sec;
|
||||
|
||||
_workerStatus.reset(next, goBack);
|
||||
_workerStack.allocate(_WORKER_STACK_SIZE);
|
||||
assert(_workerStack.ptr);
|
||||
|
||||
_workerFunction = func;
|
||||
auto stackBottom = _workerStack.as<uint8_t>();
|
||||
|
||||
initThread(
|
||||
&_workerThread, util::forcedCast<ArgFunction>(&App::_worker), this,
|
||||
&_workerThread, &_workerMain, this, reinterpret_cast<void *>(func),
|
||||
&stackBottom[(_WORKER_STACK_SIZE - 1) & ~7]
|
||||
);
|
||||
}
|
||||
@ -389,39 +437,7 @@ void App::_runWorker(
|
||||
_ctx.show(_workerStatusScreen, false, playSound);
|
||||
}
|
||||
|
||||
void App::_worker(void) {
|
||||
if (_workerFunction)
|
||||
(this->*_workerFunction)();
|
||||
|
||||
_workerStatus.setStatus(WORKER_DONE);
|
||||
|
||||
// Do nothing while waiting for vblank once the task is done.
|
||||
for (;;)
|
||||
__asm__ volatile("");
|
||||
}
|
||||
|
||||
void App::_interruptHandler(void) {
|
||||
if (acknowledgeInterrupt(IRQ_VSYNC)) {
|
||||
_ctx.tick();
|
||||
|
||||
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
||||
|
||||
if (_workerStatus.status != WORKER_REBOOT)
|
||||
io::clearWatchdog();
|
||||
if (gpu::isIdle() && (_workerStatus.status != WORKER_BUSY_SUSPEND))
|
||||
switchThread(nullptr);
|
||||
}
|
||||
|
||||
if (acknowledgeInterrupt(IRQ_SPU))
|
||||
_ctx.audioStream.handleInterrupt();
|
||||
|
||||
if (acknowledgeInterrupt(IRQ_PIO)) {
|
||||
for (auto dev : _fileIO.ideDevices) {
|
||||
if (dev)
|
||||
dev->handleInterrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
/* App main loop */
|
||||
|
||||
[[noreturn]] void App::run(const void *resourcePtr, size_t resourceLength) {
|
||||
#ifdef ENABLE_LOG_BUFFER
|
||||
@ -448,7 +464,8 @@ void App::_interruptHandler(void) {
|
||||
#endif
|
||||
_ctx.overlays[2] = &_screenshotOverlay;
|
||||
|
||||
_runWorker(&App::_startupWorker, _warningScreen);
|
||||
_audioStream.init(&_workerThread);
|
||||
_runWorker(&startupWorker, _warningScreen);
|
||||
_setupInterrupts();
|
||||
|
||||
_splashOverlay.show(_ctx);
|
||||
@ -459,7 +476,7 @@ void App::_interruptHandler(void) {
|
||||
_updateOverlays();
|
||||
|
||||
_ctx.draw();
|
||||
switchThreadImmediate(&_workerThread);
|
||||
_audioStream.yield();
|
||||
_ctx.gpuCtx.flip();
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "main/app/modals.hpp"
|
||||
#include "main/app/romactions.hpp"
|
||||
#include "main/app/tests.hpp"
|
||||
#include "main/app/threads.hpp"
|
||||
#include "main/cart/cart.hpp"
|
||||
#include "main/cart/cartdata.hpp"
|
||||
#include "main/cart/cartio.hpp"
|
||||
@ -101,47 +102,42 @@ public:
|
||||
/* App class */
|
||||
|
||||
class App {
|
||||
private:
|
||||
friend void _appInterruptHandler(void *arg0, void *arg1);
|
||||
friend void _workerMain(void *arg0, void *arg1);
|
||||
|
||||
// cartworkers.cpp
|
||||
friend bool cartDetectWorker(App &app);
|
||||
friend bool cartUnlockWorker(App &app);
|
||||
friend bool qrCodeWorker(App &app);
|
||||
friend bool cartDumpWorker(App &app);
|
||||
friend bool cartWriteWorker(App &app);
|
||||
friend bool cartRestoreWorker(App &app);
|
||||
friend bool cartReflashWorker(App &app);
|
||||
friend bool cartEraseWorker(App &app);
|
||||
|
||||
// romworkers.cpp
|
||||
friend bool romChecksumWorker(App &app);
|
||||
friend bool romDumpWorker(App &app);
|
||||
friend bool romRestoreWorker(App &app);
|
||||
friend bool romEraseWorker(App &app);
|
||||
friend bool flashExecutableWriteWorker(App &app);
|
||||
friend bool flashHeaderWriteWorker(App &app);
|
||||
|
||||
// miscworkers.cpp
|
||||
friend bool startupWorker(App &app);
|
||||
friend bool fileInitWorker(App &app);
|
||||
friend bool executableWorker(App &app);
|
||||
friend bool atapiEjectWorker(App &app);
|
||||
friend bool rebootWorker(App &app);
|
||||
|
||||
// modals.cpp
|
||||
friend class WorkerStatusScreen;
|
||||
friend class MessageScreen;
|
||||
friend class ConfirmScreen;
|
||||
friend class FilePickerScreen;
|
||||
friend class FileBrowserScreen;
|
||||
|
||||
friend class WarningScreen;
|
||||
friend class AutobootScreen;
|
||||
friend class ButtonMappingScreen;
|
||||
friend class MainMenuScreen;
|
||||
|
||||
friend class CartInfoScreen;
|
||||
friend class UnlockKeyScreen;
|
||||
friend class KeyEntryScreen;
|
||||
|
||||
friend class CartActionsScreen;
|
||||
friend class QRCodeScreen;
|
||||
friend class HexdumpScreen;
|
||||
friend class ReflashGameScreen;
|
||||
friend class SystemIDEntryScreen;
|
||||
|
||||
friend class StorageInfoScreen;
|
||||
friend class StorageActionsScreen;
|
||||
friend class CardSizeScreen;
|
||||
friend class ChecksumScreen;
|
||||
|
||||
friend class TestMenuScreen;
|
||||
friend class JAMMATestScreen;
|
||||
friend class AudioTestScreen;
|
||||
friend class TestPatternScreen;
|
||||
friend class ColorIntensityScreen;
|
||||
friend class GeometryScreen;
|
||||
|
||||
friend class IDEInfoScreen;
|
||||
friend class RTCTimeScreen;
|
||||
friend class LanguageScreen;
|
||||
friend class ResolutionScreen;
|
||||
friend class AboutScreen;
|
||||
|
||||
private:
|
||||
// modals.cpp
|
||||
WorkerStatusScreen _workerStatusScreen;
|
||||
MessageScreen _messageScreen;
|
||||
ConfirmScreen _confirmScreen;
|
||||
@ -149,17 +145,32 @@ private:
|
||||
FileBrowserScreen _fileBrowserScreen;
|
||||
|
||||
// main.cpp
|
||||
friend class WarningScreen;
|
||||
friend class AutobootScreen;
|
||||
friend class ButtonMappingScreen;
|
||||
friend class MainMenuScreen;
|
||||
|
||||
WarningScreen _warningScreen;
|
||||
AutobootScreen _autobootScreen;
|
||||
ButtonMappingScreen _buttonMappingScreen;
|
||||
MainMenuScreen _mainMenuScreen;
|
||||
|
||||
// cartunlock.cpp
|
||||
friend class CartInfoScreen;
|
||||
friend class UnlockKeyScreen;
|
||||
friend class KeyEntryScreen;
|
||||
|
||||
CartInfoScreen _cartInfoScreen;
|
||||
UnlockKeyScreen _unlockKeyScreen;
|
||||
KeyEntryScreen _keyEntryScreen;
|
||||
|
||||
// cartactions.cpp
|
||||
friend class CartActionsScreen;
|
||||
friend class QRCodeScreen;
|
||||
friend class HexdumpScreen;
|
||||
friend class ReflashGameScreen;
|
||||
friend class SystemIDEntryScreen;
|
||||
|
||||
CartActionsScreen _cartActionsScreen;
|
||||
QRCodeScreen _qrCodeScreen;
|
||||
HexdumpScreen _hexdumpScreen;
|
||||
@ -167,12 +178,24 @@ private:
|
||||
SystemIDEntryScreen _systemIDEntryScreen;
|
||||
|
||||
// romactions.cpp
|
||||
friend class StorageInfoScreen;
|
||||
friend class StorageActionsScreen;
|
||||
friend class CardSizeScreen;
|
||||
friend class ChecksumScreen;
|
||||
|
||||
StorageInfoScreen _storageInfoScreen;
|
||||
StorageActionsScreen _storageActionsScreen;
|
||||
CardSizeScreen _cardSizeScreen;
|
||||
ChecksumScreen _checksumScreen;
|
||||
|
||||
// tests.cpp
|
||||
friend class TestMenuScreen;
|
||||
friend class JAMMATestScreen;
|
||||
friend class AudioTestScreen;
|
||||
friend class TestPatternScreen;
|
||||
friend class ColorIntensityScreen;
|
||||
friend class GeometryScreen;
|
||||
|
||||
TestMenuScreen _testMenuScreen;
|
||||
JAMMATestScreen _jammaTestScreen;
|
||||
AudioTestScreen _audioTestScreen;
|
||||
@ -180,6 +203,12 @@ private:
|
||||
GeometryScreen _geometryScreen;
|
||||
|
||||
// misc.cpp
|
||||
friend class IDEInfoScreen;
|
||||
friend class RTCTimeScreen;
|
||||
friend class LanguageScreen;
|
||||
friend class ResolutionScreen;
|
||||
friend class AboutScreen;
|
||||
|
||||
IDEInfoScreen _ideInfoScreen;
|
||||
RTCTimeScreen _rtcTimeScreen;
|
||||
LanguageScreen _languageScreen;
|
||||
@ -195,66 +224,40 @@ private:
|
||||
#endif
|
||||
ui::ScreenshotOverlay _screenshotOverlay;
|
||||
|
||||
ui::Context &_ctx;
|
||||
fs::StringTable _stringTable;
|
||||
FileIOManager _fileIO;
|
||||
ui::Context &_ctx;
|
||||
fs::StringTable _stringTable;
|
||||
FileIOManager _fileIO;
|
||||
AudioStreamManager _audioStream;
|
||||
|
||||
Thread _workerThread;
|
||||
util::Data _workerStack;
|
||||
WorkerStatus _workerStatus;
|
||||
|
||||
cart::CartDump _cartDump;
|
||||
cart::ROMHeaderDump _romHeaderDump;
|
||||
cart::CartDB _cartDB;
|
||||
cart::ROMHeaderDB _romHeaderDB;
|
||||
|
||||
Thread _workerThread;
|
||||
util::Data _workerStack;
|
||||
WorkerStatus _workerStatus;
|
||||
bool (App::*_workerFunction)(void);
|
||||
|
||||
cart::Driver *_cartDriver;
|
||||
cart::CartParser *_cartParser;
|
||||
const cart::CartDBEntry *_identified, *_selectedEntry;
|
||||
|
||||
void _unloadCartData(void);
|
||||
void _setupInterrupts(void);
|
||||
void _updateOverlays(void);
|
||||
|
||||
void _loadResources(void);
|
||||
bool _createDataDirectory(void);
|
||||
bool _getNumberedPath(
|
||||
char *output, size_t length, const char *path, int maxIndex = 9999
|
||||
);
|
||||
bool _takeScreenshot(void);
|
||||
void _updateOverlays(void);
|
||||
|
||||
void _setupInterrupts(void);
|
||||
void _runWorker(
|
||||
bool (App::*func)(void), ui::Screen &next, bool goBack = false,
|
||||
bool (*func)(App &app), ui::Screen &next, bool goBack = false,
|
||||
bool playSound = false
|
||||
);
|
||||
|
||||
void _worker(void);
|
||||
void _interruptHandler(void);
|
||||
|
||||
// cartworkers.cpp
|
||||
bool _cartDetectWorker(void);
|
||||
bool _cartUnlockWorker(void);
|
||||
bool _qrCodeWorker(void);
|
||||
bool _cartDumpWorker(void);
|
||||
bool _cartWriteWorker(void);
|
||||
bool _cartRestoreWorker(void);
|
||||
bool _cartReflashWorker(void);
|
||||
bool _cartEraseWorker(void);
|
||||
|
||||
// romworkers.cpp
|
||||
bool _romChecksumWorker(void);
|
||||
bool _romDumpWorker(void);
|
||||
bool _romRestoreWorker(void);
|
||||
bool _romEraseWorker(void);
|
||||
bool _flashExecutableWriteWorker(void);
|
||||
bool _flashHeaderWriteWorker(void);
|
||||
|
||||
// miscworkers.cpp
|
||||
bool _startupWorker(void);
|
||||
bool _fileInitWorker(void);
|
||||
bool _executableWorker(void);
|
||||
bool _atapiEjectWorker(void);
|
||||
bool _rebootWorker(void);
|
||||
|
||||
public:
|
||||
App(ui::Context &ctx);
|
||||
~App(void);
|
||||
@ -266,5 +269,5 @@ public:
|
||||
#define STR(id) (APP->_stringTable.get(id ## _h))
|
||||
#define STRH(id) (APP->_stringTable.get(id))
|
||||
|
||||
#define WSTR(id) (_stringTable.get(id ## _h))
|
||||
#define WSTRH(id) (_stringTable.get(id))
|
||||
#define WSTR(id) (app._stringTable.get(id ## _h))
|
||||
#define WSTRH(id) (app._stringTable.get(id))
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "common/util/templates.hpp"
|
||||
#include "main/app/cartactions.hpp"
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/workers/cartworkers.hpp"
|
||||
#include "main/uibase.hpp"
|
||||
|
||||
/* Unlocked cartridge screens */
|
||||
@ -80,7 +81,7 @@ void CartActionsScreen::qrDump(ui::Context &ctx) {
|
||||
if (APP->_qrCodeScreen.valid)
|
||||
ctx.show(APP->_qrCodeScreen, false, true);
|
||||
else
|
||||
APP->_runWorker(&App::_qrCodeWorker, APP->_qrCodeScreen, false, true);
|
||||
APP->_runWorker(&qrCodeWorker, APP->_qrCodeScreen, false, true);
|
||||
}
|
||||
|
||||
void CartActionsScreen::hddDump(ui::Context &ctx) {
|
||||
@ -88,7 +89,7 @@ void CartActionsScreen::hddDump(ui::Context &ctx) {
|
||||
&(APP->_cartInfoScreen);
|
||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
|
||||
|
||||
APP->_runWorker(&App::_cartDumpWorker, APP->_messageScreen, false, true);
|
||||
APP->_runWorker(&cartDumpWorker, APP->_messageScreen, false, true);
|
||||
}
|
||||
|
||||
void CartActionsScreen::hexdump(ui::Context &ctx) {
|
||||
@ -111,7 +112,7 @@ void CartActionsScreen::hddRestore(ui::Context &ctx) {
|
||||
&(APP->_fileBrowserScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_cartRestoreWorker, APP->_cartInfoScreen, true, true
|
||||
cartRestoreWorker, APP->_cartInfoScreen, true, true
|
||||
);
|
||||
},
|
||||
STR("CartActionsScreen.hddRestore.confirm")
|
||||
@ -131,9 +132,7 @@ void CartActionsScreen::erase(ui::Context &ctx) {
|
||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
|
||||
&(APP->_cartActionsScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_cartEraseWorker, APP->_cartInfoScreen, true, true
|
||||
);
|
||||
APP->_runWorker(&cartEraseWorker, APP->_cartInfoScreen, true, true);
|
||||
},
|
||||
STR("CartActionsScreen.erase.confirm")
|
||||
);
|
||||
@ -153,7 +152,7 @@ void CartActionsScreen::resetSystemID(ui::Context &ctx) {
|
||||
&(APP->_cartActionsScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_cartWriteWorker, APP->_cartInfoScreen, true, true
|
||||
&cartWriteWorker, APP->_cartInfoScreen, true, true
|
||||
);
|
||||
},
|
||||
STR("CartActionsScreen.resetSystemID.confirm")
|
||||
@ -184,7 +183,7 @@ void CartActionsScreen::matchSystemID(ui::Context &ctx) {
|
||||
&(APP->_cartActionsScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_cartWriteWorker, APP->_cartInfoScreen, true, true
|
||||
&cartWriteWorker, APP->_cartInfoScreen, true, true
|
||||
);
|
||||
},
|
||||
STR("CartActionsScreen.matchSystemID.confirm")
|
||||
@ -314,8 +313,7 @@ void ReflashGameScreen::update(ui::Context &ctx) {
|
||||
&(APP->_reflashGameScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_cartReflashWorker, APP->_cartInfoScreen, true,
|
||||
true
|
||||
&cartReflashWorker, APP->_cartInfoScreen, true, true
|
||||
);
|
||||
},
|
||||
STR("CartActionsScreen.reflash.confirm")
|
||||
@ -359,8 +357,7 @@ void SystemIDEntryScreen::update(ui::Context &ctx) {
|
||||
&(APP->_systemIDEntryScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_cartWriteWorker, APP->_cartInfoScreen, true,
|
||||
true
|
||||
&cartWriteWorker, APP->_cartInfoScreen, true, true
|
||||
);
|
||||
},
|
||||
STR("CartActionsScreen.editSystemID.confirm")
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/app/cartunlock.hpp"
|
||||
#include "main/cart/cartdata.hpp"
|
||||
#include "main/workers/cartworkers.hpp"
|
||||
#include "main/uibase.hpp"
|
||||
|
||||
/* Pre-unlock cartridge screens */
|
||||
@ -331,8 +332,7 @@ void UnlockKeyScreen::update(ui::Context &ctx) {
|
||||
&(APP->_unlockKeyScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_cartUnlockWorker, APP->_cartInfoScreen, false,
|
||||
true
|
||||
&cartUnlockWorker, APP->_cartInfoScreen, false, true
|
||||
);
|
||||
},
|
||||
STRH(_UNLOCK_WARNINGS[dump.chipType])
|
||||
@ -381,8 +381,7 @@ void KeyEntryScreen::update(ui::Context &ctx) {
|
||||
&(APP->_keyEntryScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_cartUnlockWorker, APP->_cartInfoScreen, false,
|
||||
true
|
||||
&cartUnlockWorker, APP->_cartInfoScreen, false, true
|
||||
);
|
||||
},
|
||||
STRH(_UNLOCK_WARNINGS[dump.chipType])
|
||||
|
@ -1,436 +0,0 @@
|
||||
/*
|
||||
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
|
||||
*
|
||||
* 573in1 is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* 573in1 is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "common/fs/file.hpp"
|
||||
#include "common/util/hash.hpp"
|
||||
#include "common/util/log.hpp"
|
||||
#include "common/util/templates.hpp"
|
||||
#include "common/defs.hpp"
|
||||
#include "common/ioboard.hpp"
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/cart/cart.hpp"
|
||||
#include "main/cart/cartdata.hpp"
|
||||
#include "main/cart/cartio.hpp"
|
||||
|
||||
static const char *const _CARTDB_PATHS[cart::NUM_CHIP_TYPES]{
|
||||
nullptr,
|
||||
"data/x76f041.db",
|
||||
"data/x76f100.db",
|
||||
"data/zs01.db"
|
||||
};
|
||||
|
||||
bool App::_cartDetectWorker(void) {
|
||||
_workerStatus.setNextScreen(_cartInfoScreen);
|
||||
_workerStatus.update(0, 3, WSTR("App.cartDetectWorker.readCart"));
|
||||
|
||||
_unloadCartData();
|
||||
_qrCodeScreen.valid = false;
|
||||
|
||||
#ifdef ENABLE_DUMMY_CART_DRIVER
|
||||
if (!cart::dummyDriverDump.chipType)
|
||||
_fileIO.resource.loadStruct(cart::dummyDriverDump, "data/test.573");
|
||||
|
||||
if (cart::dummyDriverDump.chipType) {
|
||||
LOG_APP("using dummy cart driver");
|
||||
_cartDriver = new cart::DummyDriver(_cartDump);
|
||||
_cartDriver->readSystemID();
|
||||
} else {
|
||||
_cartDriver = cart::newCartDriver(_cartDump);
|
||||
}
|
||||
#else
|
||||
_cartDriver = cart::newCartDriver(_cartDump);
|
||||
#endif
|
||||
|
||||
if (_cartDump.chipType) {
|
||||
auto error = _cartDriver->readCartID();
|
||||
|
||||
if (error)
|
||||
LOG_APP("SID error [%s]", cart::getErrorString(error));
|
||||
|
||||
error = _cartDriver->readPublicData();
|
||||
|
||||
if (error)
|
||||
LOG_APP("read error [%s]", cart::getErrorString(error));
|
||||
else if (!_cartDump.isReadableDataEmpty())
|
||||
_cartParser = cart::newCartParser(_cartDump);
|
||||
|
||||
_workerStatus.update(1, 3, WSTR("App.cartDetectWorker.identifyGame"));
|
||||
|
||||
if (!_cartDB.ptr) {
|
||||
if (!_fileIO.resource.loadData(
|
||||
_cartDB, _CARTDB_PATHS[_cartDump.chipType])
|
||||
) {
|
||||
LOG_APP("%s not found", _CARTDB_PATHS[_cartDump.chipType]);
|
||||
goto _cartInitDone;
|
||||
}
|
||||
}
|
||||
|
||||
char code[8], region[8];
|
||||
|
||||
if (!_cartParser)
|
||||
goto _cartInitDone;
|
||||
if (_cartParser->getCode(code) && _cartParser->getRegion(region))
|
||||
_identified = _cartDB.lookup(code, region);
|
||||
if (!_identified)
|
||||
goto _cartInitDone;
|
||||
|
||||
// Force the parser to use correct format for the game (to prevent
|
||||
// ambiguity between different formats).
|
||||
delete _cartParser;
|
||||
_cartParser = cart::newCartParser(
|
||||
_cartDump, _identified->formatType, _identified->flags
|
||||
);
|
||||
}
|
||||
|
||||
_cartInitDone:
|
||||
_workerStatus.update(2, 3, WSTR("App.cartDetectWorker.readDigitalIO"));
|
||||
|
||||
if (
|
||||
#ifdef ENABLE_DUMMY_CART_DRIVER
|
||||
!(_cartDump.flags & cart::DUMP_SYSTEM_ID_OK) &&
|
||||
#endif
|
||||
io::isDigitalIOPresent()
|
||||
) {
|
||||
util::Data bitstream;
|
||||
|
||||
if (!_fileIO.resource.loadData(bitstream, "data/fpga.bit"))
|
||||
return true;
|
||||
|
||||
bool ready = io::loadDigitalIOBitstream(
|
||||
bitstream.as<uint8_t>(), bitstream.length
|
||||
);
|
||||
bitstream.destroy();
|
||||
|
||||
if (!ready)
|
||||
return true;
|
||||
|
||||
io::initDigitalIOFPGA();
|
||||
auto error = _cartDriver->readSystemID();
|
||||
|
||||
if (error)
|
||||
LOG_APP("XID error [%s]", cart::getErrorString(error));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const util::Hash _UNLOCK_ERRORS[cart::NUM_CHIP_TYPES]{
|
||||
0,
|
||||
"App.cartUnlockWorker.x76f041Error"_h,
|
||||
"App.cartUnlockWorker.x76f100Error"_h,
|
||||
"App.cartUnlockWorker.zs01Error"_h
|
||||
};
|
||||
|
||||
bool App::_cartUnlockWorker(void) {
|
||||
_workerStatus.setNextScreen(_cartInfoScreen, true);
|
||||
_workerStatus.update(0, 2, WSTR("App.cartUnlockWorker.read"));
|
||||
|
||||
_qrCodeScreen.valid = false;
|
||||
|
||||
auto error = _cartDriver->readPrivateData();
|
||||
|
||||
if (error) {
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTRH(_UNLOCK_ERRORS[_cartDump.chipType]),
|
||||
cart::getErrorString(error)
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_cartParser)
|
||||
delete _cartParser;
|
||||
|
||||
_cartParser = cart::newCartParser(_cartDump);
|
||||
|
||||
if (!_cartParser)
|
||||
return true;
|
||||
|
||||
_workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identifyGame"));
|
||||
|
||||
char code[8], region[8];
|
||||
|
||||
if (_cartParser->getCode(code) && _cartParser->getRegion(region))
|
||||
_identified = _cartDB.lookup(code, region);
|
||||
|
||||
// If auto-identification failed (e.g. because the format has no game code),
|
||||
// use the game whose unlocking key was selected as a hint.
|
||||
if (!_identified) {
|
||||
if (_selectedEntry) {
|
||||
LOG_APP("identify failed, using key as hint");
|
||||
_identified = _selectedEntry;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
delete _cartParser;
|
||||
_cartParser = cart::newCartParser(
|
||||
_cartDump, _identified->formatType, _identified->flags
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App::_qrCodeWorker(void) {
|
||||
char qrString[cart::MAX_QR_STRING_LENGTH];
|
||||
|
||||
_workerStatus.update(0, 2, WSTR("App.qrCodeWorker.compress"));
|
||||
_cartDump.toQRString(qrString);
|
||||
|
||||
_workerStatus.update(1, 2, WSTR("App.qrCodeWorker.generate"));
|
||||
_qrCodeScreen.generateCode(qrString);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App::_cartDumpWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.cartDumpWorker.save"));
|
||||
|
||||
char path[fs::MAX_PATH_LENGTH], code[8], region[8];
|
||||
size_t length = _cartDump.getDumpLength();
|
||||
|
||||
if (!_createDataDirectory())
|
||||
goto _error;
|
||||
|
||||
if (
|
||||
_identified && _cartParser->getCode(code) &&
|
||||
_cartParser->getRegion(region)
|
||||
) {
|
||||
snprintf(
|
||||
path, sizeof(path), EXTERNAL_DATA_DIR "/%s%s.573", code, region
|
||||
);
|
||||
} else {
|
||||
if (!_getNumberedPath(
|
||||
path, sizeof(path), EXTERNAL_DATA_DIR "/cart%04d.573"
|
||||
))
|
||||
goto _error;
|
||||
}
|
||||
|
||||
LOG_APP("saving %s, length=%d", path, length);
|
||||
|
||||
if (_fileIO.vfs.saveData(&_cartDump, length, path) != length)
|
||||
goto _error;
|
||||
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_SUCCESS, WSTR("App.cartDumpWorker.success"), path
|
||||
);
|
||||
return true;
|
||||
|
||||
_error:
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartDumpWorker.error"), path
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool App::_cartWriteWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.cartWriteWorker.write"));
|
||||
|
||||
uint8_t key[8];
|
||||
auto error = _cartDriver->writeData();
|
||||
|
||||
if (!error)
|
||||
_identified->copyKeyTo(key);
|
||||
|
||||
_cartDetectWorker();
|
||||
|
||||
if (error) {
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartWriteWorker.error"),
|
||||
cart::getErrorString(error)
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
_cartDump.copyKeyFrom(key);
|
||||
return _cartUnlockWorker();
|
||||
}
|
||||
|
||||
bool App::_cartRestoreWorker(void) {
|
||||
_workerStatus.update(0, 3, WSTR("App.cartRestoreWorker.init"));
|
||||
|
||||
const char *path = _fileBrowserScreen.selectedPath;
|
||||
auto file = _fileIO.vfs.openFile(path, fs::READ);
|
||||
|
||||
cart::CartDump newDump;
|
||||
|
||||
if (file) {
|
||||
auto length = file->read(&newDump, sizeof(newDump));
|
||||
|
||||
file->close();
|
||||
delete file;
|
||||
|
||||
if (length < (sizeof(newDump) - sizeof(newDump.data)))
|
||||
goto _fileError;
|
||||
if (!newDump.validateMagic())
|
||||
goto _fileError;
|
||||
if (length != newDump.getDumpLength())
|
||||
goto _fileError;
|
||||
}
|
||||
|
||||
if (_cartDump.chipType != newDump.chipType) {
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.typeError"), path
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
_workerStatus.update(1, 3, WSTR("App.cartRestoreWorker.setDataKey"));
|
||||
auto error = _cartDriver->setDataKey(newDump.dataKey);
|
||||
|
||||
if (error) {
|
||||
LOG_APP("key error [%s]", cart::getErrorString(error));
|
||||
} else {
|
||||
if (newDump.flags & (
|
||||
cart::DUMP_PUBLIC_DATA_OK | cart::DUMP_PRIVATE_DATA_OK
|
||||
))
|
||||
_cartDump.copyDataFrom(newDump.data);
|
||||
if (newDump.flags & cart::DUMP_CONFIG_OK)
|
||||
_cartDump.copyConfigFrom(newDump.config);
|
||||
|
||||
_workerStatus.update(2, 3, WSTR("App.cartRestoreWorker.write"));
|
||||
error = _cartDriver->writeData();
|
||||
}
|
||||
|
||||
_cartDetectWorker();
|
||||
|
||||
if (error) {
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.writeError"),
|
||||
cart::getErrorString(error)
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return _cartUnlockWorker();
|
||||
|
||||
_fileError:
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.fileError"), path
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool App::_cartReflashWorker(void) {
|
||||
// Make sure a valid cart ID is present if required by the new data.
|
||||
if (
|
||||
_selectedEntry->requiresCartID() &&
|
||||
!(_cartDump.flags & cart::DUMP_CART_ID_OK)
|
||||
) {
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartReflashWorker.idError")
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
_workerStatus.update(0, 3, WSTR("App.cartReflashWorker.init"));
|
||||
|
||||
// TODO: preserve 0x81 traceid if possible
|
||||
#if 0
|
||||
uint8_t traceID[8];
|
||||
_cartParser->getIdentifiers()->traceID.copyTo(traceID);
|
||||
#endif
|
||||
|
||||
if (!_cartEraseWorker())
|
||||
return false;
|
||||
if (_cartParser)
|
||||
delete _cartParser;
|
||||
|
||||
_cartParser = cart::newCartParser(
|
||||
_cartDump, _selectedEntry->formatType, _selectedEntry->flags
|
||||
);
|
||||
auto pri = _cartParser->getIdentifiers();
|
||||
auto pub = _cartParser->getPublicIdentifiers();
|
||||
|
||||
util::clear(_cartDump.data);
|
||||
_cartDump.initConfig(
|
||||
9, _selectedEntry->flags & cart::DATA_HAS_PUBLIC_SECTION
|
||||
);
|
||||
|
||||
if (pri) {
|
||||
if (_selectedEntry->flags & cart::DATA_HAS_CART_ID)
|
||||
pri->cartID.copyFrom(_cartDump.cartID.data);
|
||||
if (_selectedEntry->flags & cart::DATA_HAS_TRACE_ID)
|
||||
pri->updateTraceID(
|
||||
_selectedEntry->traceIDType, _selectedEntry->traceIDParam,
|
||||
&_cartDump.cartID
|
||||
);
|
||||
if (_selectedEntry->flags & cart::DATA_HAS_INSTALL_ID) {
|
||||
// The private installation ID seems to be unused on carts with a
|
||||
// public data section.
|
||||
if (pub)
|
||||
pub->setInstallID(_selectedEntry->installIDPrefix);
|
||||
else
|
||||
pri->setInstallID(_selectedEntry->installIDPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
_cartParser->setCode(_selectedEntry->code);
|
||||
_cartParser->setRegion(_selectedEntry->region);
|
||||
_cartParser->setYear(_selectedEntry->year);
|
||||
_cartParser->flush();
|
||||
|
||||
_workerStatus.update(1, 3, WSTR("App.cartReflashWorker.setDataKey"));
|
||||
auto error = _cartDriver->setDataKey(_selectedEntry->dataKey);
|
||||
|
||||
if (error) {
|
||||
LOG_APP("key error [%s]", cart::getErrorString(error));
|
||||
} else {
|
||||
_workerStatus.update(2, 3, WSTR("App.cartReflashWorker.write"));
|
||||
error = _cartDriver->writeData();
|
||||
}
|
||||
|
||||
_cartDetectWorker();
|
||||
|
||||
if (error) {
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartReflashWorker.writeError"),
|
||||
cart::getErrorString(error)
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
return _cartUnlockWorker();
|
||||
}
|
||||
|
||||
bool App::_cartEraseWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.cartEraseWorker.erase"));
|
||||
|
||||
auto error = _cartDriver->erase();
|
||||
_cartDetectWorker();
|
||||
|
||||
if (error) {
|
||||
_messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartEraseWorker.error"),
|
||||
cart::getErrorString(error)
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
return _cartUnlockWorker();
|
||||
}
|
@ -19,12 +19,14 @@
|
||||
#include "common/util/templates.hpp"
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/app/main.hpp"
|
||||
#include "main/workers/cartworkers.hpp"
|
||||
#include "main/workers/miscworkers.hpp"
|
||||
#include "main/uibase.hpp"
|
||||
|
||||
/* Main menu screens */
|
||||
|
||||
static constexpr int _WARNING_COOLDOWN = 10;
|
||||
static constexpr int _AUTOBOOT_DELAY = 5;
|
||||
static constexpr int _AUTOBOOT_DELAY = 5;
|
||||
|
||||
void WarningScreen::show(ui::Context &ctx, bool goBack) {
|
||||
_title = STR("WarningScreen.title");
|
||||
@ -63,13 +65,7 @@ void WarningScreen::update(ui::Context &ctx) {
|
||||
_buttons[0] = STR("WarningScreen.ok");
|
||||
|
||||
if (ctx.buttons.pressed(ui::BTN_START))
|
||||
#ifdef ENABLE_AUTOBOOT
|
||||
ctx.show(APP->_buttonMappingScreen, false, true);
|
||||
#else
|
||||
APP->_runWorker(
|
||||
&App::_ideInitWorker, APP->_buttonMappingScreen, false, true
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AutobootScreen::show(ui::Context &ctx, bool goBack) {
|
||||
@ -102,7 +98,7 @@ void AutobootScreen::update(ui::Context &ctx) {
|
||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
|
||||
&(APP->_warningScreen);
|
||||
|
||||
APP->_runWorker(&App::_executableWorker, APP->_mainMenuScreen, true);
|
||||
APP->_runWorker(&executableWorker, APP->_mainMenuScreen, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -214,9 +210,7 @@ void MainMenuScreen::cartInfo(ui::Context &ctx) {
|
||||
if (APP->_cartDriver)
|
||||
ctx.show(APP->_cartInfoScreen, false, true);
|
||||
else
|
||||
APP->_runWorker(
|
||||
&App::_cartDetectWorker, APP->_cartInfoScreen, false, true
|
||||
);
|
||||
APP->_runWorker(&cartDetectWorker, APP->_cartInfoScreen, false, true);
|
||||
}
|
||||
|
||||
void MainMenuScreen::storageInfo(ui::Context &ctx) {
|
||||
@ -236,7 +230,7 @@ void MainMenuScreen::runExecutable(ui::Context &ctx) {
|
||||
&(APP->_fileBrowserScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_executableWorker, APP->_mainMenuScreen, true, true
|
||||
&executableWorker, APP->_mainMenuScreen, true, true
|
||||
);
|
||||
},
|
||||
STR("MainMenuScreen.runExecutable.filePrompt")
|
||||
@ -269,11 +263,11 @@ void MainMenuScreen::ejectCD(ui::Context &ctx) {
|
||||
APP->_messageScreen.previousScreens[MESSAGE_SUCCESS] = this;
|
||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
|
||||
|
||||
APP->_runWorker(&App::_atapiEjectWorker, *this, true, true);
|
||||
APP->_runWorker(&atapiEjectWorker, *this, true, true);
|
||||
}
|
||||
|
||||
void MainMenuScreen::reboot(ui::Context &ctx) {
|
||||
APP->_runWorker(&App::_rebootWorker, *this, true, true);
|
||||
APP->_runWorker(&rebootWorker, *this, true, true);
|
||||
}
|
||||
|
||||
void MainMenuScreen::show(ui::Context &ctx, bool goBack) {
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "common/defs.hpp"
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/app/modals.hpp"
|
||||
#include "main/workers/miscworkers.hpp"
|
||||
#include "main/uibase.hpp"
|
||||
#include "main/uicommon.hpp"
|
||||
|
||||
@ -206,7 +207,7 @@ void FilePickerScreen::reloadAndShow(ui::Context &ctx) {
|
||||
|
||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
|
||||
|
||||
APP->_runWorker(&App::_fileInitWorker, *this, false, true);
|
||||
APP->_runWorker(&fileInitWorker, *this, false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "common/rom.hpp"
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/app/romactions.hpp"
|
||||
#include "main/workers/miscworkers.hpp"
|
||||
#include "main/workers/romworkers.hpp"
|
||||
#include "main/uibase.hpp"
|
||||
#include "main/uicommon.hpp"
|
||||
|
||||
@ -266,7 +268,7 @@ const char *StorageActionsScreen::_getItemName(
|
||||
|
||||
void StorageActionsScreen::runExecutable(ui::Context &ctx, size_t length) {
|
||||
if (selectedRegion->getBootExecutableHeader()) {
|
||||
APP->_runWorker(&App::_executableWorker, *this, true, true);
|
||||
APP->_runWorker(&executableWorker, *this, true, true);
|
||||
} else {
|
||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
|
||||
APP->_messageScreen.setMessage(
|
||||
@ -282,7 +284,7 @@ void StorageActionsScreen::checksum(ui::Context &ctx, size_t length) {
|
||||
ctx.show(APP->_checksumScreen, false, true);
|
||||
else
|
||||
APP->_runWorker(
|
||||
&App::_romChecksumWorker, APP->_checksumScreen, false, true
|
||||
&romChecksumWorker, APP->_checksumScreen, false, true
|
||||
);
|
||||
}
|
||||
|
||||
@ -296,7 +298,7 @@ void StorageActionsScreen::dump(ui::Context &ctx, size_t length) {
|
||||
&(APP->_storageActionsScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_romDumpWorker, APP->_messageScreen, false, true
|
||||
&romDumpWorker, APP->_messageScreen, false, true
|
||||
);
|
||||
},
|
||||
STR("StorageActionsScreen.dump.confirm")
|
||||
@ -325,7 +327,7 @@ void StorageActionsScreen::restore(ui::Context &ctx, size_t length) {
|
||||
&(APP->_fileBrowserScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_romRestoreWorker, APP->_messageScreen, false, true
|
||||
&romRestoreWorker, APP->_messageScreen, false, true
|
||||
);
|
||||
},
|
||||
STR("StorageActionsScreen.restore.confirm")
|
||||
@ -345,9 +347,7 @@ void StorageActionsScreen::erase(ui::Context &ctx, size_t length) {
|
||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
|
||||
&(APP->_storageActionsScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_romEraseWorker, APP->_messageScreen, false, true
|
||||
);
|
||||
APP->_runWorker(&romEraseWorker, APP->_messageScreen, false, true);
|
||||
},
|
||||
STR("StorageActionsScreen.erase.confirm")
|
||||
);
|
||||
@ -375,8 +375,7 @@ void StorageActionsScreen::installExecutable(ui::Context &ctx, size_t length) {
|
||||
&(APP->_fileBrowserScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_flashExecutableWriteWorker, APP->_messageScreen, false,
|
||||
true
|
||||
&flashExecutableWriteWorker, APP->_messageScreen, false, true
|
||||
);
|
||||
},
|
||||
STR("StorageActionsScreen.installExecutable.confirm")
|
||||
@ -395,8 +394,7 @@ void StorageActionsScreen::resetFlashHeader(ui::Context &ctx, size_t length) {
|
||||
&(APP->_storageActionsScreen);
|
||||
|
||||
APP->_runWorker(
|
||||
&App::_flashHeaderWriteWorker, APP->_storageInfoScreen, true,
|
||||
true
|
||||
&flashHeaderWriteWorker, APP->_storageInfoScreen, true, true
|
||||
);
|
||||
},
|
||||
STR("StorageActionsScreen.resetFlashHeader.confirm")
|
||||
|
152
src/main/app/threads.cpp
Normal file
152
src/main/app/threads.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
|
||||
*
|
||||
* 573in1 is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* 573in1 is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include "common/fs/file.hpp"
|
||||
#include "common/util/log.hpp"
|
||||
#include "common/util/misc.hpp"
|
||||
#include "common/spu.hpp"
|
||||
#include "ps1/system.h"
|
||||
#include "main/app/threads.hpp"
|
||||
|
||||
/* Audio stream thread */
|
||||
|
||||
static constexpr size_t _STREAM_THREAD_STACK_SIZE = 0x2000;
|
||||
|
||||
static constexpr size_t _STREAM_BUFFERED_CHUNKS = 8;
|
||||
static constexpr size_t _STREAM_MIN_FEED_CHUNKS = 4;
|
||||
|
||||
void _streamMain(void *arg0, void *arg1) {
|
||||
auto obj = reinterpret_cast<AudioStreamManager *>(arg0);
|
||||
|
||||
if (obj->_status != AUDIO_STREAM_IDLE) {
|
||||
auto &stream = obj->_stream;
|
||||
auto &buffer = obj->_buffer;
|
||||
|
||||
auto chunkLength = stream.getChunkLength();
|
||||
|
||||
for (;;) {
|
||||
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
||||
|
||||
if (obj->_status == AUDIO_STREAM_STOP) {
|
||||
stream.stop();
|
||||
break;
|
||||
}
|
||||
|
||||
// Keep yielding to the worker thread until the stream's FIFO has
|
||||
// enough space for the new chunks.
|
||||
auto numChunks = stream.getFreeChunkCount();
|
||||
|
||||
if (numChunks < _STREAM_MIN_FEED_CHUNKS) {
|
||||
switchThreadImmediate(obj->_yieldTo);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto length = obj->_file->read(buffer.ptr, chunkLength * numChunks);
|
||||
|
||||
if (length >= chunkLength) {
|
||||
stream.feed(buffer.ptr, length);
|
||||
|
||||
if (!stream.isPlaying())
|
||||
stream.start();
|
||||
} else if (obj->_status == AUDIO_STREAM_LOOP) {
|
||||
obj->_file->seek(spu::INTERLEAVED_VAG_BODY_OFFSET);
|
||||
} else {
|
||||
// Wait for any leftover data in the FIFO to finish playing,
|
||||
// then stop playback.
|
||||
while (!stream.isUnderrun())
|
||||
switchThreadImmediate(obj->_yieldTo);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do nothing and yield until the stream is restarted.
|
||||
obj->_status = AUDIO_STREAM_IDLE;
|
||||
flushWriteQueue();
|
||||
|
||||
for (;;)
|
||||
switchThreadImmediate(obj->_yieldTo);
|
||||
}
|
||||
|
||||
void AudioStreamManager::_closeFile(void) {
|
||||
if (!_file)
|
||||
return;
|
||||
|
||||
_file->close();
|
||||
delete _file;
|
||||
|
||||
_buffer.destroy();
|
||||
_file = nullptr;
|
||||
}
|
||||
|
||||
void AudioStreamManager::_startThread(
|
||||
fs::File *file, AudioStreamStatus status
|
||||
) {
|
||||
util::CriticalSection sec;
|
||||
|
||||
_file = file;
|
||||
_status = status;
|
||||
|
||||
_stack.allocate(_STREAM_THREAD_STACK_SIZE);
|
||||
assert(_stack.ptr);
|
||||
|
||||
auto stackBottom = _stack.as<uint8_t>();
|
||||
|
||||
initThread(
|
||||
&_thread, &_streamMain, this, nullptr,
|
||||
&stackBottom[(_STREAM_THREAD_STACK_SIZE - 1) & ~7]
|
||||
);
|
||||
}
|
||||
|
||||
bool AudioStreamManager::play(fs::File *file, bool loop) {
|
||||
if (isActive())
|
||||
stop();
|
||||
|
||||
spu::VAGHeader header;
|
||||
|
||||
if (file->read(&header, sizeof(header)) < sizeof(header))
|
||||
return false;
|
||||
if (
|
||||
file->seek(spu::INTERLEAVED_VAG_BODY_OFFSET)
|
||||
!= spu::INTERLEAVED_VAG_BODY_OFFSET
|
||||
)
|
||||
return false;
|
||||
|
||||
auto bufferLength = _stream.getChunkLength() * _STREAM_MIN_FEED_CHUNKS;
|
||||
|
||||
if (!_stream.initFromVAGHeader(header, 0x60000, _STREAM_BUFFERED_CHUNKS))
|
||||
return false;
|
||||
if (!_buffer.allocate(bufferLength))
|
||||
return false;
|
||||
|
||||
_startThread(file, loop ? AUDIO_STREAM_LOOP : AUDIO_STREAM_PLAY_ONCE);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioStreamManager::stop(void) {
|
||||
if (!isActive())
|
||||
return;
|
||||
|
||||
_status = AUDIO_STREAM_STOP;
|
||||
flushWriteQueue();
|
||||
|
||||
while (isActive())
|
||||
yield();
|
||||
|
||||
_closeFile();
|
||||
}
|
74
src/main/app/threads.hpp
Normal file
74
src/main/app/threads.hpp
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
|
||||
*
|
||||
* 573in1 is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* 573in1 is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/fs/file.hpp"
|
||||
#include "common/util/templates.hpp"
|
||||
#include "common/spu.hpp"
|
||||
#include "ps1/system.h"
|
||||
|
||||
/* Audio stream thread */
|
||||
|
||||
enum AudioStreamStatus {
|
||||
AUDIO_STREAM_IDLE = 0,
|
||||
AUDIO_STREAM_STOP = 1,
|
||||
AUDIO_STREAM_PLAY_ONCE = 2,
|
||||
AUDIO_STREAM_LOOP = 3
|
||||
};
|
||||
|
||||
class AudioStreamManager {
|
||||
friend void _streamMain(void *arg0, void *arg1);
|
||||
|
||||
private:
|
||||
AudioStreamStatus _status;
|
||||
|
||||
fs::File *_file;
|
||||
Thread *_yieldTo;
|
||||
|
||||
Thread _thread;
|
||||
util::Data _stack;
|
||||
spu::Stream _stream;
|
||||
util::Data _buffer;
|
||||
|
||||
void _closeFile(void);
|
||||
void _startThread(fs::File *file, AudioStreamStatus status);
|
||||
|
||||
public:
|
||||
inline AudioStreamManager(void)
|
||||
: _status(AUDIO_STREAM_IDLE), _file(nullptr), _yieldTo(nullptr) {}
|
||||
|
||||
inline void init(Thread *yieldTo) {
|
||||
_yieldTo = yieldTo;
|
||||
|
||||
_startThread(nullptr, AUDIO_STREAM_IDLE);
|
||||
}
|
||||
inline bool isActive(void) const {
|
||||
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
||||
|
||||
return (_status != AUDIO_STREAM_IDLE);
|
||||
}
|
||||
|
||||
inline void yield(void) {
|
||||
switchThreadImmediate(&_thread);
|
||||
}
|
||||
inline void handleInterrupt(void) {
|
||||
_stream.handleInterrupt();
|
||||
}
|
||||
|
||||
bool play(fs::File *file, bool loop = false);
|
||||
void stop(void);
|
||||
};
|
@ -184,7 +184,6 @@ public:
|
||||
spu::Sound sounds[NUM_UI_SOUNDS];
|
||||
|
||||
ButtonState buttons;
|
||||
spu::Stream audioStream;
|
||||
|
||||
int time;
|
||||
void *screenData; // Opaque, can be accessed by screens
|
||||
|
438
src/main/workers/cartworkers.cpp
Normal file
438
src/main/workers/cartworkers.cpp
Normal file
@ -0,0 +1,438 @@
|
||||
/*
|
||||
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
|
||||
*
|
||||
* 573in1 is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* 573in1 is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include "common/fs/file.hpp"
|
||||
#include "common/util/hash.hpp"
|
||||
#include "common/util/log.hpp"
|
||||
#include "common/util/templates.hpp"
|
||||
#include "common/defs.hpp"
|
||||
#include "common/ioboard.hpp"
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/cart/cart.hpp"
|
||||
#include "main/cart/cartdata.hpp"
|
||||
#include "main/cart/cartio.hpp"
|
||||
#include "main/workers/cartworkers.hpp"
|
||||
|
||||
static const char *const _CARTDB_PATHS[cart::NUM_CHIP_TYPES]{
|
||||
nullptr,
|
||||
"data/x76f041.db",
|
||||
"data/x76f100.db",
|
||||
"data/zs01.db"
|
||||
};
|
||||
|
||||
bool cartDetectWorker(App &app) {
|
||||
app._workerStatus.setNextScreen(app._cartInfoScreen);
|
||||
app._workerStatus.update(0, 3, WSTR("App.cartDetectWorker.readCart"));
|
||||
|
||||
app._unloadCartData();
|
||||
app._qrCodeScreen.valid = false;
|
||||
|
||||
#ifdef ENABLE_DUMMY_CART_DRIVER
|
||||
if (!cart::dummyDriverDump.chipType)
|
||||
app._fileIO.resource.loadStruct(cart::dummyDriverDump, "data/test.573");
|
||||
|
||||
if (cart::dummyDriverDump.chipType) {
|
||||
LOG_APP("using dummy cart driver");
|
||||
app._cartDriver = new cart::DummyDriver(app._cartDump);
|
||||
app._cartDriver->readSystemID();
|
||||
} else {
|
||||
app._cartDriver = cart::newCartDriver(app._cartDump);
|
||||
}
|
||||
#else
|
||||
_cartDriver = cart::newCartDriver(_cartDump);
|
||||
#endif
|
||||
|
||||
if (app._cartDump.chipType) {
|
||||
auto error = app._cartDriver->readCartID();
|
||||
|
||||
if (error)
|
||||
LOG_APP("SID error [%s]", cart::getErrorString(error));
|
||||
|
||||
error = app._cartDriver->readPublicData();
|
||||
|
||||
if (error)
|
||||
LOG_APP("read error [%s]", cart::getErrorString(error));
|
||||
else if (!app._cartDump.isReadableDataEmpty())
|
||||
app._cartParser = cart::newCartParser(app._cartDump);
|
||||
|
||||
app._workerStatus.update(1, 3, WSTR("App.cartDetectWorker.identifyGame"));
|
||||
|
||||
if (!app._cartDB.ptr) {
|
||||
if (!app._fileIO.resource.loadData(
|
||||
app._cartDB, _CARTDB_PATHS[app._cartDump.chipType])
|
||||
) {
|
||||
LOG_APP("%s not found", _CARTDB_PATHS[app._cartDump.chipType]);
|
||||
goto _cartInitDone;
|
||||
}
|
||||
}
|
||||
|
||||
char code[8], region[8];
|
||||
|
||||
if (!app._cartParser)
|
||||
goto _cartInitDone;
|
||||
if (app._cartParser->getCode(code) && app._cartParser->getRegion(region))
|
||||
app._identified = app._cartDB.lookup(code, region);
|
||||
if (!app._identified)
|
||||
goto _cartInitDone;
|
||||
|
||||
// Force the parser to use correct format for the game (to prevent
|
||||
// ambiguity between different formats).
|
||||
delete app._cartParser;
|
||||
app._cartParser = cart::newCartParser(
|
||||
app._cartDump, app._identified->formatType, app._identified->flags
|
||||
);
|
||||
}
|
||||
|
||||
_cartInitDone:
|
||||
app._workerStatus.update(2, 3, WSTR("App.cartDetectWorker.readDigitalIO"));
|
||||
|
||||
if (
|
||||
#ifdef ENABLE_DUMMY_CART_DRIVER
|
||||
!(app._cartDump.flags & cart::DUMP_SYSTEM_ID_OK) &&
|
||||
#endif
|
||||
io::isDigitalIOPresent()
|
||||
) {
|
||||
util::Data bitstream;
|
||||
|
||||
if (!app._fileIO.resource.loadData(bitstream, "data/fpga.bit"))
|
||||
return true;
|
||||
|
||||
bool ready = io::loadDigitalIOBitstream(
|
||||
bitstream.as<uint8_t>(), bitstream.length
|
||||
);
|
||||
bitstream.destroy();
|
||||
|
||||
if (!ready)
|
||||
return true;
|
||||
|
||||
io::initDigitalIOFPGA();
|
||||
auto error = app._cartDriver->readSystemID();
|
||||
|
||||
if (error)
|
||||
LOG_APP("XID error [%s]", cart::getErrorString(error));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const util::Hash _UNLOCK_ERRORS[cart::NUM_CHIP_TYPES]{
|
||||
0,
|
||||
"App.cartUnlockWorker.x76f041Error"_h,
|
||||
"App.cartUnlockWorker.x76f100Error"_h,
|
||||
"App.cartUnlockWorker.zs01Error"_h
|
||||
};
|
||||
|
||||
bool cartUnlockWorker(App &app) {
|
||||
app._workerStatus.setNextScreen(app._cartInfoScreen, true);
|
||||
app._workerStatus.update(0, 2, WSTR("App.cartUnlockWorker.read"));
|
||||
|
||||
app._qrCodeScreen.valid = false;
|
||||
|
||||
auto error = app._cartDriver->readPrivateData();
|
||||
|
||||
if (error) {
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTRH(_UNLOCK_ERRORS[app._cartDump.chipType]),
|
||||
cart::getErrorString(error)
|
||||
);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (app._cartParser)
|
||||
delete app._cartParser;
|
||||
|
||||
app._cartParser = cart::newCartParser(app._cartDump);
|
||||
|
||||
if (!app._cartParser)
|
||||
return true;
|
||||
|
||||
app._workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identifyGame"));
|
||||
|
||||
char code[8], region[8];
|
||||
|
||||
if (app._cartParser->getCode(code) && app._cartParser->getRegion(region))
|
||||
app._identified = app._cartDB.lookup(code, region);
|
||||
|
||||
// If auto-identification failed (e.g. because the format has no game code),
|
||||
// use the game whose unlocking key was selected as a hint.
|
||||
if (!app._identified) {
|
||||
if (app._selectedEntry) {
|
||||
LOG_APP("identify failed, using key as hint");
|
||||
app._identified = app._selectedEntry;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
delete app._cartParser;
|
||||
app._cartParser = cart::newCartParser(
|
||||
app._cartDump, app._identified->formatType, app._identified->flags
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qrCodeWorker(App &app) {
|
||||
char qrString[cart::MAX_QR_STRING_LENGTH];
|
||||
|
||||
app._workerStatus.update(0, 2, WSTR("App.qrCodeWorker.compress"));
|
||||
app._cartDump.toQRString(qrString);
|
||||
|
||||
app._workerStatus.update(1, 2, WSTR("App.qrCodeWorker.generate"));
|
||||
app._qrCodeScreen.generateCode(qrString);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cartDumpWorker(App &app) {
|
||||
app._workerStatus.update(0, 1, WSTR("App.cartDumpWorker.save"));
|
||||
|
||||
char path[fs::MAX_PATH_LENGTH], code[8], region[8];
|
||||
size_t length = app._cartDump.getDumpLength();
|
||||
|
||||
if (!app._createDataDirectory())
|
||||
goto _error;
|
||||
|
||||
if (
|
||||
app._identified && app._cartParser->getCode(code) &&
|
||||
app._cartParser->getRegion(region)
|
||||
) {
|
||||
snprintf(
|
||||
path, sizeof(path), EXTERNAL_DATA_DIR "/%s%s.573", code, region
|
||||
);
|
||||
} else {
|
||||
if (!app._getNumberedPath(
|
||||
path, sizeof(path), EXTERNAL_DATA_DIR "/cart%04d.573"
|
||||
))
|
||||
goto _error;
|
||||
}
|
||||
|
||||
LOG_APP("saving %s, length=%d", path, length);
|
||||
|
||||
if (app._fileIO.vfs.saveData(&app._cartDump, length, path) != length)
|
||||
goto _error;
|
||||
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_SUCCESS, WSTR("App.cartDumpWorker.success"), path
|
||||
);
|
||||
return true;
|
||||
|
||||
_error:
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartDumpWorker.error"), path
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cartWriteWorker(App &app) {
|
||||
app._workerStatus.update(0, 1, WSTR("App.cartWriteWorker.write"));
|
||||
|
||||
uint8_t key[8];
|
||||
auto error = app._cartDriver->writeData();
|
||||
|
||||
if (!error)
|
||||
app._identified->copyKeyTo(key);
|
||||
|
||||
cartDetectWorker(app);
|
||||
|
||||
if (error) {
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartWriteWorker.error"),
|
||||
cart::getErrorString(error)
|
||||
);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
app._cartDump.copyKeyFrom(key);
|
||||
return cartUnlockWorker(app);
|
||||
}
|
||||
|
||||
bool cartRestoreWorker(App &app) {
|
||||
app._workerStatus.update(0, 3, WSTR("App.cartRestoreWorker.init"));
|
||||
|
||||
const char *path = app._fileBrowserScreen.selectedPath;
|
||||
auto file = app._fileIO.vfs.openFile(path, fs::READ);
|
||||
|
||||
cart::CartDump newDump;
|
||||
|
||||
if (file) {
|
||||
auto length = file->read(&newDump, sizeof(newDump));
|
||||
|
||||
file->close();
|
||||
delete file;
|
||||
|
||||
if (length < (sizeof(newDump) - sizeof(newDump.data)))
|
||||
goto _fileError;
|
||||
if (!newDump.validateMagic())
|
||||
goto _fileError;
|
||||
if (length != newDump.getDumpLength())
|
||||
goto _fileError;
|
||||
}
|
||||
|
||||
if (app._cartDump.chipType != newDump.chipType) {
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.typeError"), path
|
||||
);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
app._workerStatus.update(1, 3, WSTR("App.cartRestoreWorker.setDataKey"));
|
||||
auto error = app._cartDriver->setDataKey(newDump.dataKey);
|
||||
|
||||
if (error) {
|
||||
LOG_APP("key error [%s]", cart::getErrorString(error));
|
||||
} else {
|
||||
if (newDump.flags & (
|
||||
cart::DUMP_PUBLIC_DATA_OK | cart::DUMP_PRIVATE_DATA_OK
|
||||
))
|
||||
app._cartDump.copyDataFrom(newDump.data);
|
||||
if (newDump.flags & cart::DUMP_CONFIG_OK)
|
||||
app._cartDump.copyConfigFrom(newDump.config);
|
||||
|
||||
app._workerStatus.update(2, 3, WSTR("App.cartRestoreWorker.write"));
|
||||
error = app._cartDriver->writeData();
|
||||
}
|
||||
|
||||
cartDetectWorker(app);
|
||||
|
||||
if (error) {
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.writeError"),
|
||||
cart::getErrorString(error)
|
||||
);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return cartUnlockWorker(app);
|
||||
|
||||
_fileError:
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartRestoreWorker.fileError"), path
|
||||
);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cartReflashWorker(App &app) {
|
||||
// Make sure a valid cart ID is present if required by the new data.
|
||||
if (
|
||||
app._selectedEntry->requiresCartID() &&
|
||||
!(app._cartDump.flags & cart::DUMP_CART_ID_OK)
|
||||
) {
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartReflashWorker.idError")
|
||||
);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
app._workerStatus.update(0, 3, WSTR("App.cartReflashWorker.init"));
|
||||
|
||||
// TODO: preserve 0x81 traceid if possible
|
||||
#if 0
|
||||
uint8_t traceID[8];
|
||||
_cartParser->getIdentifiers()->traceID.copyTo(traceID);
|
||||
#endif
|
||||
|
||||
if (!cartEraseWorker(app))
|
||||
return false;
|
||||
if (app._cartParser)
|
||||
delete app._cartParser;
|
||||
|
||||
app._cartParser = cart::newCartParser(
|
||||
app._cartDump, app._selectedEntry->formatType, app._selectedEntry->flags
|
||||
);
|
||||
auto pri = app._cartParser->getIdentifiers();
|
||||
auto pub = app._cartParser->getPublicIdentifiers();
|
||||
|
||||
util::clear(app._cartDump.data);
|
||||
app._cartDump.initConfig(
|
||||
9, app._selectedEntry->flags & cart::DATA_HAS_PUBLIC_SECTION
|
||||
);
|
||||
|
||||
if (pri) {
|
||||
if (app._selectedEntry->flags & cart::DATA_HAS_CART_ID)
|
||||
pri->cartID.copyFrom(app._cartDump.cartID.data);
|
||||
if (app._selectedEntry->flags & cart::DATA_HAS_TRACE_ID)
|
||||
pri->updateTraceID(
|
||||
app._selectedEntry->traceIDType,
|
||||
app._selectedEntry->traceIDParam, &app._cartDump.cartID
|
||||
);
|
||||
if (app._selectedEntry->flags & cart::DATA_HAS_INSTALL_ID) {
|
||||
// The private installation ID seems to be unused on carts with a
|
||||
// public data section.
|
||||
if (pub)
|
||||
pub->setInstallID(app._selectedEntry->installIDPrefix);
|
||||
else
|
||||
pri->setInstallID(app._selectedEntry->installIDPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
app._cartParser->setCode(app._selectedEntry->code);
|
||||
app._cartParser->setRegion(app._selectedEntry->region);
|
||||
app._cartParser->setYear(app._selectedEntry->year);
|
||||
app._cartParser->flush();
|
||||
|
||||
app._workerStatus.update(1, 3, WSTR("App.cartReflashWorker.setDataKey"));
|
||||
auto error = app._cartDriver->setDataKey(app._selectedEntry->dataKey);
|
||||
|
||||
if (error) {
|
||||
LOG_APP("key error [%s]", cart::getErrorString(error));
|
||||
} else {
|
||||
app._workerStatus.update(2, 3, WSTR("App.cartReflashWorker.write"));
|
||||
error = app._cartDriver->writeData();
|
||||
}
|
||||
|
||||
cartDetectWorker(app);
|
||||
|
||||
if (error) {
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartReflashWorker.writeError"),
|
||||
cart::getErrorString(error)
|
||||
);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
return cartUnlockWorker(app);
|
||||
}
|
||||
|
||||
bool cartEraseWorker(App &app) {
|
||||
app._workerStatus.update(0, 1, WSTR("App.cartEraseWorker.erase"));
|
||||
|
||||
auto error = app._cartDriver->erase();
|
||||
|
||||
cartDetectWorker(app);
|
||||
|
||||
if (error) {
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.cartEraseWorker.error"),
|
||||
cart::getErrorString(error)
|
||||
);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
return cartUnlockWorker(app);
|
||||
}
|
28
src/main/workers/cartworkers.hpp
Normal file
28
src/main/workers/cartworkers.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
|
||||
*
|
||||
* 573in1 is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* 573in1 is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "main/app/app.hpp"
|
||||
|
||||
bool cartDetectWorker(App &app);
|
||||
bool cartUnlockWorker(App &app);
|
||||
bool qrCodeWorker(App &app);
|
||||
bool cartDumpWorker(App &app);
|
||||
bool cartWriteWorker(App &app);
|
||||
bool cartRestoreWorker(App &app);
|
||||
bool cartReflashWorker(App &app);
|
||||
bool cartEraseWorker(App &app);
|
@ -25,6 +25,7 @@
|
||||
#include "common/io.hpp"
|
||||
#include "common/rom.hpp"
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/workers/miscworkers.hpp"
|
||||
#include "ps1/system.h"
|
||||
|
||||
static const rom::Region *const _AUTOBOOT_REGIONS[]{
|
||||
@ -43,25 +44,25 @@ static const char *const _AUTOBOOT_PATHS[][2]{
|
||||
{ "hdd:/noboot.txt", "hdd:/psx.exe" }
|
||||
};
|
||||
|
||||
bool App::_startupWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.startupWorker.ideInit"));
|
||||
_fileIO.initIDE();
|
||||
bool startupWorker(App &app) {
|
||||
app._workerStatus.update(0, 1, WSTR("App.startupWorker.ideInit"));
|
||||
app._fileIO.initIDE();
|
||||
|
||||
_fileInitWorker();
|
||||
fileInitWorker(app);
|
||||
|
||||
#ifdef ENABLE_AUTOBOOT
|
||||
// Only try to autoboot if DIP switch 1 is on.
|
||||
if (io::getDIPSwitch(0)) {
|
||||
_workerStatus.update(3, 4, WSTR("App.ideInitWorker.autoboot"));
|
||||
app._workerStatus.update(3, 4, WSTR("App.ideInitWorker.autoboot"));
|
||||
|
||||
if (io::getDIPSwitch(3)) {
|
||||
for (auto region : _AUTOBOOT_REGIONS) {
|
||||
if (!region->getBootExecutableHeader())
|
||||
continue;
|
||||
|
||||
_storageActionsScreen.selectedRegion = region;
|
||||
app._storageActionsScreen.selectedRegion = region;
|
||||
|
||||
_workerStatus.setNextScreen(_autobootScreen);
|
||||
app._workerStatus.setNextScreen(app._autobootScreen);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -69,18 +70,18 @@ bool App::_startupWorker(void) {
|
||||
for (auto path : _AUTOBOOT_PATHS) {
|
||||
fs::FileInfo info;
|
||||
|
||||
if (_fileIO.vfs.getFileInfo(info, path[0]))
|
||||
if (app._fileIO.vfs.getFileInfo(info, path[0]))
|
||||
continue;
|
||||
if (!_fileIO.vfs.getFileInfo(info, path[1]))
|
||||
if (!app._fileIO.vfs.getFileInfo(info, path[1]))
|
||||
continue;
|
||||
|
||||
_storageActionsScreen.selectedRegion = nullptr;
|
||||
app._storageActionsScreen.selectedRegion = nullptr;
|
||||
__builtin_strncpy(
|
||||
_fileBrowserScreen.selectedPath, path[1],
|
||||
sizeof(_fileBrowserScreen.selectedPath)
|
||||
app._fileBrowserScreen.selectedPath, path[1],
|
||||
sizeof(app._fileBrowserScreen.selectedPath)
|
||||
);
|
||||
|
||||
_workerStatus.setNextScreen(_autobootScreen);
|
||||
app._workerStatus.setNextScreen(app._autobootScreen);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -89,17 +90,17 @@ bool App::_startupWorker(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App::_fileInitWorker(void) {
|
||||
_workerStatus.update(0, 3, WSTR("App.fileInitWorker.unmount"));
|
||||
_fileIO.closeResourceFile();
|
||||
_fileIO.unmountIDE();
|
||||
bool fileInitWorker(App &app) {
|
||||
app._workerStatus.update(0, 3, WSTR("App.fileInitWorker.unmount"));
|
||||
app._fileIO.closeResourceFile();
|
||||
app._fileIO.unmountIDE();
|
||||
|
||||
_workerStatus.update(1, 3, WSTR("App.fileInitWorker.mount"));
|
||||
_fileIO.mountIDE();
|
||||
app._workerStatus.update(1, 3, WSTR("App.fileInitWorker.mount"));
|
||||
app._fileIO.mountIDE();
|
||||
|
||||
_workerStatus.update(2, 3, WSTR("App.fileInitWorker.loadResources"));
|
||||
if (_fileIO.loadResourceFile(EXTERNAL_DATA_DIR "/resource.zip"))
|
||||
_loadResources();
|
||||
app._workerStatus.update(2, 3, WSTR("App.fileInitWorker.loadResources"));
|
||||
if (app._fileIO.loadResourceFile(EXTERNAL_DATA_DIR "/resource.zip"))
|
||||
app._loadResources();
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -132,11 +133,11 @@ static const char *const _DEVICE_TYPES[]{
|
||||
"atapi" // storage::ATAPI
|
||||
};
|
||||
|
||||
bool App::_executableWorker(void) {
|
||||
_workerStatus.update(0, 2, WSTR("App.executableWorker.init"));
|
||||
bool executableWorker(App &app) {
|
||||
app._workerStatus.update(0, 2, WSTR("App.executableWorker.init"));
|
||||
|
||||
auto region = _storageActionsScreen.selectedRegion;
|
||||
const char *path = _fileBrowserScreen.selectedPath;
|
||||
auto region = app._storageActionsScreen.selectedRegion;
|
||||
const char *path = app._fileBrowserScreen.selectedPath;
|
||||
|
||||
const char *deviceType;
|
||||
int deviceIndex;
|
||||
@ -151,7 +152,7 @@ bool App::_executableWorker(void) {
|
||||
} else {
|
||||
__builtin_memset(header.magic, 0, sizeof(header.magic));
|
||||
|
||||
auto file = _fileIO.vfs.openFile(path, fs::READ);
|
||||
auto file = app._fileIO.vfs.openFile(path, fs::READ);
|
||||
|
||||
if (file) {
|
||||
file->read(&header, sizeof(header));
|
||||
@ -160,15 +161,15 @@ bool App::_executableWorker(void) {
|
||||
}
|
||||
|
||||
if (!header.validateMagic()) {
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.executableWorker.fileError"), path
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
deviceIndex = path[3] - '0';
|
||||
deviceType = _DEVICE_TYPES[_fileIO.ideDevices[deviceIndex]->type];
|
||||
deviceType = _DEVICE_TYPES[app._fileIO.ideDevices[deviceIndex]->type];
|
||||
}
|
||||
|
||||
auto executableEnd = header.textOffset + header.textLength;
|
||||
@ -197,10 +198,10 @@ bool App::_executableWorker(void) {
|
||||
// appropriate location.
|
||||
util::Data binary;
|
||||
|
||||
if (!_fileIO.resource.loadData(binary, launcher.path))
|
||||
if (!app._fileIO.resource.loadData(binary, launcher.path))
|
||||
continue;
|
||||
|
||||
_workerStatus.update(1, 2, WSTR("App.executableWorker.load"));
|
||||
app._workerStatus.update(1, 2, WSTR("App.executableWorker.load"));
|
||||
auto launcherHeader = binary.as<const util::ExecutableHeader>();
|
||||
|
||||
util::ExecutableLoader loader(
|
||||
@ -232,7 +233,7 @@ bool App::_executableWorker(void) {
|
||||
// through the command line.
|
||||
fs::FileFragmentTable fragments;
|
||||
|
||||
_fileIO.vfs.getFileFragments(fragments, path);
|
||||
app._fileIO.vfs.getFileFragments(fragments, path);
|
||||
|
||||
auto fragment = fragments.as<const fs::FileFragment>();
|
||||
auto count = fragments.getNumFragments();
|
||||
@ -245,11 +246,11 @@ bool App::_executableWorker(void) {
|
||||
|
||||
fragments.destroy();
|
||||
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.executableWorker.fragmentError"),
|
||||
path, count, i
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -258,9 +259,9 @@ bool App::_executableWorker(void) {
|
||||
|
||||
// All destructors must be invoked manually as we are not returning to
|
||||
// main() before starting the new executable.
|
||||
_unloadCartData();
|
||||
_fileIO.closeResourceFile();
|
||||
_fileIO.unmountIDE();
|
||||
app._unloadCartData();
|
||||
app._fileIO.closeResourceFile();
|
||||
app._fileIO.unmountIDE();
|
||||
|
||||
LOG_APP("jumping to launcher");
|
||||
uninstallExceptionHandler();
|
||||
@ -269,18 +270,18 @@ bool App::_executableWorker(void) {
|
||||
loader.run();
|
||||
}
|
||||
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.executableWorker.addressError"),
|
||||
header.textOffset, executableEnd - 1, stackTop
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool App::_atapiEjectWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.atapiEjectWorker.eject"));
|
||||
bool atapiEjectWorker(App &app) {
|
||||
app._workerStatus.update(0, 1, WSTR("App.atapiEjectWorker.eject"));
|
||||
|
||||
for (auto dev : _fileIO.ideDevices) {
|
||||
for (auto dev : app._fileIO.ideDevices) {
|
||||
if (!dev)
|
||||
continue;
|
||||
|
||||
@ -294,31 +295,31 @@ bool App::_atapiEjectWorker(void) {
|
||||
continue;
|
||||
|
||||
if (error) {
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.atapiEjectWorker.ejectError"),
|
||||
storage::getErrorString(error)
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.atapiEjectWorker.noDrive")
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool App::_rebootWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.rebootWorker.reboot"));
|
||||
bool rebootWorker(App &app) {
|
||||
app._workerStatus.update(0, 1, WSTR("App.rebootWorker.reboot"));
|
||||
|
||||
_unloadCartData();
|
||||
_fileIO.closeResourceFile();
|
||||
_fileIO.unmountIDE();
|
||||
_workerStatus.setStatus(WORKER_REBOOT);
|
||||
app._unloadCartData();
|
||||
app._fileIO.closeResourceFile();
|
||||
app._fileIO.unmountIDE();
|
||||
app._workerStatus.setStatus(WORKER_REBOOT);
|
||||
|
||||
// Fall back to a soft reboot if the watchdog fails to reset the system.
|
||||
delayMicroseconds(2000000);
|
25
src/main/workers/miscworkers.hpp
Normal file
25
src/main/workers/miscworkers.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
|
||||
*
|
||||
* 573in1 is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* 573in1 is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "main/app/app.hpp"
|
||||
|
||||
bool startupWorker(App &app);
|
||||
bool fileInitWorker(App &app);
|
||||
bool executableWorker(App &app);
|
||||
bool atapiEjectWorker(App &app);
|
||||
bool rebootWorker(App &app);
|
@ -25,6 +25,7 @@
|
||||
#include "common/romdrivers.hpp"
|
||||
#include "main/app/app.hpp"
|
||||
#include "main/app/romactions.hpp"
|
||||
#include "main/workers/romworkers.hpp"
|
||||
|
||||
struct RegionInfo {
|
||||
public:
|
||||
@ -73,8 +74,8 @@ static constexpr size_t _DUMP_CHUNKS_PER_CRC = 32; // Save CRC32 every 16 MB
|
||||
|
||||
// TODO: all these *really* need a cleanup...
|
||||
|
||||
bool App::_romChecksumWorker(void) {
|
||||
_checksumScreen.valid = false;
|
||||
bool romChecksumWorker(App &app) {
|
||||
app._checksumScreen.valid = false;
|
||||
|
||||
for (auto &entry : _REGION_INFO) {
|
||||
if (!entry.region.isPresent())
|
||||
@ -87,7 +88,7 @@ bool App::_romChecksumWorker(void) {
|
||||
uint32_t offset = 0;
|
||||
uint32_t crc = 0;
|
||||
auto crcPtr = reinterpret_cast<uint32_t *>(
|
||||
reinterpret_cast<uintptr_t>(&_checksumScreen.values) +
|
||||
reinterpret_cast<uintptr_t>(&app._checksumScreen.values) +
|
||||
entry.crcOffset
|
||||
);
|
||||
|
||||
@ -97,7 +98,7 @@ bool App::_romChecksumWorker(void) {
|
||||
size_t end = util::min(i + _DUMP_CHUNKS_PER_CRC, numChunks);
|
||||
|
||||
for (size_t j = i; j < end; j++) {
|
||||
_workerStatus.update(j, numChunks, WSTRH(entry.crcPrompt));
|
||||
app._workerStatus.update(j, numChunks, WSTRH(entry.crcPrompt));
|
||||
|
||||
crc = entry.region.zipCRC32(offset, chunkLength, crc);
|
||||
offset += chunkLength;
|
||||
@ -107,24 +108,24 @@ bool App::_romChecksumWorker(void) {
|
||||
}
|
||||
}
|
||||
|
||||
_checksumScreen.valid = true;
|
||||
app._checksumScreen.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App::_romDumpWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.romDumpWorker.init"));
|
||||
bool romDumpWorker(App &app) {
|
||||
app._workerStatus.update(0, 1, WSTR("App.romDumpWorker.init"));
|
||||
|
||||
// Store all dumps in a subdirectory named "dumpNNNN" within the main data
|
||||
// folder.
|
||||
char dirPath[fs::MAX_PATH_LENGTH], filePath[fs::MAX_PATH_LENGTH];
|
||||
|
||||
if (!_createDataDirectory())
|
||||
if (!app._createDataDirectory())
|
||||
goto _initError;
|
||||
if (!_getNumberedPath(
|
||||
if (!app._getNumberedPath(
|
||||
dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%04d"
|
||||
))
|
||||
goto _initError;
|
||||
if (!_fileIO.vfs.createDirectory(dirPath))
|
||||
if (!app._fileIO.vfs.createDirectory(dirPath))
|
||||
goto _initError;
|
||||
|
||||
LOG_APP("saving dumps to %s", dirPath);
|
||||
@ -145,7 +146,7 @@ bool App::_romDumpWorker(void) {
|
||||
|
||||
snprintf(filePath, sizeof(filePath), entry.path, dirPath);
|
||||
|
||||
auto file = _fileIO.vfs.openFile(
|
||||
auto file = app._fileIO.vfs.openFile(
|
||||
filePath, fs::WRITE | fs::ALLOW_CREATE
|
||||
);
|
||||
|
||||
@ -158,7 +159,7 @@ bool App::_romDumpWorker(void) {
|
||||
buffer.allocate(chunkLength);
|
||||
|
||||
for (size_t i = 0; i < numChunks; i++) {
|
||||
_workerStatus.update(i, numChunks, WSTRH(entry.dumpPrompt));
|
||||
app._workerStatus.update(i, numChunks, WSTRH(entry.dumpPrompt));
|
||||
entry.region.read(buffer.ptr, offset, chunkLength);
|
||||
|
||||
if (file->write(buffer.ptr, chunkLength) < chunkLength) {
|
||||
@ -179,42 +180,42 @@ bool App::_romDumpWorker(void) {
|
||||
LOG_APP("%s saved", filePath);
|
||||
}
|
||||
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_SUCCESS, WSTR("App.romDumpWorker.success"), dirPath
|
||||
);
|
||||
return true;
|
||||
|
||||
_initError:
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.romDumpWorker.initError"), dirPath
|
||||
);
|
||||
return false;
|
||||
|
||||
_fileError:
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.romDumpWorker.fileError"), filePath
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool App::_romRestoreWorker(void) {
|
||||
_workerStatus.update(0, 1, WSTR("App.romRestoreWorker.init"));
|
||||
bool romRestoreWorker(App &app) {
|
||||
app._workerStatus.update(0, 1, WSTR("App.romRestoreWorker.init"));
|
||||
|
||||
const char *path = _fileBrowserScreen.selectedPath;
|
||||
auto file = _fileIO.vfs.openFile(path, fs::READ);
|
||||
const char *path = app._fileBrowserScreen.selectedPath;
|
||||
auto file = app._fileIO.vfs.openFile(path, fs::READ);
|
||||
|
||||
if (!file) {
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.romRestoreWorker.fileError"), path
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_romEraseWorker())
|
||||
if (!romEraseWorker(app))
|
||||
return false;
|
||||
|
||||
auto region = _storageActionsScreen.selectedRegion;
|
||||
auto regionLength = _storageActionsScreen.selectedLength;
|
||||
auto region = app._storageActionsScreen.selectedRegion;
|
||||
auto regionLength = app._storageActionsScreen.selectedLength;
|
||||
|
||||
auto driver = region->newDriver();
|
||||
auto chipLength = driver->getChipSize().chipLength;
|
||||
@ -232,7 +233,7 @@ bool App::_romRestoreWorker(void) {
|
||||
// Parallelize writing by buffering a chunk for each chip into RAM, then
|
||||
// writing all chunks to the respective chips at the same time.
|
||||
for (size_t i = 0; i < chipLength; i += maxChunkLength) {
|
||||
_workerStatus.update(i, chipLength, WSTR("App.romRestoreWorker.write"));
|
||||
app._workerStatus.update(i, chipLength, WSTR("App.romRestoreWorker.write"));
|
||||
|
||||
auto bufferPtr = buffers.as<uint8_t>();
|
||||
auto lengthPtr = chunkLengths.as<size_t>();
|
||||
@ -305,7 +306,7 @@ bool App::_romRestoreWorker(void) {
|
||||
delete file;
|
||||
delete driver;
|
||||
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.romRestoreWorker.flashError"),
|
||||
rom::getErrorString(error), bytesWritten
|
||||
);
|
||||
@ -326,13 +327,13 @@ bool App::_romRestoreWorker(void) {
|
||||
delete file;
|
||||
delete driver;
|
||||
|
||||
_messageScreen.setMessage(MESSAGE_SUCCESS, WSTRH(message), bytesWritten);
|
||||
app._messageScreen.setMessage(MESSAGE_SUCCESS, WSTRH(message), bytesWritten);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App::_romEraseWorker(void) {
|
||||
auto region = _storageActionsScreen.selectedRegion;
|
||||
auto regionLength = _storageActionsScreen.selectedLength;
|
||||
bool romEraseWorker(App &app) {
|
||||
auto region = app._storageActionsScreen.selectedRegion;
|
||||
auto regionLength = app._storageActionsScreen.selectedLength;
|
||||
|
||||
auto driver = region->newDriver();
|
||||
size_t chipLength = driver->getChipSize().chipLength;
|
||||
@ -343,18 +344,18 @@ bool App::_romEraseWorker(void) {
|
||||
if (!chipLength) {
|
||||
delete driver;
|
||||
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.romEraseWorker.unsupported")
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
_checksumScreen.valid = false;
|
||||
app._checksumScreen.valid = false;
|
||||
|
||||
// Parallelize erasing by sending the same sector erase command to all chips
|
||||
// at the same time.
|
||||
for (size_t i = 0; i < chipLength; i += sectorLength) {
|
||||
_workerStatus.update(i, chipLength, WSTR("App.romEraseWorker.erase"));
|
||||
app._workerStatus.update(i, chipLength, WSTR("App.romEraseWorker.erase"));
|
||||
|
||||
for (size_t j = 0; j < regionLength; j += chipLength)
|
||||
driver->eraseSector(i + j);
|
||||
@ -367,7 +368,7 @@ bool App::_romEraseWorker(void) {
|
||||
|
||||
delete driver;
|
||||
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.romEraseWorker.flashError"),
|
||||
rom::getErrorString(error), sectorsErased
|
||||
);
|
||||
@ -377,18 +378,18 @@ bool App::_romEraseWorker(void) {
|
||||
|
||||
delete driver;
|
||||
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_SUCCESS, WSTR("App.romEraseWorker.success"), sectorsErased
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App::_flashExecutableWriteWorker(void) {
|
||||
bool flashExecutableWriteWorker(App &app) {
|
||||
// TODO: implement
|
||||
return false;
|
||||
}
|
||||
|
||||
bool App::_flashHeaderWriteWorker(void) {
|
||||
bool flashHeaderWriteWorker(App &app) {
|
||||
auto driver = rom::flash.newDriver();
|
||||
size_t sectorLength = driver->getChipSize().eraseSectorLength;
|
||||
|
||||
@ -397,15 +398,15 @@ bool App::_flashHeaderWriteWorker(void) {
|
||||
if (!sectorLength) {
|
||||
delete driver;
|
||||
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.flashHeaderWriteWorker.unsupported")
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
||||
|
||||
_checksumScreen.valid = false;
|
||||
_workerStatus.update(0, 2, WSTR("App.flashHeaderWriteWorker.erase"));
|
||||
app._checksumScreen.valid = false;
|
||||
app._workerStatus.update(0, 2, WSTR("App.flashHeaderWriteWorker.erase"));
|
||||
|
||||
// The flash can only be erased with sector granularity, so all data in the
|
||||
// first sector other than the header must be backed up and rewritten.
|
||||
@ -420,11 +421,11 @@ bool App::_flashHeaderWriteWorker(void) {
|
||||
if (error)
|
||||
goto _flashError;
|
||||
|
||||
_workerStatus.update(1, 2, WSTR("App.flashHeaderWriteWorker.write"));
|
||||
app._workerStatus.update(1, 2, WSTR("App.flashHeaderWriteWorker.write"));
|
||||
|
||||
// Write the new header (if any).
|
||||
if (!_romHeaderDump.isDataEmpty()) {
|
||||
auto ptr = reinterpret_cast<const uint16_t *>(_romHeaderDump.data);
|
||||
if (!app._romHeaderDump.isDataEmpty()) {
|
||||
auto ptr = reinterpret_cast<const uint16_t *>(app._romHeaderDump.data);
|
||||
|
||||
for (
|
||||
uint32_t offset = rom::FLASH_HEADER_OFFSET;
|
||||
@ -466,10 +467,10 @@ _flashError:
|
||||
buffer.destroy();
|
||||
delete driver;
|
||||
|
||||
_messageScreen.setMessage(
|
||||
app._messageScreen.setMessage(
|
||||
MESSAGE_ERROR, WSTR("App.flashHeaderWriteWorker.flashError"),
|
||||
rom::getErrorString(error)
|
||||
);
|
||||
_workerStatus.setNextScreen(_messageScreen);
|
||||
app._workerStatus.setNextScreen(app._messageScreen);
|
||||
return false;
|
||||
}
|
26
src/main/workers/romworkers.hpp
Normal file
26
src/main/workers/romworkers.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
|
||||
*
|
||||
* 573in1 is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* 573in1 is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "main/app/app.hpp"
|
||||
|
||||
bool romChecksumWorker(App &app);
|
||||
bool romDumpWorker(App &app);
|
||||
bool romRestoreWorker(App &app);
|
||||
bool romEraseWorker(App &app);
|
||||
bool flashExecutableWriteWorker(App &app);
|
||||
bool flashHeaderWriteWorker(App &app);
|
@ -31,8 +31,9 @@ static uint32_t _savedBreakpointVector[4];
|
||||
static uint32_t _savedExceptionVector[4];
|
||||
static Thread _mainThread;
|
||||
|
||||
ArgFunction interruptHandler = 0;
|
||||
void *interruptHandlerArg = 0;
|
||||
ArgFunction interruptHandler = 0;
|
||||
void *interruptHandlerArg0 = 0;
|
||||
void *interruptHandlerArg1 = 0;
|
||||
|
||||
Thread *currentThread = &_mainThread;
|
||||
Thread *nextThread = &_mainThread;
|
||||
@ -89,10 +90,11 @@ void uninstallExceptionHandler(void) {
|
||||
_flushCache();
|
||||
}
|
||||
|
||||
void setInterruptHandler(ArgFunction func, void *arg) {
|
||||
void setInterruptHandler(ArgFunction func, void *arg0, void *arg1) {
|
||||
disableInterrupts();
|
||||
interruptHandler = func;
|
||||
interruptHandlerArg = arg;
|
||||
interruptHandler = func;
|
||||
interruptHandlerArg0 = arg0;
|
||||
interruptHandlerArg1 = arg1;
|
||||
flushWriteQueue();
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ typedef struct {
|
||||
} Thread;
|
||||
|
||||
typedef void (*VoidFunction)(void);
|
||||
typedef void (*ArgFunction)(void *arg);
|
||||
typedef void (*ArgFunction)(void *arg0, void *arg1);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -92,16 +92,18 @@ __attribute__((always_inline)) static inline void flushWriteQueue(void) {
|
||||
*
|
||||
* @param thread
|
||||
* @param func
|
||||
* @param arg Optional argument to entry point
|
||||
* @param arg0 Optional first argument to entry point
|
||||
* @param arg1 Optional second argument to entry point
|
||||
* @param stack Pointer to last 8 bytes in the stack
|
||||
*/
|
||||
__attribute__((always_inline)) static inline void initThread(
|
||||
Thread *thread, ArgFunction func, void *arg, void *stack
|
||||
Thread *thread, ArgFunction func, void *arg0, void *arg1, void *stack
|
||||
) {
|
||||
register uint32_t gp __asm__("gp");
|
||||
|
||||
thread->pc = (uint32_t) func;
|
||||
thread->a0 = (uint32_t) arg;
|
||||
thread->a0 = (uint32_t) arg0;
|
||||
thread->a1 = (uint32_t) arg1;
|
||||
thread->gp = (uint32_t) gp;
|
||||
thread->sp = (uint32_t) stack;
|
||||
thread->fp = (uint32_t) stack;
|
||||
@ -154,9 +156,10 @@ void uninstallExceptionHandler(void);
|
||||
* a new handler.
|
||||
*
|
||||
* @param func
|
||||
* @param arg Optional argument to be passed to handler
|
||||
* @param arg Optional first argument to be passed to handler
|
||||
* @param arg Optional second argument to be passed to handler
|
||||
*/
|
||||
void setInterruptHandler(ArgFunction func, void *arg);
|
||||
void setInterruptHandler(ArgFunction func, void *arg0, void *arg1);
|
||||
|
||||
/**
|
||||
* @brief Temporarily disables interrupts, then calls the BIOS function to clear
|
||||
|
100
src/ps1/system.s
100
src/ps1/system.s
@ -22,23 +22,25 @@
|
||||
.set CAUSE, $13
|
||||
.set EPC, $14
|
||||
|
||||
.set COP0_CAUSE_EXC_BITMASK, 31 << 2
|
||||
.set COP0_CAUSE_EXC_SYS, 8 << 2
|
||||
|
||||
.section .text._exceptionVector, "ax", @progbits
|
||||
.global _exceptionVector
|
||||
.type _exceptionVector, @function
|
||||
|
||||
_exceptionVector:
|
||||
# This tiny stub is going to be relocated to the address the CPU jumps to
|
||||
# This 16-byte stub is going to be relocated to the address the CPU jumps to
|
||||
# when an exception occurs (0x80000080) at runtime, overriding the default
|
||||
# one installed by the BIOS. We're going to fetch a pointer to the current
|
||||
# thread, grab the EPC (i.e. the address of the instruction that was being
|
||||
# executed before the exception occurred) and jump to the exception handler.
|
||||
# NOTE: we can't use any registers other than $k0 and $k1 here, as doing so
|
||||
# would destroy their contents and corrupt the current thread's state.
|
||||
lui $k0, %hi(currentThread)
|
||||
lw $k0, %lo(currentThread)($k0)
|
||||
|
||||
lw $k0, %gprel(currentThread)($gp)
|
||||
j _exceptionHandler
|
||||
mfc0 $k1, EPC
|
||||
nop
|
||||
|
||||
.section .text._exceptionHandler, "ax", @progbits
|
||||
.global _exceptionHandler
|
||||
@ -88,15 +90,17 @@ _exceptionHandler:
|
||||
# exception. If it was caused by a syscall, increment EPC to make sure
|
||||
# returning to the thread won't trigger another syscall.
|
||||
mfc0 $v0, CAUSE
|
||||
lui $v1, %hi(interruptHandler)
|
||||
lw $v1, %gprel(interruptHandler)($gp)
|
||||
|
||||
andi $v0, 0x1f << 2 # if (((CAUSE >> 2) & 0x1f) == 0) goto checkForGTEInst
|
||||
beqz $v0, .LcheckForGTEInst
|
||||
li $at, 8 << 2 # if (((CAUSE >> 2) & 0x1f) == 8) goto applyIncrement
|
||||
beq $v0, $at, .LapplyIncrement
|
||||
lw $v1, %lo(interruptHandler)($v1)
|
||||
# int code = CAUSE & COP0_CAUSE_EXC_BITMASK;
|
||||
andi $v0, COP0_CAUSE_EXC_BITMASK
|
||||
beqz $v0, .LisInterrupt
|
||||
li $at, COP0_CAUSE_EXC_SYS
|
||||
|
||||
.LotherException:
|
||||
beq $v0, $at, .LisSyscall
|
||||
lw $a0, %gprel(interruptHandlerArg0)($gp)
|
||||
|
||||
.LisOtherException: # if ((code != INT) && (code != SYS)) {
|
||||
# If the exception was not triggered by a syscall nor by an interrupt call
|
||||
# _unhandledException(), which will then display information about the
|
||||
# exception and lock up.
|
||||
@ -107,41 +111,44 @@ _exceptionHandler:
|
||||
jal _unhandledException
|
||||
addiu $sp, -8
|
||||
|
||||
lw $k0, %gprel(nextThread)($gp)
|
||||
b .Lreturn
|
||||
addiu $sp, 8
|
||||
|
||||
.LcheckForGTEInst:
|
||||
.LisInterrupt: # } else {
|
||||
# If the exception was caused by an interrupt, check if the interrupted
|
||||
# instruction was a GTE opcode and increment EPC to avoid executing it again
|
||||
# if that is the case. This is a workaround for a hardware bug.
|
||||
lw $v0, 0($k1) # if ((*EPC >> 25) == 0x25) EPC++
|
||||
|
||||
# if ((code == INT) && ((*EPC >> 25) == 0x25)) EPC += 4;
|
||||
lw $v0, 0($k1)
|
||||
li $at, 0x25
|
||||
srl $v0, 25
|
||||
bne $v0, $at, .LskipIncrement
|
||||
lw $v1, %lo(interruptHandler)($v1)
|
||||
bne $v0, $at, .LskipEPCIncrement
|
||||
lw $a0, %gprel(interruptHandlerArg0)($gp)
|
||||
|
||||
.LapplyIncrement:
|
||||
.LisSyscall:
|
||||
# if (code == SYS) EPC += 4;
|
||||
addiu $k1, 4
|
||||
|
||||
.LskipIncrement:
|
||||
# Save the modified EPC and dispatch any pending interrupts. The handler
|
||||
# will temporarily use the current thread's stack.
|
||||
.LskipEPCIncrement:
|
||||
# Save the modified EPC and invoke the interrupt handler, which will
|
||||
# temporarily use the current thread's stack.
|
||||
sw $k1, 0x00($k0)
|
||||
|
||||
lui $a0, %hi(interruptHandlerArg)
|
||||
lw $a0, %lo(interruptHandlerArg)($a0)
|
||||
jalr $v1 # interruptHandler(interruptHandlerArg)
|
||||
# interruptHandler(interruptHandlerArg0, interruptHandlerArg1);
|
||||
lw $a1, %gprel(interruptHandlerArg1)($gp)
|
||||
jalr $v1
|
||||
addiu $sp, -8
|
||||
|
||||
lw $k0, %gprel(nextThread)($gp)
|
||||
addiu $sp, 8
|
||||
|
||||
.Lreturn:
|
||||
# Grab a pointer to the next thread to be executed, restore its state and
|
||||
# return.
|
||||
lui $k0, %hi(nextThread)
|
||||
lw $k0, %lo(nextThread)($k0)
|
||||
lui $at, %hi(currentThread)
|
||||
sw $k0, %lo(currentThread)($at)
|
||||
.Lreturn: # }
|
||||
# Grab a pointer to the next thread to be executed and restore its state.
|
||||
|
||||
# currentThread = nextThread;
|
||||
sw $k0, %gprel(currentThread)($gp)
|
||||
|
||||
lw $v0, 0x78($k0)
|
||||
lw $v1, 0x7c($k0)
|
||||
@ -197,7 +204,9 @@ _exceptionHandler:
|
||||
delayMicroseconds:
|
||||
# Calculate the approximate number of CPU cycles that need to be burned,
|
||||
# assuming a 33.8688 MHz clock (1 us = 33.8688 = ~33.875 cycles).
|
||||
sll $a1, $a0, 8 # cycles = ((us * 271) + 4) / 8
|
||||
|
||||
# cycles = ((us * 271) + 4) / 8;
|
||||
sll $a1, $a0, 8
|
||||
sll $a2, $a0, 4
|
||||
addu $a1, $a2
|
||||
subu $a1, $a0
|
||||
@ -208,9 +217,9 @@ delayMicroseconds:
|
||||
# loop and returning.
|
||||
addiu $a0, -(6 + 1 + 2 + 4 + 2)
|
||||
|
||||
# Reset timer 2 to its default setting of counting system clock edges.
|
||||
# TIMER2_CTRL = 0;
|
||||
lui $v1, %hi(IO_BASE)
|
||||
sh $0, %lo(TIMER2_CTRL)($v1) # TIMER2_CTRL = 0
|
||||
sh $0, %lo(TIMER2_CTRL)($v1)
|
||||
|
||||
# Wait for up to 0xff00 cycles at a time (resetting the timer and waiting
|
||||
# for it to count up each time), as the counter is only 16 bits wide. We
|
||||
@ -221,11 +230,13 @@ delayMicroseconds:
|
||||
beqz $v0, .LshortDelay
|
||||
li $a2, 0xff00 + 3
|
||||
|
||||
.LlongDelay: # for (; cycles > 0xff00; cycles -= (0xff00 + 3))
|
||||
sh $0, %lo(TIMER2_VALUE)($v1) # TIMER2_VALUE = 0
|
||||
.LlongDelay: # for (; cycles > 0xff00; cycles -= (0xff00 + 3)) {
|
||||
# TIMER2_VALUE = 0;
|
||||
sh $0, %lo(TIMER2_VALUE)($v1)
|
||||
li $v0, 0
|
||||
|
||||
.LlongDelayLoop: # while (TIMER2_VALUE < 0xff00);
|
||||
.LlongDelayLoop:
|
||||
# while (TIMER2_VALUE < 0xff00);
|
||||
nop
|
||||
slt $v0, $v0, $a1
|
||||
bnez $v0, .LlongDelayLoop
|
||||
@ -235,17 +246,21 @@ delayMicroseconds:
|
||||
bnez $v0, .LlongDelay
|
||||
subu $a0, $a2
|
||||
|
||||
.LshortDelay:
|
||||
.LshortDelay: # }
|
||||
# Run the last busy loop once less than 0xff00 cycles are remaining.
|
||||
sh $0, %lo(TIMER2_VALUE)($v1) # TIMER2_VALUE = 0
|
||||
|
||||
# TIMER2_VALUE = 0;
|
||||
sh $0, %lo(TIMER2_VALUE)($v1)
|
||||
li $v0, 0
|
||||
|
||||
.LshortDelayLoop: # while (TIMER2_VALUE < cycles);
|
||||
.LshortDelayLoop:
|
||||
# while (TIMER2_VALUE < cycles);
|
||||
nop
|
||||
slt $v0, $v0, $a0
|
||||
bnez $v0, .LshortDelayLoop
|
||||
lhu $v0, %lo(TIMER2_VALUE)($v1)
|
||||
|
||||
# return;
|
||||
jr $ra
|
||||
nop
|
||||
|
||||
@ -254,9 +269,8 @@ delayMicroseconds:
|
||||
.type delayMicrosecondsBusy, @function
|
||||
|
||||
delayMicrosecondsBusy:
|
||||
# Calculate the approximate number of CPU cycles that need to be burned,
|
||||
# assuming a 33.8688 MHz clock (1 us = 33.8688 = ~33.875 cycles).
|
||||
sll $a1, $a0, 8 # cycles = ((us * 271) + 4) / 8
|
||||
# cycles = ((us * 271) + 4) / 8:
|
||||
sll $a1, $a0, 8
|
||||
sll $a2, $a0, 4
|
||||
addu $a1, $a2
|
||||
subu $a1, $a0
|
||||
@ -266,9 +280,11 @@ delayMicrosecondsBusy:
|
||||
# Compensate for the overhead of calculating the cycle count and returning.
|
||||
addiu $a0, -(6 + 1 + 2)
|
||||
|
||||
.Lloop: # while (cycles > 0) cycles -= 2
|
||||
.Lloop:
|
||||
# while (cycles > 0) cycles -= 2;
|
||||
bgtz $a0, .Lloop
|
||||
addiu $a0, -2
|
||||
|
||||
# return;
|
||||
jr $ra
|
||||
nop
|
||||
|
Loading…
x
Reference in New Issue
Block a user