mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-03-01 15:30:31 +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/app.cpp
|
||||||
src/main/app/cartactions.cpp
|
src/main/app/cartactions.cpp
|
||||||
src/main/app/cartunlock.cpp
|
src/main/app/cartunlock.cpp
|
||||||
src/main/app/cartworkers.cpp
|
|
||||||
src/main/app/main.cpp
|
src/main/app/main.cpp
|
||||||
src/main/app/misc.cpp
|
src/main/app/misc.cpp
|
||||||
src/main/app/miscworkers.cpp
|
|
||||||
src/main/app/modals.cpp
|
src/main/app/modals.cpp
|
||||||
src/main/app/romactions.cpp
|
src/main/app/romactions.cpp
|
||||||
src/main/app/romworkers.cpp
|
|
||||||
src/main/app/tests.cpp
|
src/main/app/tests.cpp
|
||||||
|
src/main/app/threads.cpp
|
||||||
src/main/cart/cart.cpp
|
src/main/cart/cart.cpp
|
||||||
src/main/cart/cartdata.cpp
|
src/main/cart/cartdata.cpp
|
||||||
src/main/cart/cartio.cpp
|
src/main/cart/cartio.cpp
|
||||||
src/main/cart/zs01.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/main.cpp
|
||||||
src/main/uibase.cpp
|
src/main/uibase.cpp
|
||||||
src/main/uicommon.cpp
|
src/main/uicommon.cpp
|
||||||
|
@ -28,7 +28,7 @@ extern "C" const size_t _resourceArchiveLength;
|
|||||||
static char _ptrArg[]{ "resource.ptr=xxxxxxxx\0" };
|
static char _ptrArg[]{ "resource.ptr=xxxxxxxx\0" };
|
||||||
static char _lengthArg[]{ "resource.length=xxxxxxxx\0" };
|
static char _lengthArg[]{ "resource.length=xxxxxxxx\0" };
|
||||||
|
|
||||||
struct [[gnu::packed]] ZIPFileHeader {
|
class [[gnu::packed]] ZIPFileHeader {
|
||||||
public:
|
public:
|
||||||
uint32_t magic;
|
uint32_t magic;
|
||||||
uint16_t version, flags, compType;
|
uint16_t version, flags, compType;
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@ -22,6 +23,9 @@
|
|||||||
#include "common/util/hash.hpp"
|
#include "common/util/hash.hpp"
|
||||||
#include "common/util/templates.hpp"
|
#include "common/util/templates.hpp"
|
||||||
#include "common/gpu.hpp"
|
#include "common/gpu.hpp"
|
||||||
|
#include "common/mdec.hpp"
|
||||||
|
#include "common/spu.hpp"
|
||||||
|
#include "ps1/registers.h"
|
||||||
|
|
||||||
namespace fs {
|
namespace fs {
|
||||||
|
|
||||||
@ -63,6 +67,7 @@ size_t Provider::loadData(util::Data &output, const char *path) {
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
assert(file->size <= SIZE_MAX);
|
assert(file->size <= SIZE_MAX);
|
||||||
|
|
||||||
if (!output.allocate(size_t(file->size))) {
|
if (!output.allocate(size_t(file->size))) {
|
||||||
file->close();
|
file->close();
|
||||||
delete file;
|
delete file;
|
||||||
@ -83,6 +88,7 @@ size_t Provider::loadData(void *output, size_t length, const char *path) {
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
assert(file->size >= length);
|
assert(file->size >= length);
|
||||||
|
|
||||||
size_t actualLength = file->read(output, length);
|
size_t actualLength = file->read(output, length);
|
||||||
|
|
||||||
file->close();
|
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) {
|
size_t Provider::loadTIM(gpu::Image &output, const char *path) {
|
||||||
util::Data data;
|
util::Data data;
|
||||||
|
size_t loadLength = 0;
|
||||||
|
|
||||||
if (!loadData(data, path))
|
if (!loadData(data, path))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
auto header = data.as<const gpu::TIMHeader>();
|
auto header = data.as<const gpu::TIMHeader>();
|
||||||
auto section = reinterpret_cast<const uint8_t *>(&header[1]);
|
|
||||||
|
|
||||||
if (!output.initFromTIMHeader(header)) {
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
data.destroy();
|
data.destroy();
|
||||||
|
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;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t uploadLength = 0;
|
buffer.destroy();
|
||||||
|
|
||||||
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();
|
data.destroy();
|
||||||
return uploadLength;
|
return loadLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Provider::loadVAG(
|
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
|
// Sounds should be decompressed and uploaded to the SPU one chunk at a
|
||||||
// time, but whatever.
|
// time, but whatever.
|
||||||
util::Data data;
|
util::Data data;
|
||||||
|
size_t loadLength = 0;
|
||||||
|
|
||||||
if (!loadData(data, path))
|
if (!loadData(data, path))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
auto header = data.as<const spu::VAGHeader>();
|
auto header = data.as<const spu::VAGHeader>();
|
||||||
auto body = reinterpret_cast<const uint32_t *>(&header[1]);
|
|
||||||
|
|
||||||
if (!output.initFromVAGHeader(header, offset)) {
|
if (output.initFromVAGHeader(*header, offset))
|
||||||
data.destroy();
|
loadLength = spu::upload(
|
||||||
return 0;
|
offset, header->getData(), data.length - sizeof(spu::VAGHeader),
|
||||||
}
|
true
|
||||||
|
);
|
||||||
auto uploadLength =
|
|
||||||
spu::upload(offset, body, data.length - sizeof(spu::VAGHeader), true);
|
|
||||||
|
|
||||||
data.destroy();
|
data.destroy();
|
||||||
return uploadLength;
|
return loadLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct [[gnu::packed]] BMPHeader {
|
class [[gnu::packed]] BMPHeader {
|
||||||
public:
|
public:
|
||||||
uint16_t magic;
|
uint16_t magic;
|
||||||
uint32_t fileLength;
|
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);
|
auto file = openFile(path, WRITE | ALLOW_CREATE);
|
||||||
|
|
||||||
if (!file)
|
if (!file)
|
||||||
@ -205,11 +248,11 @@ size_t Provider::saveVRAMBMP(gpu::RectWH &rect, const char *path) {
|
|||||||
gpu::RectWH slice;
|
gpu::RectWH slice;
|
||||||
|
|
||||||
slice.x = rect.x;
|
slice.x = rect.x;
|
||||||
|
slice.y = rect.y + rect.h - 1;
|
||||||
slice.w = rect.w;
|
slice.w = rect.w;
|
||||||
slice.h = 1;
|
slice.h = 1;
|
||||||
|
|
||||||
for (int y = rect.y + rect.h - 1; y >= rect.y; y--) {
|
for (; slice.y >= rect.y; slice.y--) {
|
||||||
slice.y = y;
|
|
||||||
auto lineLength = gpu::download(slice, buffer.ptr, true);
|
auto lineLength = gpu::download(slice, buffer.ptr, true);
|
||||||
|
|
||||||
// BMP stores channels in BGR order as opposed to RGB, so the red
|
// 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);
|
virtual size_t saveData(const void *input, size_t length, const char *path);
|
||||||
|
|
||||||
size_t loadTIM(gpu::Image &output, 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 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 */
|
/* String table parser */
|
||||||
|
@ -355,22 +355,17 @@ void Image::initFromVRAMRect(
|
|||||||
texpage = gp0_page(rect.x / 64, rect.y / 256, blendMode, colorDepth);
|
texpage = gp0_page(rect.x / 64, rect.y / 256, blendMode, colorDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Image::initFromTIMHeader(const TIMHeader *header, BlendMode blendMode) {
|
bool Image::initFromTIMHeader(const TIMHeader &header, BlendMode blendMode) {
|
||||||
if (header->magic != 0x10)
|
if (!header.validateMagic())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto ptr = reinterpret_cast<const uint8_t *>(&header[1]);
|
auto image = header.getImage();
|
||||||
|
auto clut = header.getCLUT();
|
||||||
if (header->flags & (1 << 3)) {
|
|
||||||
auto clut = reinterpret_cast<const TIMSectionHeader *>(ptr);
|
|
||||||
|
|
||||||
|
if (clut)
|
||||||
palette = gp0_clut(clut->vram.x / 16, clut->vram.y);
|
palette = gp0_clut(clut->vram.x / 16, clut->vram.y);
|
||||||
ptr += clut->length;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto image = reinterpret_cast<const TIMSectionHeader *>(ptr);
|
initFromVRAMRect(image->vram, header.getColorDepth(), blendMode);
|
||||||
|
|
||||||
initFromVRAMRect(image->vram, ColorDepth(header->flags & 3), blendMode);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,8 +66,8 @@ static inline void enableDisplay(bool enable) {
|
|||||||
GPU_GP1 = gp1_dispBlank(!enable);
|
GPU_GP1 = gp1_dispBlank(!enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t upload(const RectWH &rect, const 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);
|
size_t download(const RectWH &rect, void *data, bool wait = false);
|
||||||
|
|
||||||
/* Rendering context */
|
/* Rendering context */
|
||||||
|
|
||||||
@ -181,15 +181,46 @@ public:
|
|||||||
|
|
||||||
/* Image class */
|
/* Image class */
|
||||||
|
|
||||||
struct TIMHeader {
|
class TIMSectionHeader {
|
||||||
public:
|
|
||||||
uint32_t magic, flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TIMSectionHeader {
|
|
||||||
public:
|
public:
|
||||||
uint32_t length;
|
uint32_t length;
|
||||||
RectWH vram;
|
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 {
|
class Image {
|
||||||
@ -205,7 +236,7 @@ public:
|
|||||||
BlendMode blendMode = GP0_BLEND_SEMITRANS
|
BlendMode blendMode = GP0_BLEND_SEMITRANS
|
||||||
);
|
);
|
||||||
bool initFromTIMHeader(
|
bool initFromTIMHeader(
|
||||||
const TIMHeader *header, BlendMode blendMode = GP0_BLEND_SEMITRANS
|
const TIMHeader &header, BlendMode blendMode = GP0_BLEND_SEMITRANS
|
||||||
);
|
);
|
||||||
void drawScaled(
|
void drawScaled(
|
||||||
Context &ctx, int x, int y, int w, int h, bool blend = false
|
Context &ctx, int x, int y, int w, int h, bool blend = false
|
||||||
|
@ -39,7 +39,7 @@ public:
|
|||||||
int8_t baselineOffset;
|
int8_t baselineOffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FontMetricsEntry {
|
class FontMetricsEntry {
|
||||||
public:
|
public:
|
||||||
uint32_t codePoint;
|
uint32_t codePoint;
|
||||||
CharacterSize size;
|
CharacterSize size;
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "common/util/misc.hpp"
|
#include "common/util/misc.hpp"
|
||||||
#include "common/util/string.hpp"
|
#include "common/util/string.hpp"
|
||||||
|
#include "common/util/templates.hpp"
|
||||||
#include "common/io.hpp"
|
#include "common/io.hpp"
|
||||||
#include "ps1/registers.h"
|
#include "ps1/registers.h"
|
||||||
#include "ps1/registers573.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;
|
static constexpr int _IDE_RESET_CLEAR_DELAY = 50000;
|
||||||
|
|
||||||
void init(void) {
|
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
|
BIU_DEV0_CTRL = 0
|
||||||
| (7 << 0) // Write delay
|
| (7 << 0) // Write delay
|
||||||
| (4 << 4) // Read delay
|
| (4 << 4) // Read delay
|
||||||
@ -84,6 +79,65 @@ void resetIDEDevices(void) {
|
|||||||
delayMicroseconds(_IDE_RESET_CLEAR_DELAY);
|
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 */
|
/* JAMMA and RTC functions */
|
||||||
|
|
||||||
uint32_t getJAMMAInputs(void) {
|
uint32_t getJAMMAInputs(void) {
|
||||||
|
@ -161,6 +161,15 @@ static inline void setMiscOutput(MiscOutputPin pin, bool value) {
|
|||||||
void init(void);
|
void init(void);
|
||||||
void resetIDEDevices(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 */
|
/* JAMMA and RTC functions */
|
||||||
|
|
||||||
uint32_t getJAMMAInputs(void);
|
uint32_t getJAMMAInputs(void);
|
||||||
|
@ -62,7 +62,8 @@ static const uint8_t _BS_QUANT_TABLE[]{
|
|||||||
|
|
||||||
/* Basic API */
|
/* Basic API */
|
||||||
|
|
||||||
static constexpr int _DMA_CHUNK_SIZE = 32;
|
static constexpr int _DMA_CHUNK_SIZE_IN = 32;
|
||||||
|
static constexpr int _DMA_CHUNK_SIZE_OUT = 32;
|
||||||
static constexpr int _DMA_TIMEOUT = 100000;
|
static constexpr int _DMA_TIMEOUT = 100000;
|
||||||
|
|
||||||
void init(void) {
|
void init(void) {
|
||||||
@ -82,16 +83,16 @@ size_t feed(const void *data, size_t length, bool wait) {
|
|||||||
|
|
||||||
util::assertAligned<uint32_t>(data);
|
util::assertAligned<uint32_t>(data);
|
||||||
#if 0
|
#if 0
|
||||||
assert(!(length % _DMA_CHUNK_SIZE));
|
assert(!(length % _DMA_CHUNK_SIZE_IN));
|
||||||
#else
|
#else
|
||||||
length = (length + _DMA_CHUNK_SIZE - 1) / _DMA_CHUNK_SIZE;
|
length = (length + _DMA_CHUNK_SIZE_IN - 1) / _DMA_CHUNK_SIZE_IN;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!waitForDMATransfer(DMA_MDEC_IN, _DMA_TIMEOUT))
|
if (!waitForDMATransfer(DMA_MDEC_IN, _DMA_TIMEOUT))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
DMA_MADR(DMA_MDEC_IN) = reinterpret_cast<uint32_t>(data);
|
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(DMA_MDEC_IN) = 0
|
||||||
| DMA_CHCR_WRITE
|
| DMA_CHCR_WRITE
|
||||||
| DMA_CHCR_MODE_SLICE
|
| DMA_CHCR_MODE_SLICE
|
||||||
@ -100,7 +101,33 @@ size_t feed(const void *data, size_t length, bool wait) {
|
|||||||
if (wait)
|
if (wait)
|
||||||
waitForDMATransfer(DMA_MDEC_IN, _DMA_TIMEOUT);
|
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 */
|
/* MDEC bitstream decompressor */
|
||||||
|
@ -25,13 +25,16 @@ namespace mdec {
|
|||||||
/* Basic API */
|
/* Basic API */
|
||||||
|
|
||||||
void init(void);
|
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) {
|
static inline bool isIdle(void) {
|
||||||
return !(MDEC1 & MDEC_STAT_BUSY);
|
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;
|
size_t length = data[0] & MDEC_CMD_FLAG_LENGTH_BITMASK;
|
||||||
|
|
||||||
MDEC0 = MDEC_CMD_DECODE | length | flags;
|
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 */
|
/* MDEC bitstream decompressor */
|
||||||
|
|
||||||
struct BSHeader {
|
class BSHeader {
|
||||||
public:
|
public:
|
||||||
uint16_t outputLength;
|
uint16_t outputLength;
|
||||||
uint16_t mdecCommand;
|
uint16_t mdecCommand;
|
||||||
uint16_t quantScale;
|
uint16_t quantScale;
|
||||||
uint16_t version;
|
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 {
|
enum BSDecompressorError {
|
||||||
@ -55,7 +65,7 @@ enum BSDecompressorError {
|
|||||||
};
|
};
|
||||||
|
|
||||||
extern "C" BSDecompressorError _bsDecompressorStart(
|
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(
|
extern "C" BSDecompressorError _bsDecompressorResume(
|
||||||
void *_this, uint32_t *output, size_t outputLength
|
void *_this, uint32_t *output, size_t outputLength
|
||||||
@ -63,7 +73,7 @@ extern "C" BSDecompressorError _bsDecompressorResume(
|
|||||||
|
|
||||||
class BSDecompressor {
|
class BSDecompressor {
|
||||||
protected:
|
protected:
|
||||||
const uint32_t *_input;
|
const void *_input;
|
||||||
|
|
||||||
uint32_t _bits, _nextBits;
|
uint32_t _bits, _nextBits;
|
||||||
size_t _remaining;
|
size_t _remaining;
|
||||||
@ -76,7 +86,7 @@ protected:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
inline BSDecompressorError decompress(
|
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);
|
return _bsDecompressorStart(this, output, outputLength, input);
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ void init(void) {
|
|||||||
SPU_MASTER_VOL_R = 0;
|
SPU_MASTER_VOL_R = 0;
|
||||||
SPU_REVERB_VOL_L = 0;
|
SPU_REVERB_VOL_L = 0;
|
||||||
SPU_REVERB_VOL_R = 0;
|
SPU_REVERB_VOL_R = 0;
|
||||||
SPU_REVERB_ADDR = 0xfffe;
|
SPU_REVERB_ADDR = SPU_RAM_END / 8;
|
||||||
|
|
||||||
SPU_FLAG_FM1 = 0;
|
SPU_FLAG_FM1 = 0;
|
||||||
SPU_FLAG_FM2 = 0;
|
SPU_FLAG_FM2 = 0;
|
||||||
@ -88,6 +88,8 @@ void init(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Channel getFreeChannel(void) {
|
Channel getFreeChannel(void) {
|
||||||
|
util::CriticalSection sec;
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
// The status flag gets set when a channel stops or loops for the first
|
// 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
|
// 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) {
|
ChannelMask getFreeChannels(int count) {
|
||||||
|
util::CriticalSection sec;
|
||||||
|
|
||||||
ChannelMask mask = 0;
|
ChannelMask mask = 0;
|
||||||
|
|
||||||
for (Channel ch = 0; ch < NUM_CHANNELS; ch++) {
|
for (Channel ch = 0; ch < NUM_CHANNELS; ch++) {
|
||||||
@ -130,6 +134,9 @@ ChannelMask getFreeChannels(int count) {
|
|||||||
void stopChannels(ChannelMask mask) {
|
void stopChannels(ChannelMask mask) {
|
||||||
mask &= ALL_CHANNELS;
|
mask &= ALL_CHANNELS;
|
||||||
|
|
||||||
|
SPU_FLAG_OFF1 = mask & 0xffff;
|
||||||
|
SPU_FLAG_OFF2 = mask >> 16;
|
||||||
|
|
||||||
for (Channel ch = 0; mask; ch++, mask >>= 1) {
|
for (Channel ch = 0; mask; ch++, mask >>= 1) {
|
||||||
if (!(mask & 1))
|
if (!(mask & 1))
|
||||||
continue;
|
continue;
|
||||||
@ -140,8 +147,6 @@ void stopChannels(ChannelMask mask) {
|
|||||||
SPU_CH_ADDR(ch) = DUMMY_BLOCK_OFFSET / 8;
|
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_ON1 = mask & 0xffff;
|
||||||
SPU_FLAG_ON2 = mask >> 16;
|
SPU_FLAG_ON2 = mask >> 16;
|
||||||
}
|
}
|
||||||
@ -223,16 +228,13 @@ size_t download(uint32_t offset, void *data, size_t length, bool wait) {
|
|||||||
Sound::Sound(void)
|
Sound::Sound(void)
|
||||||
: offset(0), sampleRate(0), length(0) {}
|
: offset(0), sampleRate(0), length(0) {}
|
||||||
|
|
||||||
bool Sound::initFromVAGHeader(const VAGHeader *header, uint32_t _offset) {
|
bool Sound::initFromVAGHeader(const VAGHeader &header, uint32_t _offset) {
|
||||||
if (header->magic != util::concat4('V', 'A', 'G', 'p'))
|
if (!header.validateMagic())
|
||||||
return false;
|
|
||||||
if (header->channels > 1)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
offset = _offset;
|
offset = _offset;
|
||||||
sampleRate = (__builtin_bswap32(header->sampleRate) << 12) / 44100;
|
sampleRate = header.getSPUSampleRate();
|
||||||
length = __builtin_bswap32(header->length) / 8;
|
length = header.getSPULength();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,21 +317,20 @@ channels(0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Stream::initFromVAGHeader(
|
bool Stream::initFromVAGHeader(
|
||||||
const VAGHeader *header, uint32_t _offset, size_t _numChunks
|
const VAGHeader &header, uint32_t _offset, size_t _numChunks
|
||||||
) {
|
) {
|
||||||
if (isPlaying())
|
if (isPlaying())
|
||||||
return false;
|
return false;
|
||||||
if (header->magic != util::concat4('V', 'A', 'G', 'i'))
|
if (!header.validateInterleavedMagic())
|
||||||
return false;
|
|
||||||
if (!header->interleave)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
resetBuffer();
|
||||||
|
|
||||||
offset = _offset;
|
offset = _offset;
|
||||||
interleave = header->interleave;
|
interleave = header.interleave;
|
||||||
numChunks = _numChunks;
|
numChunks = _numChunks;
|
||||||
sampleRate = (__builtin_bswap32(header->sampleRate) << 12) / 44100;
|
sampleRate = header.getSPUSampleRate();
|
||||||
channels = header->channels ? header->channels : 2;
|
channels = header.channels ? header.channels : 2;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,12 +381,13 @@ ChannelMask Stream::start(uint16_t left, uint16_t right, ChannelMask mask) {
|
|||||||
void Stream::stop(void) {
|
void Stream::stop(void) {
|
||||||
util::CriticalSection sec;
|
util::CriticalSection sec;
|
||||||
|
|
||||||
if (isPlaying()) {
|
if (!isPlaying())
|
||||||
|
return;
|
||||||
|
|
||||||
SPU_CTRL &= ~SPU_CTRL_IRQ_ENABLE;
|
SPU_CTRL &= ~SPU_CTRL_IRQ_ENABLE;
|
||||||
_channelMask = 0;
|
|
||||||
|
|
||||||
stopChannels(_channelMask);
|
stopChannels(_channelMask);
|
||||||
}
|
_channelMask = 0;
|
||||||
|
|
||||||
flushWriteQueue();
|
flushWriteQueue();
|
||||||
}
|
}
|
||||||
@ -402,30 +404,30 @@ void Stream::handleInterrupt(void) {
|
|||||||
_configureIRQ();
|
_configureIRQ();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Stream::feed(const void *data, size_t count) {
|
size_t Stream::feed(const void *data, size_t length) {
|
||||||
util::CriticalSection sec;
|
util::CriticalSection sec;
|
||||||
|
|
||||||
auto ptr = reinterpret_cast<uintptr_t>(data);
|
auto ptr = reinterpret_cast<uintptr_t>(data);
|
||||||
auto chunkLength = getChunkLength();
|
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(
|
upload(
|
||||||
_getChunkOffset(_tail), reinterpret_cast<const void *>(ptr),
|
_getChunkOffset(_tail), reinterpret_cast<const void *>(ptr),
|
||||||
chunkLength, true
|
chunkLength, true
|
||||||
);
|
);
|
||||||
|
|
||||||
_tail = (_tail + 1) % numChunks;
|
|
||||||
ptr += chunkLength;
|
ptr += chunkLength;
|
||||||
|
_tail = (_tail + 1) % numChunks;
|
||||||
|
_bufferedChunks++;
|
||||||
}
|
}
|
||||||
|
|
||||||
_bufferedChunks += count;
|
|
||||||
|
|
||||||
if (isPlaying())
|
if (isPlaying())
|
||||||
_configureIRQ();
|
_configureIRQ();
|
||||||
|
|
||||||
flushWriteQueue();
|
flushWriteQueue();
|
||||||
return count;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stream::resetBuffer(void) {
|
void Stream::resetBuffer(void) {
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "common/util/templates.hpp"
|
||||||
#include "ps1/registers.h"
|
#include "ps1/registers.h"
|
||||||
|
|
||||||
namespace spu {
|
namespace spu {
|
||||||
@ -31,8 +32,9 @@ enum LoopFlag : uint8_t {
|
|||||||
LOOP_START = 1 << 2
|
LOOP_START = 1 << 2
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr uint32_t DUMMY_BLOCK_OFFSET = 0x1000;
|
static constexpr uint32_t DUMMY_BLOCK_OFFSET = 0x01000;
|
||||||
static constexpr uint32_t DUMMY_BLOCK_END = 0x1010;
|
static constexpr uint32_t DUMMY_BLOCK_END = 0x01010;
|
||||||
|
static constexpr uint32_t SPU_RAM_END = 0x7fff0;
|
||||||
|
|
||||||
static constexpr int NUM_CHANNELS = 24;
|
static constexpr int NUM_CHANNELS = 24;
|
||||||
static constexpr uint16_t MAX_VOLUME = 0x3fff;
|
static constexpr uint16_t MAX_VOLUME = 0x3fff;
|
||||||
@ -65,18 +67,37 @@ static inline void stopChannel(Channel ch) {
|
|||||||
stopChannels(1 << ch);
|
stopChannels(1 << ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t upload(uint32_t offset, const void *data, size_t length, bool wait);
|
size_t upload(
|
||||||
size_t download(uint32_t offset, void *data, size_t length, bool wait);
|
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 */
|
/* Sound class */
|
||||||
|
|
||||||
static constexpr size_t INTERLEAVED_VAG_BODY_OFFSET = 2048;
|
static constexpr size_t INTERLEAVED_VAG_BODY_OFFSET = 2048;
|
||||||
|
|
||||||
struct VAGHeader {
|
class VAGHeader {
|
||||||
public:
|
public:
|
||||||
uint32_t magic, version, interleave, length, sampleRate;
|
uint32_t magic, version, interleave, length, sampleRate;
|
||||||
uint16_t _reserved[5], channels;
|
uint16_t _reserved[5], channels;
|
||||||
char name[16];
|
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 {
|
class Sound {
|
||||||
@ -91,7 +112,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Sound(void);
|
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;
|
Channel play(uint16_t left, uint16_t right, Channel ch) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -125,6 +146,12 @@ public:
|
|||||||
|
|
||||||
return (_channelMask != 0);
|
return (_channelMask != 0);
|
||||||
}
|
}
|
||||||
|
inline bool isUnderrun(void) const {
|
||||||
|
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
||||||
|
|
||||||
|
return !_bufferedChunks;
|
||||||
|
}
|
||||||
|
|
||||||
inline size_t getChunkLength(void) const {
|
inline size_t getChunkLength(void) const {
|
||||||
return size_t(interleave) * size_t(channels);
|
return size_t(interleave) * size_t(channels);
|
||||||
}
|
}
|
||||||
@ -132,19 +159,19 @@ public:
|
|||||||
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
__atomic_signal_fence(__ATOMIC_ACQUIRE);
|
||||||
|
|
||||||
// The currently playing chunk cannot be overwritten.
|
// The currently playing chunk cannot be overwritten.
|
||||||
size_t playingChunk = isPlaying() ? 1 : 0;
|
size_t playingChunk = _channelMask ? 1 : 0;
|
||||||
return numChunks - (_bufferedChunks + playingChunk);
|
return numChunks - (_bufferedChunks + playingChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream(void);
|
Stream(void);
|
||||||
bool initFromVAGHeader(
|
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);
|
ChannelMask start(uint16_t left, uint16_t right, ChannelMask mask);
|
||||||
void stop(void);
|
void stop(void);
|
||||||
void handleInterrupt(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);
|
void resetBuffer(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -103,9 +103,9 @@ DeviceError ATADevice::_transfer(
|
|||||||
return error;
|
return error;
|
||||||
|
|
||||||
if (write)
|
if (write)
|
||||||
_writePIO(reinterpret_cast<const void *>(ptr), _SECTOR_LENGTH);
|
_writeData(reinterpret_cast<const void *>(ptr), _SECTOR_LENGTH);
|
||||||
else
|
else
|
||||||
_readPIO(reinterpret_cast<void *>(ptr), _SECTOR_LENGTH);
|
_readData(reinterpret_cast<void *>(ptr), _SECTOR_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
lba += chunkLength;
|
lba += chunkLength;
|
||||||
@ -131,7 +131,7 @@ DeviceError ATADevice::enumerate(void) {
|
|||||||
if (_waitForDRQ(_DETECT_TIMEOUT))
|
if (_waitForDRQ(_DETECT_TIMEOUT))
|
||||||
return NO_DRIVE;
|
return NO_DRIVE;
|
||||||
|
|
||||||
_readPIO(&block, sizeof(IDEIdentifyBlock));
|
_readData(&block, sizeof(IDEIdentifyBlock));
|
||||||
|
|
||||||
if (!block.validateChecksum())
|
if (!block.validateChecksum())
|
||||||
return CHECKSUM_MISMATCH;
|
return CHECKSUM_MISMATCH;
|
||||||
|
@ -131,7 +131,7 @@ DeviceError ATAPIDevice::_requestSense(void) {
|
|||||||
error = _waitForDRQ(_REQ_SENSE_TIMEOUT, true);
|
error = _waitForDRQ(_REQ_SENSE_TIMEOUT, true);
|
||||||
}
|
}
|
||||||
if (!error) {
|
if (!error) {
|
||||||
_writePIO(&packet, _getPacketLength());
|
_writeData(&packet, _getPacketLength());
|
||||||
|
|
||||||
error = _waitForDRQ(_REQ_SENSE_TIMEOUT, true);
|
error = _waitForDRQ(_REQ_SENSE_TIMEOUT, true);
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ DeviceError ATAPIDevice::_requestSense(void) {
|
|||||||
if (!error) {
|
if (!error) {
|
||||||
size_t length = _getCylinder();
|
size_t length = _getCylinder();
|
||||||
|
|
||||||
_readPIO(&lastSenseData, length);
|
_readData(&lastSenseData, length);
|
||||||
LOG_STORAGE("data ok, length=0x%x", length);
|
LOG_STORAGE("data ok, length=0x%x", length);
|
||||||
} else {
|
} else {
|
||||||
// If the request sense command fails, fall back to reading the sense
|
// If the request sense command fails, fall back to reading the sense
|
||||||
@ -185,7 +185,7 @@ DeviceError ATAPIDevice::_issuePacket(
|
|||||||
error = _waitForDRQ();
|
error = _waitForDRQ();
|
||||||
}
|
}
|
||||||
if (!error) {
|
if (!error) {
|
||||||
_writePIO(&packet, _getPacketLength());
|
_writeData(&packet, _getPacketLength());
|
||||||
|
|
||||||
error = dataLength
|
error = dataLength
|
||||||
? _waitForDRQ()
|
? _waitForDRQ()
|
||||||
@ -228,7 +228,7 @@ DeviceError ATAPIDevice::enumerate(void) {
|
|||||||
if (_waitForDRQ(_DETECT_TIMEOUT))
|
if (_waitForDRQ(_DETECT_TIMEOUT))
|
||||||
return NO_DRIVE;
|
return NO_DRIVE;
|
||||||
|
|
||||||
_readPIO(&block, sizeof(IDEIdentifyBlock));
|
_readData(&block, sizeof(IDEIdentifyBlock));
|
||||||
|
|
||||||
if (!block.validateChecksum())
|
if (!block.validateChecksum())
|
||||||
return CHECKSUM_MISMATCH;
|
return CHECKSUM_MISMATCH;
|
||||||
@ -298,7 +298,7 @@ DeviceError ATAPIDevice::read(void *data, uint64_t lba, size_t count) {
|
|||||||
|
|
||||||
size_t chunkLength = _getCylinder();
|
size_t chunkLength = _getCylinder();
|
||||||
|
|
||||||
_readPIO(reinterpret_cast<void *>(ptr), chunkLength);
|
_readData(reinterpret_cast<void *>(ptr), chunkLength);
|
||||||
ptr += chunkLength;
|
ptr += chunkLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "common/storage/atapi.hpp"
|
#include "common/storage/atapi.hpp"
|
||||||
#include "common/storage/device.hpp"
|
#include "common/storage/device.hpp"
|
||||||
#include "common/util/log.hpp"
|
#include "common/util/log.hpp"
|
||||||
|
#include "common/io.hpp"
|
||||||
#include "ps1/registers573.h"
|
#include "ps1/registers573.h"
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
|
|
||||||
@ -98,11 +99,11 @@ int IDEIdentifyBlock::getHighestPIOMode(void) const {
|
|||||||
|
|
||||||
/* IDE data transfers */
|
/* IDE data transfers */
|
||||||
|
|
||||||
static constexpr int _DMA_TIMEOUT = 10000;
|
void IDEDevice::_readData(void *data, size_t length) const {
|
||||||
|
#if 0
|
||||||
void IDEDevice::_readPIO(void *data, size_t length) const {
|
io::doDMARead(&SYS573_IDE_CS0_BASE[CS0_DATA], data, length);
|
||||||
length++;
|
#else
|
||||||
length /= 2;
|
length = (length + 1) / 2;
|
||||||
|
|
||||||
util::assertAligned<uint16_t>(data);
|
util::assertAligned<uint16_t>(data);
|
||||||
|
|
||||||
@ -110,11 +111,14 @@ void IDEDevice::_readPIO(void *data, size_t length) const {
|
|||||||
|
|
||||||
for (; length > 0; length--)
|
for (; length > 0; length--)
|
||||||
*(ptr++) = SYS573_IDE_CS0_BASE[CS0_DATA];
|
*(ptr++) = SYS573_IDE_CS0_BASE[CS0_DATA];
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDEDevice::_writePIO(const void *data, size_t length) const {
|
void IDEDevice::_writeData(const void *data, size_t length) const {
|
||||||
length++;
|
#if 0
|
||||||
length /= 2;
|
io::doDMAWrite(&SYS573_IDE_CS0_BASE[CS0_DATA], data, length);
|
||||||
|
#else
|
||||||
|
length = (length + 1) / 2;
|
||||||
|
|
||||||
util::assertAligned<uint16_t>(data);
|
util::assertAligned<uint16_t>(data);
|
||||||
|
|
||||||
@ -122,40 +126,7 @@ void IDEDevice::_writePIO(const void *data, size_t length) const {
|
|||||||
|
|
||||||
for (; length > 0; length--)
|
for (; length > 0; length--)
|
||||||
SYS573_IDE_CS0_BASE[CS0_DATA] = *(ptr++);
|
SYS573_IDE_CS0_BASE[CS0_DATA] = *(ptr++);
|
||||||
}
|
#endif
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* IDE status and error polling */
|
/* IDE status and error polling */
|
||||||
|
@ -271,10 +271,8 @@ protected:
|
|||||||
return util::concat2(_get(CS0_CYLINDER_L), _get(CS0_CYLINDER_H));
|
return util::concat2(_get(CS0_CYLINDER_L), _get(CS0_CYLINDER_H));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _readPIO(void *data, size_t length) const;
|
void _readData(void *data, size_t length) const;
|
||||||
void _writePIO(const void *data, size_t length) const;
|
void _writeData(const void *data, size_t length) const;
|
||||||
bool _readDMA(void *data, size_t length) const;
|
|
||||||
bool _writeDMA(const void *data, size_t length) const;
|
|
||||||
|
|
||||||
DeviceError _setup(const IDEIdentifyBlock &block);
|
DeviceError _setup(const IDEIdentifyBlock &block);
|
||||||
DeviceError _waitForIdle(
|
DeviceError _waitForIdle(
|
||||||
|
@ -82,10 +82,6 @@ template<typename T> static constexpr inline size_t countOf(T &array) {
|
|||||||
return sizeof(array) / sizeof(array[0]);
|
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) {
|
static constexpr inline uint16_t concat2(uint8_t low, uint8_t high) {
|
||||||
return low | (high << 8);
|
return low | (high << 8);
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,9 @@
|
|||||||
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
* 573in1. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "common/fs/fat.hpp"
|
#include "common/fs/fat.hpp"
|
||||||
#include "common/fs/file.hpp"
|
#include "common/fs/file.hpp"
|
||||||
@ -30,7 +32,9 @@
|
|||||||
#include "common/io.hpp"
|
#include "common/io.hpp"
|
||||||
#include "common/spu.hpp"
|
#include "common/spu.hpp"
|
||||||
#include "main/app/app.hpp"
|
#include "main/app/app.hpp"
|
||||||
|
#include "main/app/threads.hpp"
|
||||||
#include "main/cart/cart.hpp"
|
#include "main/cart/cart.hpp"
|
||||||
|
#include "main/workers/miscworkers.hpp"
|
||||||
#include "main/uibase.hpp"
|
#include "main/uibase.hpp"
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
|
|
||||||
@ -201,7 +205,7 @@ void FileIOManager::closeResourceFile(void) {
|
|||||||
|
|
||||||
/* App class */
|
/* 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;
|
static constexpr int _SPLASH_SCREEN_TIMEOUT = 5;
|
||||||
|
|
||||||
@ -239,18 +243,42 @@ void App::_unloadCartData(void) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::_setupInterrupts(void) {
|
void App::_updateOverlays(void) {
|
||||||
setInterruptHandler(
|
// Date and time overlay
|
||||||
util::forcedCast<ArgFunction>(&App::_interruptHandler), this
|
static char dateString[24];
|
||||||
);
|
util::Date date;
|
||||||
|
|
||||||
IRQ_MASK = 0
|
io::getRTCTime(date);
|
||||||
| (1 << IRQ_VSYNC)
|
date.toString(dateString);
|
||||||
| (1 << IRQ_SPU)
|
|
||||||
| (1 << IRQ_PIO);
|
_textOverlay.leftText = dateString;
|
||||||
enableInterrupts();
|
|
||||||
|
// 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]{
|
static const char *const _UI_SOUND_PATHS[ui::NUM_UI_SOUNDS]{
|
||||||
"assets/sounds/startup.vag", // ui::SOUND_STARTUP
|
"assets/sounds/startup.vag", // ui::SOUND_STARTUP
|
||||||
"assets/sounds/about.vag", // ui::SOUND_ABOUT_SCREEN
|
"assets/sounds/about.vag", // ui::SOUND_ABOUT_SCREEN
|
||||||
@ -275,7 +303,7 @@ void App::_loadResources(void) {
|
|||||||
uint32_t spuOffset = spu::DUMMY_BLOCK_END;
|
uint32_t spuOffset = spu::DUMMY_BLOCK_END;
|
||||||
|
|
||||||
for (int i = 0; i < ui::NUM_UI_SOUNDS; i++)
|
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) {
|
bool App::_createDataDirectory(void) {
|
||||||
@ -334,54 +362,74 @@ bool App::_takeScreenshot(void) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::_updateOverlays(void) {
|
/* App callbacks */
|
||||||
// Date and time overlay
|
|
||||||
static char dateString[24];
|
|
||||||
util::Date date;
|
|
||||||
|
|
||||||
io::getRTCTime(date);
|
void _appInterruptHandler(void *arg0, void *arg1) {
|
||||||
date.toString(dateString);
|
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 ((_workerStatus.status != WORKER_BUSY) || (_ctx.time > timeout))
|
if (app->_workerStatus.status != WORKER_REBOOT)
|
||||||
_splashOverlay.hide(_ctx);
|
io::clearWatchdog();
|
||||||
|
|
||||||
#ifdef ENABLE_LOG_BUFFER
|
|
||||||
// Log overlay
|
|
||||||
if (
|
if (
|
||||||
_ctx.buttons.released(ui::BTN_DEBUG) &&
|
gpu::isIdle() &&
|
||||||
!_ctx.buttons.longReleased(ui::BTN_DEBUG)
|
(app->_workerStatus.status != WORKER_BUSY_SUSPEND)
|
||||||
)
|
)
|
||||||
_logOverlay.toggle(_ctx);
|
switchThread(nullptr);
|
||||||
#endif
|
}
|
||||||
|
|
||||||
// Screenshot overlay
|
if (acknowledgeInterrupt(IRQ_SPU))
|
||||||
if (_ctx.buttons.longPressed(ui::BTN_DEBUG)) {
|
app->_audioStream.handleInterrupt();
|
||||||
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(
|
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;
|
util::CriticalSection sec;
|
||||||
|
|
||||||
_workerStatus.reset(next, goBack);
|
_workerStatus.reset(next, goBack);
|
||||||
_workerStack.allocate(_WORKER_STACK_SIZE);
|
_workerStack.allocate(_WORKER_STACK_SIZE);
|
||||||
|
assert(_workerStack.ptr);
|
||||||
|
|
||||||
_workerFunction = func;
|
|
||||||
auto stackBottom = _workerStack.as<uint8_t>();
|
auto stackBottom = _workerStack.as<uint8_t>();
|
||||||
|
|
||||||
initThread(
|
initThread(
|
||||||
&_workerThread, util::forcedCast<ArgFunction>(&App::_worker), this,
|
&_workerThread, &_workerMain, this, reinterpret_cast<void *>(func),
|
||||||
&stackBottom[(_WORKER_STACK_SIZE - 1) & ~7]
|
&stackBottom[(_WORKER_STACK_SIZE - 1) & ~7]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -389,39 +437,7 @@ void App::_runWorker(
|
|||||||
_ctx.show(_workerStatusScreen, false, playSound);
|
_ctx.show(_workerStatusScreen, false, playSound);
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::_worker(void) {
|
/* App main loop */
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] void App::run(const void *resourcePtr, size_t resourceLength) {
|
[[noreturn]] void App::run(const void *resourcePtr, size_t resourceLength) {
|
||||||
#ifdef ENABLE_LOG_BUFFER
|
#ifdef ENABLE_LOG_BUFFER
|
||||||
@ -448,7 +464,8 @@ void App::_interruptHandler(void) {
|
|||||||
#endif
|
#endif
|
||||||
_ctx.overlays[2] = &_screenshotOverlay;
|
_ctx.overlays[2] = &_screenshotOverlay;
|
||||||
|
|
||||||
_runWorker(&App::_startupWorker, _warningScreen);
|
_audioStream.init(&_workerThread);
|
||||||
|
_runWorker(&startupWorker, _warningScreen);
|
||||||
_setupInterrupts();
|
_setupInterrupts();
|
||||||
|
|
||||||
_splashOverlay.show(_ctx);
|
_splashOverlay.show(_ctx);
|
||||||
@ -459,7 +476,7 @@ void App::_interruptHandler(void) {
|
|||||||
_updateOverlays();
|
_updateOverlays();
|
||||||
|
|
||||||
_ctx.draw();
|
_ctx.draw();
|
||||||
switchThreadImmediate(&_workerThread);
|
_audioStream.yield();
|
||||||
_ctx.gpuCtx.flip();
|
_ctx.gpuCtx.flip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "main/app/modals.hpp"
|
#include "main/app/modals.hpp"
|
||||||
#include "main/app/romactions.hpp"
|
#include "main/app/romactions.hpp"
|
||||||
#include "main/app/tests.hpp"
|
#include "main/app/tests.hpp"
|
||||||
|
#include "main/app/threads.hpp"
|
||||||
#include "main/cart/cart.hpp"
|
#include "main/cart/cart.hpp"
|
||||||
#include "main/cart/cartdata.hpp"
|
#include "main/cart/cartdata.hpp"
|
||||||
#include "main/cart/cartio.hpp"
|
#include "main/cart/cartio.hpp"
|
||||||
@ -101,47 +102,42 @@ public:
|
|||||||
/* App class */
|
/* App class */
|
||||||
|
|
||||||
class App {
|
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 WorkerStatusScreen;
|
||||||
friend class MessageScreen;
|
friend class MessageScreen;
|
||||||
friend class ConfirmScreen;
|
friend class ConfirmScreen;
|
||||||
friend class FilePickerScreen;
|
friend class FilePickerScreen;
|
||||||
friend class FileBrowserScreen;
|
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;
|
WorkerStatusScreen _workerStatusScreen;
|
||||||
MessageScreen _messageScreen;
|
MessageScreen _messageScreen;
|
||||||
ConfirmScreen _confirmScreen;
|
ConfirmScreen _confirmScreen;
|
||||||
@ -149,17 +145,32 @@ private:
|
|||||||
FileBrowserScreen _fileBrowserScreen;
|
FileBrowserScreen _fileBrowserScreen;
|
||||||
|
|
||||||
// main.cpp
|
// main.cpp
|
||||||
|
friend class WarningScreen;
|
||||||
|
friend class AutobootScreen;
|
||||||
|
friend class ButtonMappingScreen;
|
||||||
|
friend class MainMenuScreen;
|
||||||
|
|
||||||
WarningScreen _warningScreen;
|
WarningScreen _warningScreen;
|
||||||
AutobootScreen _autobootScreen;
|
AutobootScreen _autobootScreen;
|
||||||
ButtonMappingScreen _buttonMappingScreen;
|
ButtonMappingScreen _buttonMappingScreen;
|
||||||
MainMenuScreen _mainMenuScreen;
|
MainMenuScreen _mainMenuScreen;
|
||||||
|
|
||||||
// cartunlock.cpp
|
// cartunlock.cpp
|
||||||
|
friend class CartInfoScreen;
|
||||||
|
friend class UnlockKeyScreen;
|
||||||
|
friend class KeyEntryScreen;
|
||||||
|
|
||||||
CartInfoScreen _cartInfoScreen;
|
CartInfoScreen _cartInfoScreen;
|
||||||
UnlockKeyScreen _unlockKeyScreen;
|
UnlockKeyScreen _unlockKeyScreen;
|
||||||
KeyEntryScreen _keyEntryScreen;
|
KeyEntryScreen _keyEntryScreen;
|
||||||
|
|
||||||
// cartactions.cpp
|
// cartactions.cpp
|
||||||
|
friend class CartActionsScreen;
|
||||||
|
friend class QRCodeScreen;
|
||||||
|
friend class HexdumpScreen;
|
||||||
|
friend class ReflashGameScreen;
|
||||||
|
friend class SystemIDEntryScreen;
|
||||||
|
|
||||||
CartActionsScreen _cartActionsScreen;
|
CartActionsScreen _cartActionsScreen;
|
||||||
QRCodeScreen _qrCodeScreen;
|
QRCodeScreen _qrCodeScreen;
|
||||||
HexdumpScreen _hexdumpScreen;
|
HexdumpScreen _hexdumpScreen;
|
||||||
@ -167,12 +178,24 @@ private:
|
|||||||
SystemIDEntryScreen _systemIDEntryScreen;
|
SystemIDEntryScreen _systemIDEntryScreen;
|
||||||
|
|
||||||
// romactions.cpp
|
// romactions.cpp
|
||||||
|
friend class StorageInfoScreen;
|
||||||
|
friend class StorageActionsScreen;
|
||||||
|
friend class CardSizeScreen;
|
||||||
|
friend class ChecksumScreen;
|
||||||
|
|
||||||
StorageInfoScreen _storageInfoScreen;
|
StorageInfoScreen _storageInfoScreen;
|
||||||
StorageActionsScreen _storageActionsScreen;
|
StorageActionsScreen _storageActionsScreen;
|
||||||
CardSizeScreen _cardSizeScreen;
|
CardSizeScreen _cardSizeScreen;
|
||||||
ChecksumScreen _checksumScreen;
|
ChecksumScreen _checksumScreen;
|
||||||
|
|
||||||
// tests.cpp
|
// tests.cpp
|
||||||
|
friend class TestMenuScreen;
|
||||||
|
friend class JAMMATestScreen;
|
||||||
|
friend class AudioTestScreen;
|
||||||
|
friend class TestPatternScreen;
|
||||||
|
friend class ColorIntensityScreen;
|
||||||
|
friend class GeometryScreen;
|
||||||
|
|
||||||
TestMenuScreen _testMenuScreen;
|
TestMenuScreen _testMenuScreen;
|
||||||
JAMMATestScreen _jammaTestScreen;
|
JAMMATestScreen _jammaTestScreen;
|
||||||
AudioTestScreen _audioTestScreen;
|
AudioTestScreen _audioTestScreen;
|
||||||
@ -180,6 +203,12 @@ private:
|
|||||||
GeometryScreen _geometryScreen;
|
GeometryScreen _geometryScreen;
|
||||||
|
|
||||||
// misc.cpp
|
// misc.cpp
|
||||||
|
friend class IDEInfoScreen;
|
||||||
|
friend class RTCTimeScreen;
|
||||||
|
friend class LanguageScreen;
|
||||||
|
friend class ResolutionScreen;
|
||||||
|
friend class AboutScreen;
|
||||||
|
|
||||||
IDEInfoScreen _ideInfoScreen;
|
IDEInfoScreen _ideInfoScreen;
|
||||||
RTCTimeScreen _rtcTimeScreen;
|
RTCTimeScreen _rtcTimeScreen;
|
||||||
LanguageScreen _languageScreen;
|
LanguageScreen _languageScreen;
|
||||||
@ -198,63 +227,37 @@ private:
|
|||||||
ui::Context &_ctx;
|
ui::Context &_ctx;
|
||||||
fs::StringTable _stringTable;
|
fs::StringTable _stringTable;
|
||||||
FileIOManager _fileIO;
|
FileIOManager _fileIO;
|
||||||
|
AudioStreamManager _audioStream;
|
||||||
|
|
||||||
|
Thread _workerThread;
|
||||||
|
util::Data _workerStack;
|
||||||
|
WorkerStatus _workerStatus;
|
||||||
|
|
||||||
cart::CartDump _cartDump;
|
cart::CartDump _cartDump;
|
||||||
cart::ROMHeaderDump _romHeaderDump;
|
cart::ROMHeaderDump _romHeaderDump;
|
||||||
cart::CartDB _cartDB;
|
cart::CartDB _cartDB;
|
||||||
cart::ROMHeaderDB _romHeaderDB;
|
cart::ROMHeaderDB _romHeaderDB;
|
||||||
|
|
||||||
Thread _workerThread;
|
|
||||||
util::Data _workerStack;
|
|
||||||
WorkerStatus _workerStatus;
|
|
||||||
bool (App::*_workerFunction)(void);
|
|
||||||
|
|
||||||
cart::Driver *_cartDriver;
|
cart::Driver *_cartDriver;
|
||||||
cart::CartParser *_cartParser;
|
cart::CartParser *_cartParser;
|
||||||
const cart::CartDBEntry *_identified, *_selectedEntry;
|
const cart::CartDBEntry *_identified, *_selectedEntry;
|
||||||
|
|
||||||
void _unloadCartData(void);
|
void _unloadCartData(void);
|
||||||
void _setupInterrupts(void);
|
void _updateOverlays(void);
|
||||||
|
|
||||||
void _loadResources(void);
|
void _loadResources(void);
|
||||||
bool _createDataDirectory(void);
|
bool _createDataDirectory(void);
|
||||||
bool _getNumberedPath(
|
bool _getNumberedPath(
|
||||||
char *output, size_t length, const char *path, int maxIndex = 9999
|
char *output, size_t length, const char *path, int maxIndex = 9999
|
||||||
);
|
);
|
||||||
bool _takeScreenshot(void);
|
bool _takeScreenshot(void);
|
||||||
void _updateOverlays(void);
|
|
||||||
|
void _setupInterrupts(void);
|
||||||
void _runWorker(
|
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
|
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:
|
public:
|
||||||
App(ui::Context &ctx);
|
App(ui::Context &ctx);
|
||||||
~App(void);
|
~App(void);
|
||||||
@ -266,5 +269,5 @@ public:
|
|||||||
#define STR(id) (APP->_stringTable.get(id ## _h))
|
#define STR(id) (APP->_stringTable.get(id ## _h))
|
||||||
#define STRH(id) (APP->_stringTable.get(id))
|
#define STRH(id) (APP->_stringTable.get(id))
|
||||||
|
|
||||||
#define WSTR(id) (_stringTable.get(id ## _h))
|
#define WSTR(id) (app._stringTable.get(id ## _h))
|
||||||
#define WSTRH(id) (_stringTable.get(id))
|
#define WSTRH(id) (app._stringTable.get(id))
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "common/util/templates.hpp"
|
#include "common/util/templates.hpp"
|
||||||
#include "main/app/cartactions.hpp"
|
#include "main/app/cartactions.hpp"
|
||||||
#include "main/app/app.hpp"
|
#include "main/app/app.hpp"
|
||||||
|
#include "main/workers/cartworkers.hpp"
|
||||||
#include "main/uibase.hpp"
|
#include "main/uibase.hpp"
|
||||||
|
|
||||||
/* Unlocked cartridge screens */
|
/* Unlocked cartridge screens */
|
||||||
@ -80,7 +81,7 @@ void CartActionsScreen::qrDump(ui::Context &ctx) {
|
|||||||
if (APP->_qrCodeScreen.valid)
|
if (APP->_qrCodeScreen.valid)
|
||||||
ctx.show(APP->_qrCodeScreen, false, true);
|
ctx.show(APP->_qrCodeScreen, false, true);
|
||||||
else
|
else
|
||||||
APP->_runWorker(&App::_qrCodeWorker, APP->_qrCodeScreen, false, true);
|
APP->_runWorker(&qrCodeWorker, APP->_qrCodeScreen, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CartActionsScreen::hddDump(ui::Context &ctx) {
|
void CartActionsScreen::hddDump(ui::Context &ctx) {
|
||||||
@ -88,7 +89,7 @@ void CartActionsScreen::hddDump(ui::Context &ctx) {
|
|||||||
&(APP->_cartInfoScreen);
|
&(APP->_cartInfoScreen);
|
||||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
|
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) {
|
void CartActionsScreen::hexdump(ui::Context &ctx) {
|
||||||
@ -111,7 +112,7 @@ void CartActionsScreen::hddRestore(ui::Context &ctx) {
|
|||||||
&(APP->_fileBrowserScreen);
|
&(APP->_fileBrowserScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_cartRestoreWorker, APP->_cartInfoScreen, true, true
|
cartRestoreWorker, APP->_cartInfoScreen, true, true
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STR("CartActionsScreen.hddRestore.confirm")
|
STR("CartActionsScreen.hddRestore.confirm")
|
||||||
@ -131,9 +132,7 @@ void CartActionsScreen::erase(ui::Context &ctx) {
|
|||||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
|
APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
|
||||||
&(APP->_cartActionsScreen);
|
&(APP->_cartActionsScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(&cartEraseWorker, APP->_cartInfoScreen, true, true);
|
||||||
&App::_cartEraseWorker, APP->_cartInfoScreen, true, true
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
STR("CartActionsScreen.erase.confirm")
|
STR("CartActionsScreen.erase.confirm")
|
||||||
);
|
);
|
||||||
@ -153,7 +152,7 @@ void CartActionsScreen::resetSystemID(ui::Context &ctx) {
|
|||||||
&(APP->_cartActionsScreen);
|
&(APP->_cartActionsScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_cartWriteWorker, APP->_cartInfoScreen, true, true
|
&cartWriteWorker, APP->_cartInfoScreen, true, true
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STR("CartActionsScreen.resetSystemID.confirm")
|
STR("CartActionsScreen.resetSystemID.confirm")
|
||||||
@ -184,7 +183,7 @@ void CartActionsScreen::matchSystemID(ui::Context &ctx) {
|
|||||||
&(APP->_cartActionsScreen);
|
&(APP->_cartActionsScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_cartWriteWorker, APP->_cartInfoScreen, true, true
|
&cartWriteWorker, APP->_cartInfoScreen, true, true
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STR("CartActionsScreen.matchSystemID.confirm")
|
STR("CartActionsScreen.matchSystemID.confirm")
|
||||||
@ -314,8 +313,7 @@ void ReflashGameScreen::update(ui::Context &ctx) {
|
|||||||
&(APP->_reflashGameScreen);
|
&(APP->_reflashGameScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_cartReflashWorker, APP->_cartInfoScreen, true,
|
&cartReflashWorker, APP->_cartInfoScreen, true, true
|
||||||
true
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STR("CartActionsScreen.reflash.confirm")
|
STR("CartActionsScreen.reflash.confirm")
|
||||||
@ -359,8 +357,7 @@ void SystemIDEntryScreen::update(ui::Context &ctx) {
|
|||||||
&(APP->_systemIDEntryScreen);
|
&(APP->_systemIDEntryScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_cartWriteWorker, APP->_cartInfoScreen, true,
|
&cartWriteWorker, APP->_cartInfoScreen, true, true
|
||||||
true
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STR("CartActionsScreen.editSystemID.confirm")
|
STR("CartActionsScreen.editSystemID.confirm")
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "main/app/app.hpp"
|
#include "main/app/app.hpp"
|
||||||
#include "main/app/cartunlock.hpp"
|
#include "main/app/cartunlock.hpp"
|
||||||
#include "main/cart/cartdata.hpp"
|
#include "main/cart/cartdata.hpp"
|
||||||
|
#include "main/workers/cartworkers.hpp"
|
||||||
#include "main/uibase.hpp"
|
#include "main/uibase.hpp"
|
||||||
|
|
||||||
/* Pre-unlock cartridge screens */
|
/* Pre-unlock cartridge screens */
|
||||||
@ -331,8 +332,7 @@ void UnlockKeyScreen::update(ui::Context &ctx) {
|
|||||||
&(APP->_unlockKeyScreen);
|
&(APP->_unlockKeyScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_cartUnlockWorker, APP->_cartInfoScreen, false,
|
&cartUnlockWorker, APP->_cartInfoScreen, false, true
|
||||||
true
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STRH(_UNLOCK_WARNINGS[dump.chipType])
|
STRH(_UNLOCK_WARNINGS[dump.chipType])
|
||||||
@ -381,8 +381,7 @@ void KeyEntryScreen::update(ui::Context &ctx) {
|
|||||||
&(APP->_keyEntryScreen);
|
&(APP->_keyEntryScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_cartUnlockWorker, APP->_cartInfoScreen, false,
|
&cartUnlockWorker, APP->_cartInfoScreen, false, true
|
||||||
true
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STRH(_UNLOCK_WARNINGS[dump.chipType])
|
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,6 +19,8 @@
|
|||||||
#include "common/util/templates.hpp"
|
#include "common/util/templates.hpp"
|
||||||
#include "main/app/app.hpp"
|
#include "main/app/app.hpp"
|
||||||
#include "main/app/main.hpp"
|
#include "main/app/main.hpp"
|
||||||
|
#include "main/workers/cartworkers.hpp"
|
||||||
|
#include "main/workers/miscworkers.hpp"
|
||||||
#include "main/uibase.hpp"
|
#include "main/uibase.hpp"
|
||||||
|
|
||||||
/* Main menu screens */
|
/* Main menu screens */
|
||||||
@ -63,13 +65,7 @@ void WarningScreen::update(ui::Context &ctx) {
|
|||||||
_buttons[0] = STR("WarningScreen.ok");
|
_buttons[0] = STR("WarningScreen.ok");
|
||||||
|
|
||||||
if (ctx.buttons.pressed(ui::BTN_START))
|
if (ctx.buttons.pressed(ui::BTN_START))
|
||||||
#ifdef ENABLE_AUTOBOOT
|
|
||||||
ctx.show(APP->_buttonMappingScreen, false, true);
|
ctx.show(APP->_buttonMappingScreen, false, true);
|
||||||
#else
|
|
||||||
APP->_runWorker(
|
|
||||||
&App::_ideInitWorker, APP->_buttonMappingScreen, false, true
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutobootScreen::show(ui::Context &ctx, bool goBack) {
|
void AutobootScreen::show(ui::Context &ctx, bool goBack) {
|
||||||
@ -102,7 +98,7 @@ void AutobootScreen::update(ui::Context &ctx) {
|
|||||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
|
APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
|
||||||
&(APP->_warningScreen);
|
&(APP->_warningScreen);
|
||||||
|
|
||||||
APP->_runWorker(&App::_executableWorker, APP->_mainMenuScreen, true);
|
APP->_runWorker(&executableWorker, APP->_mainMenuScreen, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,9 +210,7 @@ void MainMenuScreen::cartInfo(ui::Context &ctx) {
|
|||||||
if (APP->_cartDriver)
|
if (APP->_cartDriver)
|
||||||
ctx.show(APP->_cartInfoScreen, false, true);
|
ctx.show(APP->_cartInfoScreen, false, true);
|
||||||
else
|
else
|
||||||
APP->_runWorker(
|
APP->_runWorker(&cartDetectWorker, APP->_cartInfoScreen, false, true);
|
||||||
&App::_cartDetectWorker, APP->_cartInfoScreen, false, true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainMenuScreen::storageInfo(ui::Context &ctx) {
|
void MainMenuScreen::storageInfo(ui::Context &ctx) {
|
||||||
@ -236,7 +230,7 @@ void MainMenuScreen::runExecutable(ui::Context &ctx) {
|
|||||||
&(APP->_fileBrowserScreen);
|
&(APP->_fileBrowserScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_executableWorker, APP->_mainMenuScreen, true, true
|
&executableWorker, APP->_mainMenuScreen, true, true
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STR("MainMenuScreen.runExecutable.filePrompt")
|
STR("MainMenuScreen.runExecutable.filePrompt")
|
||||||
@ -269,11 +263,11 @@ void MainMenuScreen::ejectCD(ui::Context &ctx) {
|
|||||||
APP->_messageScreen.previousScreens[MESSAGE_SUCCESS] = this;
|
APP->_messageScreen.previousScreens[MESSAGE_SUCCESS] = this;
|
||||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = 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) {
|
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) {
|
void MainMenuScreen::show(ui::Context &ctx, bool goBack) {
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "common/defs.hpp"
|
#include "common/defs.hpp"
|
||||||
#include "main/app/app.hpp"
|
#include "main/app/app.hpp"
|
||||||
#include "main/app/modals.hpp"
|
#include "main/app/modals.hpp"
|
||||||
|
#include "main/workers/miscworkers.hpp"
|
||||||
#include "main/uibase.hpp"
|
#include "main/uibase.hpp"
|
||||||
#include "main/uicommon.hpp"
|
#include "main/uicommon.hpp"
|
||||||
|
|
||||||
@ -206,7 +207,7 @@ void FilePickerScreen::reloadAndShow(ui::Context &ctx) {
|
|||||||
|
|
||||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
|
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
|
||||||
|
|
||||||
APP->_runWorker(&App::_fileInitWorker, *this, false, true);
|
APP->_runWorker(&fileInitWorker, *this, false, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
#include "common/rom.hpp"
|
#include "common/rom.hpp"
|
||||||
#include "main/app/app.hpp"
|
#include "main/app/app.hpp"
|
||||||
#include "main/app/romactions.hpp"
|
#include "main/app/romactions.hpp"
|
||||||
|
#include "main/workers/miscworkers.hpp"
|
||||||
|
#include "main/workers/romworkers.hpp"
|
||||||
#include "main/uibase.hpp"
|
#include "main/uibase.hpp"
|
||||||
#include "main/uicommon.hpp"
|
#include "main/uicommon.hpp"
|
||||||
|
|
||||||
@ -266,7 +268,7 @@ const char *StorageActionsScreen::_getItemName(
|
|||||||
|
|
||||||
void StorageActionsScreen::runExecutable(ui::Context &ctx, size_t length) {
|
void StorageActionsScreen::runExecutable(ui::Context &ctx, size_t length) {
|
||||||
if (selectedRegion->getBootExecutableHeader()) {
|
if (selectedRegion->getBootExecutableHeader()) {
|
||||||
APP->_runWorker(&App::_executableWorker, *this, true, true);
|
APP->_runWorker(&executableWorker, *this, true, true);
|
||||||
} else {
|
} else {
|
||||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
|
APP->_messageScreen.previousScreens[MESSAGE_ERROR] = this;
|
||||||
APP->_messageScreen.setMessage(
|
APP->_messageScreen.setMessage(
|
||||||
@ -282,7 +284,7 @@ void StorageActionsScreen::checksum(ui::Context &ctx, size_t length) {
|
|||||||
ctx.show(APP->_checksumScreen, false, true);
|
ctx.show(APP->_checksumScreen, false, true);
|
||||||
else
|
else
|
||||||
APP->_runWorker(
|
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->_storageActionsScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_romDumpWorker, APP->_messageScreen, false, true
|
&romDumpWorker, APP->_messageScreen, false, true
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STR("StorageActionsScreen.dump.confirm")
|
STR("StorageActionsScreen.dump.confirm")
|
||||||
@ -325,7 +327,7 @@ void StorageActionsScreen::restore(ui::Context &ctx, size_t length) {
|
|||||||
&(APP->_fileBrowserScreen);
|
&(APP->_fileBrowserScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_romRestoreWorker, APP->_messageScreen, false, true
|
&romRestoreWorker, APP->_messageScreen, false, true
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STR("StorageActionsScreen.restore.confirm")
|
STR("StorageActionsScreen.restore.confirm")
|
||||||
@ -345,9 +347,7 @@ void StorageActionsScreen::erase(ui::Context &ctx, size_t length) {
|
|||||||
APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
|
APP->_messageScreen.previousScreens[MESSAGE_ERROR] =
|
||||||
&(APP->_storageActionsScreen);
|
&(APP->_storageActionsScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(&romEraseWorker, APP->_messageScreen, false, true);
|
||||||
&App::_romEraseWorker, APP->_messageScreen, false, true
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
STR("StorageActionsScreen.erase.confirm")
|
STR("StorageActionsScreen.erase.confirm")
|
||||||
);
|
);
|
||||||
@ -375,8 +375,7 @@ void StorageActionsScreen::installExecutable(ui::Context &ctx, size_t length) {
|
|||||||
&(APP->_fileBrowserScreen);
|
&(APP->_fileBrowserScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_flashExecutableWriteWorker, APP->_messageScreen, false,
|
&flashExecutableWriteWorker, APP->_messageScreen, false, true
|
||||||
true
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STR("StorageActionsScreen.installExecutable.confirm")
|
STR("StorageActionsScreen.installExecutable.confirm")
|
||||||
@ -395,8 +394,7 @@ void StorageActionsScreen::resetFlashHeader(ui::Context &ctx, size_t length) {
|
|||||||
&(APP->_storageActionsScreen);
|
&(APP->_storageActionsScreen);
|
||||||
|
|
||||||
APP->_runWorker(
|
APP->_runWorker(
|
||||||
&App::_flashHeaderWriteWorker, APP->_storageInfoScreen, true,
|
&flashHeaderWriteWorker, APP->_storageInfoScreen, true, true
|
||||||
true
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
STR("StorageActionsScreen.resetFlashHeader.confirm")
|
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];
|
spu::Sound sounds[NUM_UI_SOUNDS];
|
||||||
|
|
||||||
ButtonState buttons;
|
ButtonState buttons;
|
||||||
spu::Stream audioStream;
|
|
||||||
|
|
||||||
int time;
|
int time;
|
||||||
void *screenData; // Opaque, can be accessed by screens
|
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/io.hpp"
|
||||||
#include "common/rom.hpp"
|
#include "common/rom.hpp"
|
||||||
#include "main/app/app.hpp"
|
#include "main/app/app.hpp"
|
||||||
|
#include "main/workers/miscworkers.hpp"
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
|
|
||||||
static const rom::Region *const _AUTOBOOT_REGIONS[]{
|
static const rom::Region *const _AUTOBOOT_REGIONS[]{
|
||||||
@ -43,25 +44,25 @@ static const char *const _AUTOBOOT_PATHS[][2]{
|
|||||||
{ "hdd:/noboot.txt", "hdd:/psx.exe" }
|
{ "hdd:/noboot.txt", "hdd:/psx.exe" }
|
||||||
};
|
};
|
||||||
|
|
||||||
bool App::_startupWorker(void) {
|
bool startupWorker(App &app) {
|
||||||
_workerStatus.update(0, 1, WSTR("App.startupWorker.ideInit"));
|
app._workerStatus.update(0, 1, WSTR("App.startupWorker.ideInit"));
|
||||||
_fileIO.initIDE();
|
app._fileIO.initIDE();
|
||||||
|
|
||||||
_fileInitWorker();
|
fileInitWorker(app);
|
||||||
|
|
||||||
#ifdef ENABLE_AUTOBOOT
|
#ifdef ENABLE_AUTOBOOT
|
||||||
// Only try to autoboot if DIP switch 1 is on.
|
// Only try to autoboot if DIP switch 1 is on.
|
||||||
if (io::getDIPSwitch(0)) {
|
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)) {
|
if (io::getDIPSwitch(3)) {
|
||||||
for (auto region : _AUTOBOOT_REGIONS) {
|
for (auto region : _AUTOBOOT_REGIONS) {
|
||||||
if (!region->getBootExecutableHeader())
|
if (!region->getBootExecutableHeader())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
_storageActionsScreen.selectedRegion = region;
|
app._storageActionsScreen.selectedRegion = region;
|
||||||
|
|
||||||
_workerStatus.setNextScreen(_autobootScreen);
|
app._workerStatus.setNextScreen(app._autobootScreen);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,18 +70,18 @@ bool App::_startupWorker(void) {
|
|||||||
for (auto path : _AUTOBOOT_PATHS) {
|
for (auto path : _AUTOBOOT_PATHS) {
|
||||||
fs::FileInfo info;
|
fs::FileInfo info;
|
||||||
|
|
||||||
if (_fileIO.vfs.getFileInfo(info, path[0]))
|
if (app._fileIO.vfs.getFileInfo(info, path[0]))
|
||||||
continue;
|
continue;
|
||||||
if (!_fileIO.vfs.getFileInfo(info, path[1]))
|
if (!app._fileIO.vfs.getFileInfo(info, path[1]))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
_storageActionsScreen.selectedRegion = nullptr;
|
app._storageActionsScreen.selectedRegion = nullptr;
|
||||||
__builtin_strncpy(
|
__builtin_strncpy(
|
||||||
_fileBrowserScreen.selectedPath, path[1],
|
app._fileBrowserScreen.selectedPath, path[1],
|
||||||
sizeof(_fileBrowserScreen.selectedPath)
|
sizeof(app._fileBrowserScreen.selectedPath)
|
||||||
);
|
);
|
||||||
|
|
||||||
_workerStatus.setNextScreen(_autobootScreen);
|
app._workerStatus.setNextScreen(app._autobootScreen);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,17 +90,17 @@ bool App::_startupWorker(void) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool App::_fileInitWorker(void) {
|
bool fileInitWorker(App &app) {
|
||||||
_workerStatus.update(0, 3, WSTR("App.fileInitWorker.unmount"));
|
app._workerStatus.update(0, 3, WSTR("App.fileInitWorker.unmount"));
|
||||||
_fileIO.closeResourceFile();
|
app._fileIO.closeResourceFile();
|
||||||
_fileIO.unmountIDE();
|
app._fileIO.unmountIDE();
|
||||||
|
|
||||||
_workerStatus.update(1, 3, WSTR("App.fileInitWorker.mount"));
|
app._workerStatus.update(1, 3, WSTR("App.fileInitWorker.mount"));
|
||||||
_fileIO.mountIDE();
|
app._fileIO.mountIDE();
|
||||||
|
|
||||||
_workerStatus.update(2, 3, WSTR("App.fileInitWorker.loadResources"));
|
app._workerStatus.update(2, 3, WSTR("App.fileInitWorker.loadResources"));
|
||||||
if (_fileIO.loadResourceFile(EXTERNAL_DATA_DIR "/resource.zip"))
|
if (app._fileIO.loadResourceFile(EXTERNAL_DATA_DIR "/resource.zip"))
|
||||||
_loadResources();
|
app._loadResources();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -132,11 +133,11 @@ static const char *const _DEVICE_TYPES[]{
|
|||||||
"atapi" // storage::ATAPI
|
"atapi" // storage::ATAPI
|
||||||
};
|
};
|
||||||
|
|
||||||
bool App::_executableWorker(void) {
|
bool executableWorker(App &app) {
|
||||||
_workerStatus.update(0, 2, WSTR("App.executableWorker.init"));
|
app._workerStatus.update(0, 2, WSTR("App.executableWorker.init"));
|
||||||
|
|
||||||
auto region = _storageActionsScreen.selectedRegion;
|
auto region = app._storageActionsScreen.selectedRegion;
|
||||||
const char *path = _fileBrowserScreen.selectedPath;
|
const char *path = app._fileBrowserScreen.selectedPath;
|
||||||
|
|
||||||
const char *deviceType;
|
const char *deviceType;
|
||||||
int deviceIndex;
|
int deviceIndex;
|
||||||
@ -151,7 +152,7 @@ bool App::_executableWorker(void) {
|
|||||||
} else {
|
} else {
|
||||||
__builtin_memset(header.magic, 0, sizeof(header.magic));
|
__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) {
|
if (file) {
|
||||||
file->read(&header, sizeof(header));
|
file->read(&header, sizeof(header));
|
||||||
@ -160,15 +161,15 @@ bool App::_executableWorker(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!header.validateMagic()) {
|
if (!header.validateMagic()) {
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.executableWorker.fileError"), path
|
MESSAGE_ERROR, WSTR("App.executableWorker.fileError"), path
|
||||||
);
|
);
|
||||||
_workerStatus.setNextScreen(_messageScreen);
|
app._workerStatus.setNextScreen(app._messageScreen);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceIndex = path[3] - '0';
|
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;
|
auto executableEnd = header.textOffset + header.textLength;
|
||||||
@ -197,10 +198,10 @@ bool App::_executableWorker(void) {
|
|||||||
// appropriate location.
|
// appropriate location.
|
||||||
util::Data binary;
|
util::Data binary;
|
||||||
|
|
||||||
if (!_fileIO.resource.loadData(binary, launcher.path))
|
if (!app._fileIO.resource.loadData(binary, launcher.path))
|
||||||
continue;
|
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>();
|
auto launcherHeader = binary.as<const util::ExecutableHeader>();
|
||||||
|
|
||||||
util::ExecutableLoader loader(
|
util::ExecutableLoader loader(
|
||||||
@ -232,7 +233,7 @@ bool App::_executableWorker(void) {
|
|||||||
// through the command line.
|
// through the command line.
|
||||||
fs::FileFragmentTable fragments;
|
fs::FileFragmentTable fragments;
|
||||||
|
|
||||||
_fileIO.vfs.getFileFragments(fragments, path);
|
app._fileIO.vfs.getFileFragments(fragments, path);
|
||||||
|
|
||||||
auto fragment = fragments.as<const fs::FileFragment>();
|
auto fragment = fragments.as<const fs::FileFragment>();
|
||||||
auto count = fragments.getNumFragments();
|
auto count = fragments.getNumFragments();
|
||||||
@ -245,11 +246,11 @@ bool App::_executableWorker(void) {
|
|||||||
|
|
||||||
fragments.destroy();
|
fragments.destroy();
|
||||||
|
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.executableWorker.fragmentError"),
|
MESSAGE_ERROR, WSTR("App.executableWorker.fragmentError"),
|
||||||
path, count, i
|
path, count, i
|
||||||
);
|
);
|
||||||
_workerStatus.setNextScreen(_messageScreen);
|
app._workerStatus.setNextScreen(app._messageScreen);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,9 +259,9 @@ bool App::_executableWorker(void) {
|
|||||||
|
|
||||||
// All destructors must be invoked manually as we are not returning to
|
// All destructors must be invoked manually as we are not returning to
|
||||||
// main() before starting the new executable.
|
// main() before starting the new executable.
|
||||||
_unloadCartData();
|
app._unloadCartData();
|
||||||
_fileIO.closeResourceFile();
|
app._fileIO.closeResourceFile();
|
||||||
_fileIO.unmountIDE();
|
app._fileIO.unmountIDE();
|
||||||
|
|
||||||
LOG_APP("jumping to launcher");
|
LOG_APP("jumping to launcher");
|
||||||
uninstallExceptionHandler();
|
uninstallExceptionHandler();
|
||||||
@ -269,18 +270,18 @@ bool App::_executableWorker(void) {
|
|||||||
loader.run();
|
loader.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.executableWorker.addressError"),
|
MESSAGE_ERROR, WSTR("App.executableWorker.addressError"),
|
||||||
header.textOffset, executableEnd - 1, stackTop
|
header.textOffset, executableEnd - 1, stackTop
|
||||||
);
|
);
|
||||||
_workerStatus.setNextScreen(_messageScreen);
|
app._workerStatus.setNextScreen(app._messageScreen);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool App::_atapiEjectWorker(void) {
|
bool atapiEjectWorker(App &app) {
|
||||||
_workerStatus.update(0, 1, WSTR("App.atapiEjectWorker.eject"));
|
app._workerStatus.update(0, 1, WSTR("App.atapiEjectWorker.eject"));
|
||||||
|
|
||||||
for (auto dev : _fileIO.ideDevices) {
|
for (auto dev : app._fileIO.ideDevices) {
|
||||||
if (!dev)
|
if (!dev)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -294,31 +295,31 @@ bool App::_atapiEjectWorker(void) {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.atapiEjectWorker.ejectError"),
|
MESSAGE_ERROR, WSTR("App.atapiEjectWorker.ejectError"),
|
||||||
storage::getErrorString(error)
|
storage::getErrorString(error)
|
||||||
);
|
);
|
||||||
_workerStatus.setNextScreen(_messageScreen);
|
app._workerStatus.setNextScreen(app._messageScreen);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.atapiEjectWorker.noDrive")
|
MESSAGE_ERROR, WSTR("App.atapiEjectWorker.noDrive")
|
||||||
);
|
);
|
||||||
_workerStatus.setNextScreen(_messageScreen);
|
app._workerStatus.setNextScreen(app._messageScreen);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool App::_rebootWorker(void) {
|
bool rebootWorker(App &app) {
|
||||||
_workerStatus.update(0, 1, WSTR("App.rebootWorker.reboot"));
|
app._workerStatus.update(0, 1, WSTR("App.rebootWorker.reboot"));
|
||||||
|
|
||||||
_unloadCartData();
|
app._unloadCartData();
|
||||||
_fileIO.closeResourceFile();
|
app._fileIO.closeResourceFile();
|
||||||
_fileIO.unmountIDE();
|
app._fileIO.unmountIDE();
|
||||||
_workerStatus.setStatus(WORKER_REBOOT);
|
app._workerStatus.setStatus(WORKER_REBOOT);
|
||||||
|
|
||||||
// Fall back to a soft reboot if the watchdog fails to reset the system.
|
// Fall back to a soft reboot if the watchdog fails to reset the system.
|
||||||
delayMicroseconds(2000000);
|
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 "common/romdrivers.hpp"
|
||||||
#include "main/app/app.hpp"
|
#include "main/app/app.hpp"
|
||||||
#include "main/app/romactions.hpp"
|
#include "main/app/romactions.hpp"
|
||||||
|
#include "main/workers/romworkers.hpp"
|
||||||
|
|
||||||
struct RegionInfo {
|
struct RegionInfo {
|
||||||
public:
|
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...
|
// TODO: all these *really* need a cleanup...
|
||||||
|
|
||||||
bool App::_romChecksumWorker(void) {
|
bool romChecksumWorker(App &app) {
|
||||||
_checksumScreen.valid = false;
|
app._checksumScreen.valid = false;
|
||||||
|
|
||||||
for (auto &entry : _REGION_INFO) {
|
for (auto &entry : _REGION_INFO) {
|
||||||
if (!entry.region.isPresent())
|
if (!entry.region.isPresent())
|
||||||
@ -87,7 +88,7 @@ bool App::_romChecksumWorker(void) {
|
|||||||
uint32_t offset = 0;
|
uint32_t offset = 0;
|
||||||
uint32_t crc = 0;
|
uint32_t crc = 0;
|
||||||
auto crcPtr = reinterpret_cast<uint32_t *>(
|
auto crcPtr = reinterpret_cast<uint32_t *>(
|
||||||
reinterpret_cast<uintptr_t>(&_checksumScreen.values) +
|
reinterpret_cast<uintptr_t>(&app._checksumScreen.values) +
|
||||||
entry.crcOffset
|
entry.crcOffset
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -97,7 +98,7 @@ bool App::_romChecksumWorker(void) {
|
|||||||
size_t end = util::min(i + _DUMP_CHUNKS_PER_CRC, numChunks);
|
size_t end = util::min(i + _DUMP_CHUNKS_PER_CRC, numChunks);
|
||||||
|
|
||||||
for (size_t j = i; j < end; j++) {
|
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);
|
crc = entry.region.zipCRC32(offset, chunkLength, crc);
|
||||||
offset += chunkLength;
|
offset += chunkLength;
|
||||||
@ -107,24 +108,24 @@ bool App::_romChecksumWorker(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_checksumScreen.valid = true;
|
app._checksumScreen.valid = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool App::_romDumpWorker(void) {
|
bool romDumpWorker(App &app) {
|
||||||
_workerStatus.update(0, 1, WSTR("App.romDumpWorker.init"));
|
app._workerStatus.update(0, 1, WSTR("App.romDumpWorker.init"));
|
||||||
|
|
||||||
// Store all dumps in a subdirectory named "dumpNNNN" within the main data
|
// Store all dumps in a subdirectory named "dumpNNNN" within the main data
|
||||||
// folder.
|
// folder.
|
||||||
char dirPath[fs::MAX_PATH_LENGTH], filePath[fs::MAX_PATH_LENGTH];
|
char dirPath[fs::MAX_PATH_LENGTH], filePath[fs::MAX_PATH_LENGTH];
|
||||||
|
|
||||||
if (!_createDataDirectory())
|
if (!app._createDataDirectory())
|
||||||
goto _initError;
|
goto _initError;
|
||||||
if (!_getNumberedPath(
|
if (!app._getNumberedPath(
|
||||||
dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%04d"
|
dirPath, sizeof(dirPath), EXTERNAL_DATA_DIR "/dump%04d"
|
||||||
))
|
))
|
||||||
goto _initError;
|
goto _initError;
|
||||||
if (!_fileIO.vfs.createDirectory(dirPath))
|
if (!app._fileIO.vfs.createDirectory(dirPath))
|
||||||
goto _initError;
|
goto _initError;
|
||||||
|
|
||||||
LOG_APP("saving dumps to %s", dirPath);
|
LOG_APP("saving dumps to %s", dirPath);
|
||||||
@ -145,7 +146,7 @@ bool App::_romDumpWorker(void) {
|
|||||||
|
|
||||||
snprintf(filePath, sizeof(filePath), entry.path, dirPath);
|
snprintf(filePath, sizeof(filePath), entry.path, dirPath);
|
||||||
|
|
||||||
auto file = _fileIO.vfs.openFile(
|
auto file = app._fileIO.vfs.openFile(
|
||||||
filePath, fs::WRITE | fs::ALLOW_CREATE
|
filePath, fs::WRITE | fs::ALLOW_CREATE
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -158,7 +159,7 @@ bool App::_romDumpWorker(void) {
|
|||||||
buffer.allocate(chunkLength);
|
buffer.allocate(chunkLength);
|
||||||
|
|
||||||
for (size_t i = 0; i < numChunks; i++) {
|
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);
|
entry.region.read(buffer.ptr, offset, chunkLength);
|
||||||
|
|
||||||
if (file->write(buffer.ptr, chunkLength) < chunkLength) {
|
if (file->write(buffer.ptr, chunkLength) < chunkLength) {
|
||||||
@ -179,42 +180,42 @@ bool App::_romDumpWorker(void) {
|
|||||||
LOG_APP("%s saved", filePath);
|
LOG_APP("%s saved", filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_SUCCESS, WSTR("App.romDumpWorker.success"), dirPath
|
MESSAGE_SUCCESS, WSTR("App.romDumpWorker.success"), dirPath
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
_initError:
|
_initError:
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.romDumpWorker.initError"), dirPath
|
MESSAGE_ERROR, WSTR("App.romDumpWorker.initError"), dirPath
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
_fileError:
|
_fileError:
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.romDumpWorker.fileError"), filePath
|
MESSAGE_ERROR, WSTR("App.romDumpWorker.fileError"), filePath
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool App::_romRestoreWorker(void) {
|
bool romRestoreWorker(App &app) {
|
||||||
_workerStatus.update(0, 1, WSTR("App.romRestoreWorker.init"));
|
app._workerStatus.update(0, 1, WSTR("App.romRestoreWorker.init"));
|
||||||
|
|
||||||
const char *path = _fileBrowserScreen.selectedPath;
|
const char *path = app._fileBrowserScreen.selectedPath;
|
||||||
auto file = _fileIO.vfs.openFile(path, fs::READ);
|
auto file = app._fileIO.vfs.openFile(path, fs::READ);
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.romRestoreWorker.fileError"), path
|
MESSAGE_ERROR, WSTR("App.romRestoreWorker.fileError"), path
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_romEraseWorker())
|
if (!romEraseWorker(app))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto region = _storageActionsScreen.selectedRegion;
|
auto region = app._storageActionsScreen.selectedRegion;
|
||||||
auto regionLength = _storageActionsScreen.selectedLength;
|
auto regionLength = app._storageActionsScreen.selectedLength;
|
||||||
|
|
||||||
auto driver = region->newDriver();
|
auto driver = region->newDriver();
|
||||||
auto chipLength = driver->getChipSize().chipLength;
|
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
|
// Parallelize writing by buffering a chunk for each chip into RAM, then
|
||||||
// writing all chunks to the respective chips at the same time.
|
// writing all chunks to the respective chips at the same time.
|
||||||
for (size_t i = 0; i < chipLength; i += maxChunkLength) {
|
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 bufferPtr = buffers.as<uint8_t>();
|
||||||
auto lengthPtr = chunkLengths.as<size_t>();
|
auto lengthPtr = chunkLengths.as<size_t>();
|
||||||
@ -305,7 +306,7 @@ bool App::_romRestoreWorker(void) {
|
|||||||
delete file;
|
delete file;
|
||||||
delete driver;
|
delete driver;
|
||||||
|
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.romRestoreWorker.flashError"),
|
MESSAGE_ERROR, WSTR("App.romRestoreWorker.flashError"),
|
||||||
rom::getErrorString(error), bytesWritten
|
rom::getErrorString(error), bytesWritten
|
||||||
);
|
);
|
||||||
@ -326,13 +327,13 @@ bool App::_romRestoreWorker(void) {
|
|||||||
delete file;
|
delete file;
|
||||||
delete driver;
|
delete driver;
|
||||||
|
|
||||||
_messageScreen.setMessage(MESSAGE_SUCCESS, WSTRH(message), bytesWritten);
|
app._messageScreen.setMessage(MESSAGE_SUCCESS, WSTRH(message), bytesWritten);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool App::_romEraseWorker(void) {
|
bool romEraseWorker(App &app) {
|
||||||
auto region = _storageActionsScreen.selectedRegion;
|
auto region = app._storageActionsScreen.selectedRegion;
|
||||||
auto regionLength = _storageActionsScreen.selectedLength;
|
auto regionLength = app._storageActionsScreen.selectedLength;
|
||||||
|
|
||||||
auto driver = region->newDriver();
|
auto driver = region->newDriver();
|
||||||
size_t chipLength = driver->getChipSize().chipLength;
|
size_t chipLength = driver->getChipSize().chipLength;
|
||||||
@ -343,18 +344,18 @@ bool App::_romEraseWorker(void) {
|
|||||||
if (!chipLength) {
|
if (!chipLength) {
|
||||||
delete driver;
|
delete driver;
|
||||||
|
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.romEraseWorker.unsupported")
|
MESSAGE_ERROR, WSTR("App.romEraseWorker.unsupported")
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_checksumScreen.valid = false;
|
app._checksumScreen.valid = false;
|
||||||
|
|
||||||
// Parallelize erasing by sending the same sector erase command to all chips
|
// Parallelize erasing by sending the same sector erase command to all chips
|
||||||
// at the same time.
|
// at the same time.
|
||||||
for (size_t i = 0; i < chipLength; i += sectorLength) {
|
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)
|
for (size_t j = 0; j < regionLength; j += chipLength)
|
||||||
driver->eraseSector(i + j);
|
driver->eraseSector(i + j);
|
||||||
@ -367,7 +368,7 @@ bool App::_romEraseWorker(void) {
|
|||||||
|
|
||||||
delete driver;
|
delete driver;
|
||||||
|
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.romEraseWorker.flashError"),
|
MESSAGE_ERROR, WSTR("App.romEraseWorker.flashError"),
|
||||||
rom::getErrorString(error), sectorsErased
|
rom::getErrorString(error), sectorsErased
|
||||||
);
|
);
|
||||||
@ -377,18 +378,18 @@ bool App::_romEraseWorker(void) {
|
|||||||
|
|
||||||
delete driver;
|
delete driver;
|
||||||
|
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_SUCCESS, WSTR("App.romEraseWorker.success"), sectorsErased
|
MESSAGE_SUCCESS, WSTR("App.romEraseWorker.success"), sectorsErased
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool App::_flashExecutableWriteWorker(void) {
|
bool flashExecutableWriteWorker(App &app) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool App::_flashHeaderWriteWorker(void) {
|
bool flashHeaderWriteWorker(App &app) {
|
||||||
auto driver = rom::flash.newDriver();
|
auto driver = rom::flash.newDriver();
|
||||||
size_t sectorLength = driver->getChipSize().eraseSectorLength;
|
size_t sectorLength = driver->getChipSize().eraseSectorLength;
|
||||||
|
|
||||||
@ -397,15 +398,15 @@ bool App::_flashHeaderWriteWorker(void) {
|
|||||||
if (!sectorLength) {
|
if (!sectorLength) {
|
||||||
delete driver;
|
delete driver;
|
||||||
|
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.flashHeaderWriteWorker.unsupported")
|
MESSAGE_ERROR, WSTR("App.flashHeaderWriteWorker.unsupported")
|
||||||
);
|
);
|
||||||
_workerStatus.setNextScreen(_messageScreen);
|
app._workerStatus.setNextScreen(app._messageScreen);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_checksumScreen.valid = false;
|
app._checksumScreen.valid = false;
|
||||||
_workerStatus.update(0, 2, WSTR("App.flashHeaderWriteWorker.erase"));
|
app._workerStatus.update(0, 2, WSTR("App.flashHeaderWriteWorker.erase"));
|
||||||
|
|
||||||
// The flash can only be erased with sector granularity, so all data in the
|
// 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.
|
// first sector other than the header must be backed up and rewritten.
|
||||||
@ -420,11 +421,11 @@ bool App::_flashHeaderWriteWorker(void) {
|
|||||||
if (error)
|
if (error)
|
||||||
goto _flashError;
|
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).
|
// Write the new header (if any).
|
||||||
if (!_romHeaderDump.isDataEmpty()) {
|
if (!app._romHeaderDump.isDataEmpty()) {
|
||||||
auto ptr = reinterpret_cast<const uint16_t *>(_romHeaderDump.data);
|
auto ptr = reinterpret_cast<const uint16_t *>(app._romHeaderDump.data);
|
||||||
|
|
||||||
for (
|
for (
|
||||||
uint32_t offset = rom::FLASH_HEADER_OFFSET;
|
uint32_t offset = rom::FLASH_HEADER_OFFSET;
|
||||||
@ -466,10 +467,10 @@ _flashError:
|
|||||||
buffer.destroy();
|
buffer.destroy();
|
||||||
delete driver;
|
delete driver;
|
||||||
|
|
||||||
_messageScreen.setMessage(
|
app._messageScreen.setMessage(
|
||||||
MESSAGE_ERROR, WSTR("App.flashHeaderWriteWorker.flashError"),
|
MESSAGE_ERROR, WSTR("App.flashHeaderWriteWorker.flashError"),
|
||||||
rom::getErrorString(error)
|
rom::getErrorString(error)
|
||||||
);
|
);
|
||||||
_workerStatus.setNextScreen(_messageScreen);
|
app._workerStatus.setNextScreen(app._messageScreen);
|
||||||
return false;
|
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);
|
@ -32,7 +32,8 @@ static uint32_t _savedExceptionVector[4];
|
|||||||
static Thread _mainThread;
|
static Thread _mainThread;
|
||||||
|
|
||||||
ArgFunction interruptHandler = 0;
|
ArgFunction interruptHandler = 0;
|
||||||
void *interruptHandlerArg = 0;
|
void *interruptHandlerArg0 = 0;
|
||||||
|
void *interruptHandlerArg1 = 0;
|
||||||
|
|
||||||
Thread *currentThread = &_mainThread;
|
Thread *currentThread = &_mainThread;
|
||||||
Thread *nextThread = &_mainThread;
|
Thread *nextThread = &_mainThread;
|
||||||
@ -89,10 +90,11 @@ void uninstallExceptionHandler(void) {
|
|||||||
_flushCache();
|
_flushCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setInterruptHandler(ArgFunction func, void *arg) {
|
void setInterruptHandler(ArgFunction func, void *arg0, void *arg1) {
|
||||||
disableInterrupts();
|
disableInterrupts();
|
||||||
interruptHandler = func;
|
interruptHandler = func;
|
||||||
interruptHandlerArg = arg;
|
interruptHandlerArg0 = arg0;
|
||||||
|
interruptHandlerArg1 = arg1;
|
||||||
flushWriteQueue();
|
flushWriteQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ typedef struct {
|
|||||||
} Thread;
|
} Thread;
|
||||||
|
|
||||||
typedef void (*VoidFunction)(void);
|
typedef void (*VoidFunction)(void);
|
||||||
typedef void (*ArgFunction)(void *arg);
|
typedef void (*ArgFunction)(void *arg0, void *arg1);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -92,16 +92,18 @@ __attribute__((always_inline)) static inline void flushWriteQueue(void) {
|
|||||||
*
|
*
|
||||||
* @param thread
|
* @param thread
|
||||||
* @param func
|
* @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
|
* @param stack Pointer to last 8 bytes in the stack
|
||||||
*/
|
*/
|
||||||
__attribute__((always_inline)) static inline void initThread(
|
__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");
|
register uint32_t gp __asm__("gp");
|
||||||
|
|
||||||
thread->pc = (uint32_t) func;
|
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->gp = (uint32_t) gp;
|
||||||
thread->sp = (uint32_t) stack;
|
thread->sp = (uint32_t) stack;
|
||||||
thread->fp = (uint32_t) stack;
|
thread->fp = (uint32_t) stack;
|
||||||
@ -154,9 +156,10 @@ void uninstallExceptionHandler(void);
|
|||||||
* a new handler.
|
* a new handler.
|
||||||
*
|
*
|
||||||
* @param func
|
* @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
|
* @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 CAUSE, $13
|
||||||
.set EPC, $14
|
.set EPC, $14
|
||||||
|
|
||||||
|
.set COP0_CAUSE_EXC_BITMASK, 31 << 2
|
||||||
|
.set COP0_CAUSE_EXC_SYS, 8 << 2
|
||||||
|
|
||||||
.section .text._exceptionVector, "ax", @progbits
|
.section .text._exceptionVector, "ax", @progbits
|
||||||
.global _exceptionVector
|
.global _exceptionVector
|
||||||
.type _exceptionVector, @function
|
.type _exceptionVector, @function
|
||||||
|
|
||||||
_exceptionVector:
|
_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
|
# 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
|
# 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
|
# 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.
|
# 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
|
# 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.
|
# would destroy their contents and corrupt the current thread's state.
|
||||||
lui $k0, %hi(currentThread)
|
lw $k0, %gprel(currentThread)($gp)
|
||||||
lw $k0, %lo(currentThread)($k0)
|
|
||||||
|
|
||||||
j _exceptionHandler
|
j _exceptionHandler
|
||||||
mfc0 $k1, EPC
|
mfc0 $k1, EPC
|
||||||
|
nop
|
||||||
|
|
||||||
.section .text._exceptionHandler, "ax", @progbits
|
.section .text._exceptionHandler, "ax", @progbits
|
||||||
.global _exceptionHandler
|
.global _exceptionHandler
|
||||||
@ -88,15 +90,17 @@ _exceptionHandler:
|
|||||||
# exception. If it was caused by a syscall, increment EPC to make sure
|
# exception. If it was caused by a syscall, increment EPC to make sure
|
||||||
# returning to the thread won't trigger another syscall.
|
# returning to the thread won't trigger another syscall.
|
||||||
mfc0 $v0, CAUSE
|
mfc0 $v0, CAUSE
|
||||||
lui $v1, %hi(interruptHandler)
|
lw $v1, %gprel(interruptHandler)($gp)
|
||||||
|
|
||||||
andi $v0, 0x1f << 2 # if (((CAUSE >> 2) & 0x1f) == 0) goto checkForGTEInst
|
# int code = CAUSE & COP0_CAUSE_EXC_BITMASK;
|
||||||
beqz $v0, .LcheckForGTEInst
|
andi $v0, COP0_CAUSE_EXC_BITMASK
|
||||||
li $at, 8 << 2 # if (((CAUSE >> 2) & 0x1f) == 8) goto applyIncrement
|
beqz $v0, .LisInterrupt
|
||||||
beq $v0, $at, .LapplyIncrement
|
li $at, COP0_CAUSE_EXC_SYS
|
||||||
lw $v1, %lo(interruptHandler)($v1)
|
|
||||||
|
|
||||||
.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
|
# If the exception was not triggered by a syscall nor by an interrupt call
|
||||||
# _unhandledException(), which will then display information about the
|
# _unhandledException(), which will then display information about the
|
||||||
# exception and lock up.
|
# exception and lock up.
|
||||||
@ -107,41 +111,44 @@ _exceptionHandler:
|
|||||||
jal _unhandledException
|
jal _unhandledException
|
||||||
addiu $sp, -8
|
addiu $sp, -8
|
||||||
|
|
||||||
|
lw $k0, %gprel(nextThread)($gp)
|
||||||
b .Lreturn
|
b .Lreturn
|
||||||
addiu $sp, 8
|
addiu $sp, 8
|
||||||
|
|
||||||
.LcheckForGTEInst:
|
.LisInterrupt: # } else {
|
||||||
# If the exception was caused by an interrupt, check if the interrupted
|
# 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
|
# 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.
|
# 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
|
li $at, 0x25
|
||||||
srl $v0, 25
|
srl $v0, 25
|
||||||
bne $v0, $at, .LskipIncrement
|
bne $v0, $at, .LskipEPCIncrement
|
||||||
lw $v1, %lo(interruptHandler)($v1)
|
lw $a0, %gprel(interruptHandlerArg0)($gp)
|
||||||
|
|
||||||
.LapplyIncrement:
|
.LisSyscall:
|
||||||
|
# if (code == SYS) EPC += 4;
|
||||||
addiu $k1, 4
|
addiu $k1, 4
|
||||||
|
|
||||||
.LskipIncrement:
|
.LskipEPCIncrement:
|
||||||
# Save the modified EPC and dispatch any pending interrupts. The handler
|
# Save the modified EPC and invoke the interrupt handler, which will
|
||||||
# will temporarily use the current thread's stack.
|
# temporarily use the current thread's stack.
|
||||||
sw $k1, 0x00($k0)
|
sw $k1, 0x00($k0)
|
||||||
|
|
||||||
lui $a0, %hi(interruptHandlerArg)
|
# interruptHandler(interruptHandlerArg0, interruptHandlerArg1);
|
||||||
lw $a0, %lo(interruptHandlerArg)($a0)
|
lw $a1, %gprel(interruptHandlerArg1)($gp)
|
||||||
jalr $v1 # interruptHandler(interruptHandlerArg)
|
jalr $v1
|
||||||
addiu $sp, -8
|
addiu $sp, -8
|
||||||
|
|
||||||
|
lw $k0, %gprel(nextThread)($gp)
|
||||||
addiu $sp, 8
|
addiu $sp, 8
|
||||||
|
|
||||||
.Lreturn:
|
.Lreturn: # }
|
||||||
# Grab a pointer to the next thread to be executed, restore its state and
|
# Grab a pointer to the next thread to be executed and restore its state.
|
||||||
# return.
|
|
||||||
lui $k0, %hi(nextThread)
|
# currentThread = nextThread;
|
||||||
lw $k0, %lo(nextThread)($k0)
|
sw $k0, %gprel(currentThread)($gp)
|
||||||
lui $at, %hi(currentThread)
|
|
||||||
sw $k0, %lo(currentThread)($at)
|
|
||||||
|
|
||||||
lw $v0, 0x78($k0)
|
lw $v0, 0x78($k0)
|
||||||
lw $v1, 0x7c($k0)
|
lw $v1, 0x7c($k0)
|
||||||
@ -197,7 +204,9 @@ _exceptionHandler:
|
|||||||
delayMicroseconds:
|
delayMicroseconds:
|
||||||
# Calculate the approximate number of CPU cycles that need to be burned,
|
# 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).
|
# 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
|
sll $a2, $a0, 4
|
||||||
addu $a1, $a2
|
addu $a1, $a2
|
||||||
subu $a1, $a0
|
subu $a1, $a0
|
||||||
@ -208,9 +217,9 @@ delayMicroseconds:
|
|||||||
# loop and returning.
|
# loop and returning.
|
||||||
addiu $a0, -(6 + 1 + 2 + 4 + 2)
|
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)
|
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
|
# 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
|
# for it to count up each time), as the counter is only 16 bits wide. We
|
||||||
@ -221,11 +230,13 @@ delayMicroseconds:
|
|||||||
beqz $v0, .LshortDelay
|
beqz $v0, .LshortDelay
|
||||||
li $a2, 0xff00 + 3
|
li $a2, 0xff00 + 3
|
||||||
|
|
||||||
.LlongDelay: # for (; cycles > 0xff00; cycles -= (0xff00 + 3))
|
.LlongDelay: # for (; cycles > 0xff00; cycles -= (0xff00 + 3)) {
|
||||||
sh $0, %lo(TIMER2_VALUE)($v1) # TIMER2_VALUE = 0
|
# TIMER2_VALUE = 0;
|
||||||
|
sh $0, %lo(TIMER2_VALUE)($v1)
|
||||||
li $v0, 0
|
li $v0, 0
|
||||||
|
|
||||||
.LlongDelayLoop: # while (TIMER2_VALUE < 0xff00);
|
.LlongDelayLoop:
|
||||||
|
# while (TIMER2_VALUE < 0xff00);
|
||||||
nop
|
nop
|
||||||
slt $v0, $v0, $a1
|
slt $v0, $v0, $a1
|
||||||
bnez $v0, .LlongDelayLoop
|
bnez $v0, .LlongDelayLoop
|
||||||
@ -235,17 +246,21 @@ delayMicroseconds:
|
|||||||
bnez $v0, .LlongDelay
|
bnez $v0, .LlongDelay
|
||||||
subu $a0, $a2
|
subu $a0, $a2
|
||||||
|
|
||||||
.LshortDelay:
|
.LshortDelay: # }
|
||||||
# Run the last busy loop once less than 0xff00 cycles are remaining.
|
# 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
|
li $v0, 0
|
||||||
|
|
||||||
.LshortDelayLoop: # while (TIMER2_VALUE < cycles);
|
.LshortDelayLoop:
|
||||||
|
# while (TIMER2_VALUE < cycles);
|
||||||
nop
|
nop
|
||||||
slt $v0, $v0, $a0
|
slt $v0, $v0, $a0
|
||||||
bnez $v0, .LshortDelayLoop
|
bnez $v0, .LshortDelayLoop
|
||||||
lhu $v0, %lo(TIMER2_VALUE)($v1)
|
lhu $v0, %lo(TIMER2_VALUE)($v1)
|
||||||
|
|
||||||
|
# return;
|
||||||
jr $ra
|
jr $ra
|
||||||
nop
|
nop
|
||||||
|
|
||||||
@ -254,9 +269,8 @@ delayMicroseconds:
|
|||||||
.type delayMicrosecondsBusy, @function
|
.type delayMicrosecondsBusy, @function
|
||||||
|
|
||||||
delayMicrosecondsBusy:
|
delayMicrosecondsBusy:
|
||||||
# Calculate the approximate number of CPU cycles that need to be burned,
|
# cycles = ((us * 271) + 4) / 8:
|
||||||
# assuming a 33.8688 MHz clock (1 us = 33.8688 = ~33.875 cycles).
|
sll $a1, $a0, 8
|
||||||
sll $a1, $a0, 8 # cycles = ((us * 271) + 4) / 8
|
|
||||||
sll $a2, $a0, 4
|
sll $a2, $a0, 4
|
||||||
addu $a1, $a2
|
addu $a1, $a2
|
||||||
subu $a1, $a0
|
subu $a1, $a0
|
||||||
@ -266,9 +280,11 @@ delayMicrosecondsBusy:
|
|||||||
# Compensate for the overhead of calculating the cycle count and returning.
|
# Compensate for the overhead of calculating the cycle count and returning.
|
||||||
addiu $a0, -(6 + 1 + 2)
|
addiu $a0, -(6 + 1 + 2)
|
||||||
|
|
||||||
.Lloop: # while (cycles > 0) cycles -= 2
|
.Lloop:
|
||||||
|
# while (cycles > 0) cycles -= 2;
|
||||||
bgtz $a0, .Lloop
|
bgtz $a0, .Lloop
|
||||||
addiu $a0, -2
|
addiu $a0, -2
|
||||||
|
|
||||||
|
# return;
|
||||||
jr $ra
|
jr $ra
|
||||||
nop
|
nop
|
||||||
|
Loading…
x
Reference in New Issue
Block a user