mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-02-08 23:09:32 +01:00
More refactoring, remove layer queue from GPU driver
This commit is contained in:
parent
bd50057976
commit
9788a3fbcc
@ -6,7 +6,7 @@ set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/cmake/toolchain.cmake")
|
|||||||
project(
|
project(
|
||||||
cart_tool_private
|
cart_tool_private
|
||||||
LANGUAGES C CXX ASM
|
LANGUAGES C CXX ASM
|
||||||
VERSION 0.3.0
|
VERSION 0.3.1
|
||||||
DESCRIPTION "Konami System 573 security cartridge tool"
|
DESCRIPTION "Konami System 573 security cartridge tool"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,6 +17,7 @@ find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
|||||||
add_executable(
|
add_executable(
|
||||||
cart_tool
|
cart_tool
|
||||||
src/asset.cpp
|
src/asset.cpp
|
||||||
|
src/cart.cpp
|
||||||
src/cartdata.cpp
|
src/cartdata.cpp
|
||||||
src/cartio.cpp
|
src/cartio.cpp
|
||||||
src/gpu.cpp
|
src/gpu.cpp
|
||||||
@ -67,21 +68,26 @@ target_compile_definitions(
|
|||||||
add_custom_command(
|
add_custom_command(
|
||||||
TARGET cart_tool POST_BUILD
|
TARGET cart_tool POST_BUILD
|
||||||
COMMAND
|
COMMAND
|
||||||
"${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tools/convertExecutable.py"
|
"${Python3_EXECUTABLE}"
|
||||||
|
"${PROJECT_SOURCE_DIR}/tools/convertExecutable.py"
|
||||||
|
-r "cart_tool build ${PROJECT_VERSION} - (C) 2022-2023 spicyjpeg"
|
||||||
$<TARGET_FILE:cart_tool> cart_tool.psexe
|
$<TARGET_FILE:cart_tool> cart_tool.psexe
|
||||||
BYPRODUCTS cart_tool.psexe
|
BYPRODUCTS cart_tool.psexe
|
||||||
COMMENT "Converting executable"
|
COMMENT "Converting executable"
|
||||||
|
VERBATIM
|
||||||
)
|
)
|
||||||
|
|
||||||
## Default resource archive
|
## Default resource archive
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
COMMAND
|
COMMAND
|
||||||
"${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tools/buildResourceArchive.py"
|
"${Python3_EXECUTABLE}"
|
||||||
|
"${PROJECT_SOURCE_DIR}/tools/buildResourceArchive.py"
|
||||||
"${PROJECT_SOURCE_DIR}/resources.json" resources.zip
|
"${PROJECT_SOURCE_DIR}/resources.json" resources.zip
|
||||||
OUTPUT resources.zip
|
OUTPUT resources.zip
|
||||||
DEPENDS resources.json
|
DEPENDS resources.json
|
||||||
COMMENT "Building resource archive"
|
COMMENT "Building resource archive"
|
||||||
|
VERBATIM
|
||||||
)
|
)
|
||||||
ps1_target_incbin(
|
ps1_target_incbin(
|
||||||
cart_tool PRIVATE
|
cart_tool PRIVATE
|
||||||
@ -99,6 +105,7 @@ ps1_target_incbin(
|
|||||||
# OUTPUT cart_tool.iso
|
# OUTPUT cart_tool.iso
|
||||||
# DEPENDS cd.json cart_tool
|
# DEPENDS cd.json cart_tool
|
||||||
# COMMENT "Building CD-ROM image"
|
# COMMENT "Building CD-ROM image"
|
||||||
|
# VERBATIM
|
||||||
#)
|
#)
|
||||||
|
|
||||||
# Add a dummy target that depends on the CD image to make sure it gets built.
|
# Add a dummy target that depends on the CD image to make sure it gets built.
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "app/app.hpp"
|
#include "app/app.hpp"
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
#include "asset.hpp"
|
#include "asset.hpp"
|
||||||
|
#include "cart.hpp"
|
||||||
#include "cartdata.hpp"
|
#include "cartdata.hpp"
|
||||||
#include "cartio.hpp"
|
#include "cartio.hpp"
|
||||||
#include "io.hpp"
|
#include "io.hpp"
|
||||||
@ -12,17 +13,18 @@
|
|||||||
|
|
||||||
/* App class */
|
/* App class */
|
||||||
|
|
||||||
App::App(void)
|
void App::_unloadCartData(void) {
|
||||||
: _cart(nullptr), _cartData(nullptr), _identified(nullptr) {
|
if (_driver) {
|
||||||
_workerStack = new uint8_t[WORKER_STACK_SIZE];
|
delete _driver;
|
||||||
}
|
_driver = nullptr;
|
||||||
|
}
|
||||||
|
if (_parser) {
|
||||||
|
delete _parser;
|
||||||
|
_parser = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
App::~App(void) {
|
_db.unload();
|
||||||
//_dbFile.unload();
|
_identified = nullptr;
|
||||||
delete[] _workerStack;
|
|
||||||
|
|
||||||
if (_cart)
|
|
||||||
delete _cart;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::_setupWorker(void (App::* func)(void)) {
|
void App::_setupWorker(void (App::* func)(void)) {
|
||||||
@ -41,7 +43,7 @@ void App::_setupInterrupts(void) {
|
|||||||
util::forcedCast<ArgFunction>(&App::_interruptHandler), this
|
util::forcedCast<ArgFunction>(&App::_interruptHandler), this
|
||||||
);
|
);
|
||||||
|
|
||||||
setInterruptMask((1 << IRQ_VBLANK) | (1 << IRQ_GPU));
|
setInterruptMask(1 << IRQ_VBLANK);
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::_dummyWorker(void) {
|
void App::_dummyWorker(void) {
|
||||||
@ -59,27 +61,20 @@ static const char *const _CARTDB_PATHS[cart::NUM_CHIP_TYPES]{
|
|||||||
void App::_cartDetectWorker(void) {
|
void App::_cartDetectWorker(void) {
|
||||||
_workerStatus.update(0, 4, WSTR("App.cartDetectWorker.identifyCart"));
|
_workerStatus.update(0, 4, WSTR("App.cartDetectWorker.identifyCart"));
|
||||||
|
|
||||||
_db.unload();
|
_unloadCartData();
|
||||||
if (_cart)
|
_driver = cart::newCartDriver(_dump);
|
||||||
delete _cart;
|
|
||||||
if (_cartData)
|
|
||||||
delete _cartData;
|
|
||||||
|
|
||||||
_cart = cart::createCart(_dump);
|
|
||||||
_cartData = nullptr;
|
|
||||||
_identified = nullptr;
|
|
||||||
|
|
||||||
if (_dump.chipType) {
|
if (_dump.chipType) {
|
||||||
LOG("dump @ 0x%08x, cart object @ 0x%08x", &_dump, _cart);
|
LOG("dump @ 0x%08x, cart driver @ 0x%08x", &_dump, _driver);
|
||||||
_workerStatus.update(1, 4, WSTR("App.cartDetectWorker.readCart"));
|
_workerStatus.update(1, 4, WSTR("App.cartDetectWorker.readCart"));
|
||||||
|
|
||||||
_cart->readCartID();
|
_driver->readCartID();
|
||||||
if (!_cart->readPublicData())
|
if (!_driver->readPublicData())
|
||||||
_cartData = cart::createCartData(_dump);
|
_parser = cart::newCartParser(_dump);
|
||||||
if (!_cartData)
|
if (!_parser)
|
||||||
goto _cartInitDone;
|
goto _cartInitDone;
|
||||||
|
|
||||||
LOG("cart data object @ 0x%08x", _cartData);
|
LOG("cart parser @ 0x%08x", _parser);
|
||||||
_workerStatus.update(2, 4, WSTR("App.cartDetectWorker.identifyGame"));
|
_workerStatus.update(2, 4, WSTR("App.cartDetectWorker.identifyGame"));
|
||||||
|
|
||||||
if (!_loader->loadAsset(_db, _CARTDB_PATHS[_dump.chipType])) {
|
if (!_loader->loadAsset(_db, _CARTDB_PATHS[_dump.chipType])) {
|
||||||
@ -115,7 +110,7 @@ _cartInitDone:
|
|||||||
|
|
||||||
delayMicroseconds(5000); // Probably not necessary
|
delayMicroseconds(5000); // Probably not necessary
|
||||||
io::initKonamiBitstream();
|
io::initKonamiBitstream();
|
||||||
_cart->readSystemID();
|
_driver->readSystemID();
|
||||||
}
|
}
|
||||||
|
|
||||||
_initDone:
|
_initDone:
|
||||||
@ -126,12 +121,12 @@ _initDone:
|
|||||||
void App::_cartUnlockWorker(void) {
|
void App::_cartUnlockWorker(void) {
|
||||||
_workerStatus.update(0, 2, WSTR("App.cartUnlockWorker.read"));
|
_workerStatus.update(0, 2, WSTR("App.cartUnlockWorker.read"));
|
||||||
|
|
||||||
//_cart->readPrivateData(); // TODO: implement this
|
//_driver->readPrivateData(); // TODO: implement this
|
||||||
|
|
||||||
_workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identify"));
|
_workerStatus.update(1, 2, WSTR("App.cartUnlockWorker.identify"));
|
||||||
|
|
||||||
//if (_dump.flags & cart::DUMP_PRIVATE_DATA_OK)
|
//if (_dump.flags & cart::DUMP_PRIVATE_DATA_OK)
|
||||||
//_identifyResult = _db.identifyCart(*_cart);
|
//_identifyResult = _db.identifyCart(*_driver);
|
||||||
|
|
||||||
_workerStatus.finish(_cartInfoScreen, true);
|
_workerStatus.finish(_cartInfoScreen, true);
|
||||||
_dummyWorker();
|
_dummyWorker();
|
||||||
@ -154,11 +149,10 @@ void App::_interruptHandler(void) {
|
|||||||
if (acknowledgeInterrupt(IRQ_VBLANK)) {
|
if (acknowledgeInterrupt(IRQ_VBLANK)) {
|
||||||
_ctx->tick();
|
_ctx->tick();
|
||||||
io::clearWatchdog();
|
io::clearWatchdog();
|
||||||
switchThread(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (acknowledgeInterrupt(IRQ_GPU))
|
if (gpu::isIdle())
|
||||||
_ctx->gpuCtx.drawNextLayer();
|
switchThread(nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::run(
|
void App::run(
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "app/unlock.hpp"
|
#include "app/unlock.hpp"
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
#include "asset.hpp"
|
#include "asset.hpp"
|
||||||
|
#include "cart.hpp"
|
||||||
#include "cartdata.hpp"
|
#include "cartdata.hpp"
|
||||||
#include "cartio.hpp"
|
#include "cartio.hpp"
|
||||||
#include "uibase.hpp"
|
#include "uibase.hpp"
|
||||||
@ -90,11 +91,12 @@ private:
|
|||||||
Thread _workerThread;
|
Thread _workerThread;
|
||||||
WorkerStatus _workerStatus;
|
WorkerStatus _workerStatus;
|
||||||
|
|
||||||
uint8_t *_workerStack;
|
uint8_t *_workerStack;
|
||||||
cart::Cart *_cart;
|
cart::Driver *_driver;
|
||||||
cart::CartData *_cartData;
|
cart::Parser *_parser;
|
||||||
cart::DBEntry *_identified;
|
cart::DBEntry *_identified;
|
||||||
|
|
||||||
|
void _unloadCartData(void);
|
||||||
void _setupWorker(void (App::* func)(void));
|
void _setupWorker(void (App::* func)(void));
|
||||||
void _setupInterrupts(void);
|
void _setupInterrupts(void);
|
||||||
|
|
||||||
@ -106,8 +108,16 @@ private:
|
|||||||
void _interruptHandler(void);
|
void _interruptHandler(void);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
App(void);
|
inline App(void)
|
||||||
~App(void);
|
: _driver(nullptr), _parser(nullptr), _identified(nullptr) {
|
||||||
|
_workerStack = new uint8_t[WORKER_STACK_SIZE];
|
||||||
|
}
|
||||||
|
inline ~App(void) {
|
||||||
|
_unloadCartData();
|
||||||
|
|
||||||
|
delete[] _workerStack;
|
||||||
|
}
|
||||||
|
|
||||||
void run(
|
void run(
|
||||||
ui::Context &ctx, asset::AssetLoader &loader,
|
ui::Context &ctx, asset::AssetLoader &loader,
|
||||||
asset::StringTable &strings
|
asset::StringTable &strings
|
||||||
|
164
src/cart.cpp
Normal file
164
src/cart.cpp
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "vendor/miniz.h"
|
||||||
|
#include "cart.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
/* Common data structures */
|
||||||
|
|
||||||
|
void Identifier::updateChecksum(void) {
|
||||||
|
data[7] = (util::sum(data, 7) & 0xff) ^ 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Identifier::validateChecksum(void) const {
|
||||||
|
uint8_t value = (util::sum(data, 7) & 0xff) ^ 0xff;
|
||||||
|
|
||||||
|
if (value != data[7]) {
|
||||||
|
LOG("checksum mismatch, exp=0x%02x, got=0x%02x", value, data[7]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Identifier::updateDSCRC(void) {
|
||||||
|
data[7] = util::dsCRC8(data, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Identifier::validateDSCRC(void) const {
|
||||||
|
uint8_t value = util::dsCRC8(data, 7);
|
||||||
|
|
||||||
|
if (value != data[7]) {
|
||||||
|
LOG("CRC mismatch, exp=0x%02x, got=0x%02x", value, data[7]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t IdentifierSet::getFlags(void) const {
|
||||||
|
uint8_t flags = 0;
|
||||||
|
|
||||||
|
if (!traceID.isEmpty())
|
||||||
|
flags |= DATA_HAS_TRACE_ID;
|
||||||
|
if (!cartID.isEmpty())
|
||||||
|
flags |= DATA_HAS_CART_ID;
|
||||||
|
if (!installID.isEmpty())
|
||||||
|
flags |= DATA_HAS_INSTALL_ID;
|
||||||
|
if (!systemID.isEmpty())
|
||||||
|
flags |= DATA_HAS_SYSTEM_ID;
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentifierSet::setInstallID(uint8_t prefix) {
|
||||||
|
installID.clear();
|
||||||
|
|
||||||
|
installID.data[0] = prefix;
|
||||||
|
installID.updateChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IdentifierSet::updateTraceID(uint8_t prefix, int param) {
|
||||||
|
traceID.clear();
|
||||||
|
|
||||||
|
uint8_t *input = &cartID.data[1];
|
||||||
|
uint16_t checksum = 0;
|
||||||
|
|
||||||
|
switch (prefix) {
|
||||||
|
case 0x81:
|
||||||
|
// TODO: reverse engineer this TID format
|
||||||
|
traceID.data[2] = 0;
|
||||||
|
traceID.data[5] = 0;
|
||||||
|
traceID.data[6] = 0;
|
||||||
|
|
||||||
|
LOG("prefix=0x81");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x82:
|
||||||
|
for (size_t i = 0; i < (sizeof(cartID.data) - 2); i++) {
|
||||||
|
uint8_t value = *(input++);
|
||||||
|
|
||||||
|
for (int j = 0; j < 8; j++, value >>= 1) {
|
||||||
|
if (value % 2)
|
||||||
|
checksum ^= 1 << (i % param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traceID.data[1] = checksum >> 8;
|
||||||
|
traceID.data[2] = checksum & 0xff;
|
||||||
|
|
||||||
|
LOG("prefix=0x82, checksum=%04x", checksum);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOG("unknown prefix 0x%02x", prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
traceID.data[0] = prefix;
|
||||||
|
traceID.updateChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BasicHeader::updateChecksum(void) {
|
||||||
|
auto value = util::sum(reinterpret_cast<const uint8_t *>(this), 4);
|
||||||
|
|
||||||
|
checksum = uint8_t((value & 0xff) ^ 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BasicHeader::validateChecksum(void) const {
|
||||||
|
auto value = util::sum(reinterpret_cast<const uint8_t *>(this), 4);
|
||||||
|
|
||||||
|
return (checksum == ((value & 0xff) ^ 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtendedHeader::updateChecksum(void) {
|
||||||
|
auto value = util::sum(reinterpret_cast<const uint16_t *>(this), 14);
|
||||||
|
|
||||||
|
checksum = uint16_t(value & 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExtendedHeader::validateChecksum(void) const {
|
||||||
|
auto value = util::sum(reinterpret_cast<const uint16_t *>(this), 14);
|
||||||
|
|
||||||
|
return (checksum == (value & 0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dump structure and utilities */
|
||||||
|
|
||||||
|
const ChipSize CHIP_SIZES[NUM_CHIP_TYPES]{
|
||||||
|
{ .dataLength = 0, .publicDataOffset = 0, .publicDataLength = 0 },
|
||||||
|
{ .dataLength = 512, .publicDataOffset = 384, .publicDataLength = 128 },
|
||||||
|
{ .dataLength = 112, .publicDataOffset = 0, .publicDataLength = 0 },
|
||||||
|
{ .dataLength = 112, .publicDataOffset = 0, .publicDataLength = 32 }
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t Dump::toQRString(char *output) const {
|
||||||
|
uint8_t compressed[MAX_QR_STRING_LENGTH];
|
||||||
|
size_t uncompLength = getDumpLength();
|
||||||
|
size_t compLength = MAX_QR_STRING_LENGTH;
|
||||||
|
|
||||||
|
int error = mz_compress2(
|
||||||
|
compressed, reinterpret_cast<mz_ulong *>(&compLength),
|
||||||
|
reinterpret_cast<const uint8_t *>(this), uncompLength,
|
||||||
|
MZ_BEST_COMPRESSION
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error != MZ_OK) {
|
||||||
|
LOG("compression error, code=%d", error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
LOG(
|
||||||
|
"dump compressed, size=%d, ratio=%d%%", compLength,
|
||||||
|
compLength * 100 / uncompLength
|
||||||
|
);
|
||||||
|
|
||||||
|
compLength = util::encodeBase45(&output[5], compressed, compLength);
|
||||||
|
__builtin_memcpy(&output[0], "573::", 5);
|
||||||
|
__builtin_memcpy(&output[compLength + 5], "::", 3);
|
||||||
|
|
||||||
|
return compLength + 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
175
src/cart.hpp
Normal file
175
src/cart.hpp
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
/* Definitions */
|
||||||
|
|
||||||
|
enum ChipType : uint8_t {
|
||||||
|
NONE = 0,
|
||||||
|
X76F041 = 1,
|
||||||
|
X76F100 = 2,
|
||||||
|
ZS01 = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FormatType : uint8_t {
|
||||||
|
BLANK = 0,
|
||||||
|
SIMPLE = 1,
|
||||||
|
BASIC = 2,
|
||||||
|
EXTENDED = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum DumpFlag : uint8_t {
|
||||||
|
DUMP_HAS_SYSTEM_ID = 1 << 0,
|
||||||
|
DUMP_HAS_CART_ID = 1 << 1,
|
||||||
|
DUMP_CONFIG_OK = 1 << 2,
|
||||||
|
DUMP_SYSTEM_ID_OK = 1 << 3,
|
||||||
|
DUMP_CART_ID_OK = 1 << 4,
|
||||||
|
DUMP_ZS_ID_OK = 1 << 5,
|
||||||
|
DUMP_PUBLIC_DATA_OK = 1 << 6,
|
||||||
|
DUMP_PRIVATE_DATA_OK = 1 << 7
|
||||||
|
};
|
||||||
|
|
||||||
|
// | | Simple | Basic | Extended |
|
||||||
|
// | :---------------------- | :-------- | :------- | :-------- |
|
||||||
|
// | DATA_HAS_CODE_PREFIX | | Optional | Mandatory |
|
||||||
|
// | DATA_HAS_*_ID | | Optional | Optional |
|
||||||
|
// | DATA_HAS_PUBLIC_SECTION | Mandatory | | Optional |
|
||||||
|
|
||||||
|
enum DataFlag : uint8_t {
|
||||||
|
DATA_HAS_CODE_PREFIX = 1 << 0,
|
||||||
|
DATA_HAS_TRACE_ID = 1 << 1,
|
||||||
|
DATA_HAS_CART_ID = 1 << 2,
|
||||||
|
DATA_HAS_INSTALL_ID = 1 << 3,
|
||||||
|
DATA_HAS_SYSTEM_ID = 1 << 4,
|
||||||
|
DATA_HAS_PUBLIC_SECTION = 1 << 5
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr int NUM_CHIP_TYPES = 4;
|
||||||
|
static constexpr size_t MAX_QR_STRING_LENGTH = 0x600;
|
||||||
|
|
||||||
|
/* Common data structures */
|
||||||
|
|
||||||
|
class [[gnu::packed]] Identifier {
|
||||||
|
public:
|
||||||
|
uint8_t data[8];
|
||||||
|
|
||||||
|
inline void copyFrom(const uint8_t *source) {
|
||||||
|
__builtin_memcpy(data, source, sizeof(data));
|
||||||
|
}
|
||||||
|
inline void copyTo(uint8_t *dest) const {
|
||||||
|
__builtin_memcpy(dest, data, sizeof(data));
|
||||||
|
}
|
||||||
|
inline void clear(void) {
|
||||||
|
__builtin_memset(data, 0, sizeof(data));
|
||||||
|
}
|
||||||
|
inline bool isEmpty(void) const {
|
||||||
|
return (util::sum(data, sizeof(data)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t toString(char *output) const {
|
||||||
|
return util::hexToString(output, data, sizeof(data), '-');
|
||||||
|
}
|
||||||
|
inline size_t toSerialNumber(char *output) const {
|
||||||
|
return util::serialNumberToString(output, &data[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateChecksum(void);
|
||||||
|
bool validateChecksum(void) const;
|
||||||
|
void updateDSCRC(void);
|
||||||
|
bool validateDSCRC(void) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] IdentifierSet {
|
||||||
|
public:
|
||||||
|
Identifier traceID, cartID, installID, systemID; // aka TID, SID, MID, XID
|
||||||
|
|
||||||
|
inline void clear(void) {
|
||||||
|
__builtin_memset(this, 0, sizeof(IdentifierSet));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getFlags(void) const;
|
||||||
|
void setInstallID(uint8_t prefix);
|
||||||
|
void updateTraceID(uint8_t prefix, int param);
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] PublicIdentifierSet {
|
||||||
|
public:
|
||||||
|
Identifier traceID, cartID; // aka TID, SID
|
||||||
|
|
||||||
|
inline void clear(void) {
|
||||||
|
__builtin_memset(this, 0, sizeof(PublicIdentifierSet));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] SimpleHeader {
|
||||||
|
public:
|
||||||
|
char region[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] BasicHeader {
|
||||||
|
public:
|
||||||
|
char region[2], codePrefix[2];
|
||||||
|
uint8_t checksum, _pad[3];
|
||||||
|
|
||||||
|
void updateChecksum(void);
|
||||||
|
bool validateChecksum(void) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class [[gnu::packed]] ExtendedHeader {
|
||||||
|
public:
|
||||||
|
char code[8];
|
||||||
|
uint16_t year; // BCD, can be little endian, big endian or zero
|
||||||
|
char region[4];
|
||||||
|
uint16_t checksum;
|
||||||
|
|
||||||
|
void updateChecksum(void);
|
||||||
|
bool validateChecksum(void) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Cartridge dump structure */
|
||||||
|
|
||||||
|
struct ChipSize {
|
||||||
|
public:
|
||||||
|
size_t dataLength, publicDataOffset, publicDataLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const ChipSize CHIP_SIZES[NUM_CHIP_TYPES];
|
||||||
|
|
||||||
|
class [[gnu::packed]] Dump {
|
||||||
|
public:
|
||||||
|
ChipType chipType;
|
||||||
|
uint8_t flags;
|
||||||
|
|
||||||
|
Identifier systemID, cartID, zsID;
|
||||||
|
|
||||||
|
uint8_t dataKey[8], config[8];
|
||||||
|
uint8_t data[512];
|
||||||
|
|
||||||
|
inline const ChipSize &getChipSize(void) const {
|
||||||
|
return CHIP_SIZES[chipType];
|
||||||
|
}
|
||||||
|
inline size_t getDumpLength(void) const {
|
||||||
|
return (sizeof(Dump) - sizeof(data)) + getChipSize().dataLength;
|
||||||
|
}
|
||||||
|
inline void clear(void) {
|
||||||
|
__builtin_memset(this, 0, sizeof(Dump));
|
||||||
|
}
|
||||||
|
inline void clearData(void) {
|
||||||
|
__builtin_memset(data, 0, getChipSize().dataLength);
|
||||||
|
}
|
||||||
|
inline bool isDataEmpty(void) const {
|
||||||
|
size_t length = getChipSize().dataLength;
|
||||||
|
auto sum = util::sum(data, length);
|
||||||
|
|
||||||
|
return (!sum || (sum == (0xff * length)));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t toQRString(char *output) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
372
src/cartdata.cpp
372
src/cartdata.cpp
@ -3,12 +3,151 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "asset.hpp"
|
#include "asset.hpp"
|
||||||
|
#include "cart.hpp"
|
||||||
#include "cartdata.hpp"
|
#include "cartdata.hpp"
|
||||||
#include "cartio.hpp"
|
|
||||||
|
|
||||||
namespace cart {
|
namespace cart {
|
||||||
|
|
||||||
/* Utilities */
|
/* Cartridge data parsers */
|
||||||
|
|
||||||
|
bool Parser::validate(void) {
|
||||||
|
char region[8];
|
||||||
|
|
||||||
|
if (getRegion(region) < REGION_MIN_LENGTH)
|
||||||
|
return false;
|
||||||
|
if (!isValidRegion(region))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto id = getIdentifiers();
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
// The system ID is excluded from validation as it is going to be
|
||||||
|
// missing if the game hasn't yet been installed.
|
||||||
|
uint8_t idFlags = flags & (
|
||||||
|
DATA_HAS_TRACE_ID | DATA_HAS_CART_ID | DATA_HAS_INSTALL_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
if (id->getFlags() != idFlags)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SimpleParser::getRegion(char *output) const {
|
||||||
|
auto header = _getHeader();
|
||||||
|
|
||||||
|
__builtin_memcpy(output, header->region, 4);
|
||||||
|
output[4] = 0;
|
||||||
|
|
||||||
|
return __builtin_strlen(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BasicParser::getRegion(char *output) const {
|
||||||
|
auto header = _getHeader();
|
||||||
|
|
||||||
|
output[0] = header->region[0];
|
||||||
|
output[1] = header->region[1];
|
||||||
|
output[2] = 0;
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentifierSet *BasicParser::getIdentifiers(void) {
|
||||||
|
return reinterpret_cast<IdentifierSet *>(&_dump.data[sizeof(BasicHeader)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BasicParser::validate(void) {
|
||||||
|
return (Parser::validate() && _getHeader()->validateChecksum());
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ExtendedParser::getRegion(char *output) const {
|
||||||
|
auto header = _getHeader();
|
||||||
|
|
||||||
|
__builtin_memcpy(output, header->region, 4);
|
||||||
|
output[4] = 0;
|
||||||
|
|
||||||
|
return __builtin_strlen(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentifierSet *ExtendedParser::getIdentifiers(void) {
|
||||||
|
if (!(flags & DATA_HAS_PUBLIC_SECTION))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return reinterpret_cast<IdentifierSet *>(
|
||||||
|
&_dump.data[sizeof(ExtendedHeader) + sizeof(PublicIdentifierSet)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtendedParser::flush(void) {
|
||||||
|
// Copy over the private identifiers to the public data area. On X76F041
|
||||||
|
// carts this area is in the last sector, while on ZS01 carts it is placed
|
||||||
|
// in the first 32 bytes.
|
||||||
|
auto pri = getIdentifiers();
|
||||||
|
auto pub = reinterpret_cast<PublicIdentifierSet *>(
|
||||||
|
&_getPublicData()[sizeof(ExtendedHeader)]
|
||||||
|
);
|
||||||
|
|
||||||
|
pub->traceID.copyFrom(pri->traceID.data);
|
||||||
|
pub->cartID.copyFrom(pri->cartID.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExtendedParser::validate(void) {
|
||||||
|
return (Parser::validate() && _getHeader()->validateChecksum());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Data format identification */
|
||||||
|
|
||||||
|
struct KnownFormat {
|
||||||
|
public:
|
||||||
|
const char *name;
|
||||||
|
FormatType format;
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr int _NUM_KNOWN_FORMATS = 8;
|
||||||
|
|
||||||
|
static const KnownFormat _KNOWN_FORMATS[_NUM_KNOWN_FORMATS]{
|
||||||
|
{
|
||||||
|
// Used by GCB48 (and possibly other games?)
|
||||||
|
.name = "region string only",
|
||||||
|
.format = SIMPLE,
|
||||||
|
.flags = DATA_HAS_PUBLIC_SECTION
|
||||||
|
}, {
|
||||||
|
.name = "basic with no IDs",
|
||||||
|
.format = BASIC,
|
||||||
|
.flags = 0
|
||||||
|
}, {
|
||||||
|
.name = "basic with TID",
|
||||||
|
.format = BASIC,
|
||||||
|
.flags = DATA_HAS_TRACE_ID
|
||||||
|
}, {
|
||||||
|
.name = "basic with TID, SID",
|
||||||
|
.format = BASIC,
|
||||||
|
.flags = DATA_HAS_TRACE_ID | DATA_HAS_CART_ID
|
||||||
|
}, {
|
||||||
|
.name = "basic with code prefix, TID, SID",
|
||||||
|
.format = BASIC,
|
||||||
|
.flags = DATA_HAS_CODE_PREFIX | DATA_HAS_TRACE_ID | DATA_HAS_CART_ID
|
||||||
|
}, {
|
||||||
|
// Used by most pre-ZS01 Bemani games
|
||||||
|
.name = "basic with code prefix, all IDs",
|
||||||
|
.format = BASIC,
|
||||||
|
.flags = DATA_HAS_CODE_PREFIX | DATA_HAS_TRACE_ID | DATA_HAS_CART_ID
|
||||||
|
| DATA_HAS_INSTALL_ID | DATA_HAS_SYSTEM_ID
|
||||||
|
}, {
|
||||||
|
// Used by early (pre-digital-I/O) Bemani games
|
||||||
|
.name = "extended with no IDs",
|
||||||
|
.format = EXTENDED,
|
||||||
|
.flags = DATA_HAS_CODE_PREFIX
|
||||||
|
}, {
|
||||||
|
// Used by GE936/GK936 and all ZS01 Bemani games
|
||||||
|
.name = "extended with all IDs",
|
||||||
|
.format = EXTENDED,
|
||||||
|
.flags = DATA_HAS_CODE_PREFIX | DATA_HAS_TRACE_ID | DATA_HAS_CART_ID
|
||||||
|
| DATA_HAS_INSTALL_ID | DATA_HAS_SYSTEM_ID | DATA_HAS_PUBLIC_SECTION
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
bool isValidRegion(const char *region) {
|
bool isValidRegion(const char *region) {
|
||||||
// Character 0: region (A=Asia?, E=Europe, J=Japan, K=Korea, S=?, U=US)
|
// Character 0: region (A=Asia?, E=Europe, J=Japan, K=Korea, S=?, U=US)
|
||||||
@ -36,235 +175,38 @@ bool isValidRegion(const char *region) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Common data structures */
|
Parser *newCartParser(Dump &dump, FormatType formatType, uint8_t flags) {
|
||||||
|
|
||||||
void BasicHeader::updateChecksum(void) {
|
|
||||||
auto value = util::sum(reinterpret_cast<const uint8_t *>(this), 4);
|
|
||||||
|
|
||||||
checksum = uint8_t((value & 0xff) ^ 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BasicHeader::validateChecksum(void) const {
|
|
||||||
auto value = util::sum(reinterpret_cast<const uint8_t *>(this), 4);
|
|
||||||
|
|
||||||
return (checksum == ((value & 0xff) ^ 0xff));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExtendedHeader::updateChecksum(void) {
|
|
||||||
auto value = util::sum(reinterpret_cast<const uint16_t *>(this), 14);
|
|
||||||
|
|
||||||
checksum = uint16_t(value & 0xffff);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExtendedHeader::validateChecksum(void) const {
|
|
||||||
auto value = util::sum(reinterpret_cast<const uint16_t *>(this), 14);
|
|
||||||
|
|
||||||
return (checksum == (value & 0xffff));
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t IdentifierSet::getFlags(void) const {
|
|
||||||
uint8_t flags = 0;
|
|
||||||
|
|
||||||
if (!traceID.isEmpty())
|
|
||||||
flags |= DATA_HAS_TRACE_ID;
|
|
||||||
if (!cartID.isEmpty())
|
|
||||||
flags |= DATA_HAS_CART_ID;
|
|
||||||
if (!installID.isEmpty())
|
|
||||||
flags |= DATA_HAS_INSTALL_ID;
|
|
||||||
if (!systemID.isEmpty())
|
|
||||||
flags |= DATA_HAS_SYSTEM_ID;
|
|
||||||
|
|
||||||
return flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IdentifierSet::setInstallID(uint8_t prefix) {
|
|
||||||
installID.clear();
|
|
||||||
|
|
||||||
installID.data[0] = prefix;
|
|
||||||
installID.updateChecksum();
|
|
||||||
}
|
|
||||||
|
|
||||||
void IdentifierSet::updateTraceID(uint8_t prefix, int param) {
|
|
||||||
traceID.clear();
|
|
||||||
|
|
||||||
uint8_t *input = &cartID.data[1];
|
|
||||||
uint16_t checksum = 0;
|
|
||||||
|
|
||||||
switch (prefix) {
|
|
||||||
case 0x81:
|
|
||||||
// TODO: reverse engineer this TID format
|
|
||||||
traceID.data[2] = 0;
|
|
||||||
traceID.data[5] = 0;
|
|
||||||
traceID.data[6] = 0;
|
|
||||||
|
|
||||||
LOG("prefix=0x81");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x82:
|
|
||||||
for (size_t i = 0; i < (sizeof(cartID.data) - 2); i++) {
|
|
||||||
uint8_t value = *(input++);
|
|
||||||
|
|
||||||
for (int j = 0; j < 8; j++, value >>= 1) {
|
|
||||||
if (value % 2)
|
|
||||||
checksum ^= 1 << (i % param);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
traceID.data[1] = checksum >> 8;
|
|
||||||
traceID.data[2] = checksum & 0xff;
|
|
||||||
|
|
||||||
LOG("prefix=0x82, checksum=%04x", checksum);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
LOG("unknown prefix 0x%02x", prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
traceID.data[0] = prefix;
|
|
||||||
traceID.updateChecksum();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Data formats */
|
|
||||||
|
|
||||||
bool CartData::validate(void) {
|
|
||||||
char region[8];
|
|
||||||
|
|
||||||
if (getRegion(region) < REGION_MIN_LENGTH)
|
|
||||||
return false;
|
|
||||||
if (!isValidRegion(region))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto id = getIdentifiers();
|
|
||||||
|
|
||||||
if (id) {
|
|
||||||
uint8_t idFlags = flags & (
|
|
||||||
DATA_HAS_TRACE_ID | DATA_HAS_CART_ID | DATA_HAS_INSTALL_ID |
|
|
||||||
DATA_HAS_SYSTEM_ID
|
|
||||||
);
|
|
||||||
|
|
||||||
if (id->getFlags() != idFlags)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t SimpleCartData::getRegion(char *output) const {
|
|
||||||
auto header = _getHeader();
|
|
||||||
|
|
||||||
__builtin_memcpy(output, header->region, 4);
|
|
||||||
output[4] = 0;
|
|
||||||
|
|
||||||
return __builtin_strlen(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t BasicCartData::getRegion(char *output) const {
|
|
||||||
auto header = _getHeader();
|
|
||||||
|
|
||||||
output[0] = header->region[0];
|
|
||||||
output[1] = header->region[1];
|
|
||||||
output[2] = 0;
|
|
||||||
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentifierSet *BasicCartData::getIdentifiers(void) {
|
|
||||||
return reinterpret_cast<IdentifierSet *>(&_dump.data[sizeof(BasicHeader)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BasicCartData::validate(void) {
|
|
||||||
return (CartData::validate() && _getHeader()->validateChecksum());
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ExtendedCartData::getRegion(char *output) const {
|
|
||||||
auto header = _getHeader();
|
|
||||||
|
|
||||||
__builtin_memcpy(output, header->region, 4);
|
|
||||||
output[4] = 0;
|
|
||||||
|
|
||||||
return __builtin_strlen(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
IdentifierSet *ExtendedCartData::getIdentifiers(void) {
|
|
||||||
if (!(flags & DATA_HAS_PUBLIC_SECTION))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return reinterpret_cast<IdentifierSet *>(
|
|
||||||
&_dump.data[sizeof(ExtendedHeader) + sizeof(PublicIdentifierSet)]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExtendedCartData::flush(void) {
|
|
||||||
// Copy over the private identifiers to the public data area. On X76F041
|
|
||||||
// carts this area is in the last sector, while on ZS01 carts it is placed
|
|
||||||
// in the first 32 bytes.
|
|
||||||
auto pri = getIdentifiers();
|
|
||||||
auto pub = reinterpret_cast<PublicIdentifierSet *>(
|
|
||||||
&_getPublicData()[sizeof(ExtendedHeader)]
|
|
||||||
);
|
|
||||||
|
|
||||||
pub->traceID.copyFrom(pri->traceID.data);
|
|
||||||
pub->cartID.copyFrom(pri->cartID.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExtendedCartData::validate(void) {
|
|
||||||
return (CartData::validate() && _getHeader()->validateChecksum());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Data format identification */
|
|
||||||
|
|
||||||
struct KnownFormat {
|
|
||||||
public:
|
|
||||||
FormatType formatType;
|
|
||||||
uint8_t flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr int _NUM_KNOWN_FORMATS = 8;
|
|
||||||
|
|
||||||
// More variants may exist.
|
|
||||||
static const KnownFormat _KNOWN_FORMATS[_NUM_KNOWN_FORMATS]{
|
|
||||||
{ SIMPLE, DATA_HAS_PUBLIC_SECTION },
|
|
||||||
{ BASIC, 0 },
|
|
||||||
{ BASIC, DATA_HAS_TRACE_ID },
|
|
||||||
{ BASIC, DATA_HAS_TRACE_ID | DATA_HAS_CART_ID },
|
|
||||||
{ BASIC, DATA_HAS_CODE_PREFIX | DATA_HAS_TRACE_ID | DATA_HAS_CART_ID },
|
|
||||||
{ BASIC, DATA_HAS_CODE_PREFIX | DATA_HAS_TRACE_ID | DATA_HAS_CART_ID
|
|
||||||
| DATA_HAS_INSTALL_ID | DATA_HAS_SYSTEM_ID },
|
|
||||||
{ EXTENDED, 0 },
|
|
||||||
{ EXTENDED, DATA_HAS_CODE_PREFIX | DATA_HAS_TRACE_ID | DATA_HAS_CART_ID
|
|
||||||
| DATA_HAS_INSTALL_ID | DATA_HAS_SYSTEM_ID | DATA_HAS_PUBLIC_SECTION },
|
|
||||||
};
|
|
||||||
|
|
||||||
CartData *createCartData(Dump &dump, FormatType formatType, uint8_t flags) {
|
|
||||||
switch (formatType) {
|
switch (formatType) {
|
||||||
case SIMPLE:
|
case SIMPLE:
|
||||||
return new SimpleCartData(dump, flags);
|
return new SimpleParser(dump, flags);
|
||||||
|
|
||||||
case BASIC:
|
case BASIC:
|
||||||
return new BasicCartData(dump, flags);
|
return new BasicParser(dump, flags);
|
||||||
|
|
||||||
case EXTENDED:
|
case EXTENDED:
|
||||||
return new ExtendedCartData(dump, flags);
|
return new ExtendedParser(dump, flags);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return new CartData(dump, flags);
|
return new Parser(dump, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CartData *createCartData(Dump &dump) {
|
Parser *newCartParser(Dump &dump) {
|
||||||
for (int i = 0; i < _NUM_KNOWN_FORMATS; i++) {
|
// Try all formats from the most complex one to the simplest.
|
||||||
auto &fmt = _KNOWN_FORMATS[i];
|
for (int i = _NUM_KNOWN_FORMATS - 1; i >= 0; i++) {
|
||||||
CartData *data = createCartData(dump, fmt.formatType, fmt.flags);
|
auto &format = _KNOWN_FORMATS[i];
|
||||||
|
Parser *parser = newCartParser(dump, format.format, format.flags);
|
||||||
|
|
||||||
if (data->validate()) {
|
if (parser->validate()) {
|
||||||
LOG("found known format, index=%d", i);
|
LOG("found known data format");
|
||||||
return data;
|
LOG("%s, index=%d", format.name, i);
|
||||||
|
return parser;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete data;
|
delete parser;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG("unrecognized dump format");
|
LOG("unrecognized data format");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
114
src/cartdata.hpp
114
src/cartdata.hpp
@ -5,92 +5,17 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "asset.hpp"
|
#include "asset.hpp"
|
||||||
#include "cartio.hpp"
|
#include "cart.hpp"
|
||||||
|
|
||||||
namespace cart {
|
namespace cart {
|
||||||
|
|
||||||
/* Definitions */
|
/* Cartridge data parsers */
|
||||||
|
|
||||||
enum FormatType : uint8_t {
|
|
||||||
BLANK = 0,
|
|
||||||
SIMPLE = 1,
|
|
||||||
BASIC = 2,
|
|
||||||
EXTENDED = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
// | | Simple | Basic | Extended |
|
|
||||||
// | :---------------------- | :-------- | :------- | :-------- |
|
|
||||||
// | DATA_HAS_CODE_PREFIX | | Optional | Mandatory |
|
|
||||||
// | DATA_HAS_*_ID | | Optional | Optional |
|
|
||||||
// | DATA_HAS_PUBLIC_SECTION | Mandatory | | Optional |
|
|
||||||
|
|
||||||
enum FormatFlags : uint8_t {
|
|
||||||
DATA_HAS_CODE_PREFIX = 1 << 0,
|
|
||||||
DATA_HAS_TRACE_ID = 1 << 1,
|
|
||||||
DATA_HAS_CART_ID = 1 << 2,
|
|
||||||
DATA_HAS_INSTALL_ID = 1 << 3,
|
|
||||||
DATA_HAS_SYSTEM_ID = 1 << 4,
|
|
||||||
DATA_HAS_PUBLIC_SECTION = 1 << 5
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr size_t CODE_LENGTH = 5;
|
static constexpr size_t CODE_LENGTH = 5;
|
||||||
static constexpr size_t REGION_MIN_LENGTH = 2;
|
static constexpr size_t REGION_MIN_LENGTH = 2;
|
||||||
static constexpr size_t REGION_MAX_LENGTH = 5;
|
static constexpr size_t REGION_MAX_LENGTH = 5;
|
||||||
|
|
||||||
bool isValidRegion(const char *region);
|
class Parser {
|
||||||
|
|
||||||
/* Common data structures */
|
|
||||||
|
|
||||||
class [[gnu::packed]] SimpleHeader {
|
|
||||||
public:
|
|
||||||
char region[4];
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] BasicHeader {
|
|
||||||
public:
|
|
||||||
char region[2], codePrefix[2];
|
|
||||||
uint8_t checksum, _pad[3];
|
|
||||||
|
|
||||||
void updateChecksum(void);
|
|
||||||
bool validateChecksum(void) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] ExtendedHeader {
|
|
||||||
public:
|
|
||||||
char code[8];
|
|
||||||
uint16_t year; // BCD, can be little endian, big endian or zero
|
|
||||||
char region[4];
|
|
||||||
uint16_t checksum;
|
|
||||||
|
|
||||||
void updateChecksum(void);
|
|
||||||
bool validateChecksum(void) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] IdentifierSet {
|
|
||||||
public:
|
|
||||||
Identifier traceID, cartID, installID, systemID; // aka TID, SID, MID, XID
|
|
||||||
|
|
||||||
inline void clear(void) {
|
|
||||||
__builtin_memset(this, 0, sizeof(IdentifierSet));
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t getFlags(void) const;
|
|
||||||
void setInstallID(uint8_t prefix);
|
|
||||||
void updateTraceID(uint8_t prefix, int param);
|
|
||||||
};
|
|
||||||
|
|
||||||
class [[gnu::packed]] PublicIdentifierSet {
|
|
||||||
public:
|
|
||||||
Identifier traceID, cartID; // aka TID, SID
|
|
||||||
|
|
||||||
inline void clear(void) {
|
|
||||||
__builtin_memset(this, 0, sizeof(PublicIdentifierSet));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Data formats */
|
|
||||||
|
|
||||||
class CartData {
|
|
||||||
protected:
|
protected:
|
||||||
Dump &_dump;
|
Dump &_dump;
|
||||||
|
|
||||||
@ -104,52 +29,53 @@ protected:
|
|||||||
public:
|
public:
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
|
|
||||||
inline CartData(Dump &dump, uint8_t flags = 0)
|
inline Parser(Dump &dump, uint8_t flags = 0)
|
||||||
: _dump(dump), flags(flags) {}
|
: _dump(dump), flags(flags) {}
|
||||||
|
|
||||||
|
virtual ~Parser(void) {}
|
||||||
virtual size_t getRegion(char *output) const { return 0; }
|
virtual size_t getRegion(char *output) const { return 0; }
|
||||||
virtual IdentifierSet *getIdentifiers(void) { return nullptr; }
|
virtual IdentifierSet *getIdentifiers(void) { return nullptr; }
|
||||||
virtual void flush(void) {}
|
virtual void flush(void) {}
|
||||||
virtual bool validate(void);
|
virtual bool validate(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
class SimpleCartData : public CartData {
|
class SimpleParser : public Parser {
|
||||||
private:
|
private:
|
||||||
inline SimpleHeader *_getHeader(void) const {
|
inline SimpleHeader *_getHeader(void) const {
|
||||||
return reinterpret_cast<SimpleHeader *>(_getPublicData());
|
return reinterpret_cast<SimpleHeader *>(_getPublicData());
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline SimpleCartData(Dump &dump, uint8_t flags = 0)
|
inline SimpleParser(Dump &dump, uint8_t flags = 0)
|
||||||
: CartData(dump, flags | DATA_HAS_PUBLIC_SECTION) {}
|
: Parser(dump, flags | DATA_HAS_PUBLIC_SECTION) {}
|
||||||
|
|
||||||
size_t getRegion(char *output) const;
|
size_t getRegion(char *output) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BasicCartData : public CartData {
|
class BasicParser : public Parser {
|
||||||
private:
|
private:
|
||||||
inline BasicHeader *_getHeader(void) const {
|
inline BasicHeader *_getHeader(void) const {
|
||||||
return reinterpret_cast<BasicHeader *>(_getPublicData());
|
return reinterpret_cast<BasicHeader *>(_getPublicData());
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline BasicCartData(Dump &dump, uint8_t flags = 0)
|
inline BasicParser(Dump &dump, uint8_t flags = 0)
|
||||||
: CartData(dump, flags) {}
|
: Parser(dump, flags) {}
|
||||||
|
|
||||||
size_t getRegion(char *output) const;
|
size_t getRegion(char *output) const;
|
||||||
IdentifierSet *getIdentifiers(void);
|
IdentifierSet *getIdentifiers(void);
|
||||||
bool validate(void);
|
bool validate(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
class ExtendedCartData : public CartData {
|
class ExtendedParser : public Parser {
|
||||||
private:
|
private:
|
||||||
inline ExtendedHeader *_getHeader(void) const {
|
inline ExtendedHeader *_getHeader(void) const {
|
||||||
return reinterpret_cast<ExtendedHeader *>(_getPublicData());
|
return reinterpret_cast<ExtendedHeader *>(_getPublicData());
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline ExtendedCartData(Dump &dump, uint8_t flags = 0)
|
inline ExtendedParser(Dump &dump, uint8_t flags = 0)
|
||||||
: CartData(dump, flags | DATA_HAS_CODE_PREFIX) {}
|
: Parser(dump, flags | DATA_HAS_CODE_PREFIX) {}
|
||||||
|
|
||||||
size_t getRegion(char *output) const;
|
size_t getRegion(char *output) const;
|
||||||
IdentifierSet *getIdentifiers(void);
|
IdentifierSet *getIdentifiers(void);
|
||||||
@ -157,8 +83,9 @@ public:
|
|||||||
bool validate(void);
|
bool validate(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
CartData *createCartData(Dump &dump, FormatType formatType, uint8_t flags = 0);
|
bool isValidRegion(const char *region);
|
||||||
CartData *createCartData(Dump &dump);
|
Parser *newCartParser(Dump &dump, FormatType formatType, uint8_t flags = 0);
|
||||||
|
Parser *newCartParser(Dump &dump);
|
||||||
|
|
||||||
/* Cartridge database */
|
/* Cartridge database */
|
||||||
|
|
||||||
@ -168,9 +95,10 @@ public:
|
|||||||
FormatType formatType;
|
FormatType formatType;
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
|
|
||||||
uint8_t traceIDPrefix, traceIDParam, installIDPrefix, _reserved[2];
|
uint8_t traceIDPrefix, traceIDParam, installIDPrefix;
|
||||||
uint8_t dataKey[8];
|
uint16_t year;
|
||||||
char code[8], region[8], name[64];
|
uint8_t dataKey[8];
|
||||||
|
char code[8], region[8], name[64];
|
||||||
|
|
||||||
inline int compare(const char *_code, const char *_region) const {
|
inline int compare(const char *_code, const char *_region) const {
|
||||||
int diff = __builtin_strncmp(code, _code, CODE_LENGTH + 1);
|
int diff = __builtin_strncmp(code, _code, CODE_LENGTH + 1);
|
||||||
|
138
src/cartio.cpp
138
src/cartio.cpp
@ -1,90 +1,20 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
#include "vendor/miniz.h"
|
#include "cart.hpp"
|
||||||
#include "cartio.hpp"
|
#include "cartio.hpp"
|
||||||
#include "io.hpp"
|
#include "io.hpp"
|
||||||
#include "util.hpp"
|
|
||||||
#include "zs01.hpp"
|
#include "zs01.hpp"
|
||||||
|
|
||||||
namespace cart {
|
namespace cart {
|
||||||
|
|
||||||
/* Common data structures */
|
|
||||||
|
|
||||||
void Identifier::updateChecksum(void) {
|
|
||||||
data[7] = (util::sum(data, 7) & 0xff) ^ 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Identifier::validateChecksum(void) const {
|
|
||||||
uint8_t value = (util::sum(data, 7) & 0xff) ^ 0xff;
|
|
||||||
|
|
||||||
if (value != data[7]) {
|
|
||||||
LOG("checksum mismatch, exp=0x%02x, got=0x%02x", value, data[7]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Identifier::updateDSCRC(void) {
|
|
||||||
data[7] = util::dsCRC8(data, 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Identifier::validateDSCRC(void) const {
|
|
||||||
uint8_t value = util::dsCRC8(data, 7);
|
|
||||||
|
|
||||||
if (value != data[7]) {
|
|
||||||
LOG("CRC mismatch, exp=0x%02x, got=0x%02x", value, data[7]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dump structure and utilities */
|
|
||||||
|
|
||||||
const ChipSize CHIP_SIZES[NUM_CHIP_TYPES]{
|
|
||||||
{ .dataLength = 0, .publicDataOffset = 0, .publicDataLength = 0 },
|
|
||||||
{ .dataLength = 512, .publicDataOffset = 384, .publicDataLength = 128 },
|
|
||||||
{ .dataLength = 112, .publicDataOffset = 0, .publicDataLength = 0 },
|
|
||||||
{ .dataLength = 112, .publicDataOffset = 0, .publicDataLength = 32 }
|
|
||||||
};
|
|
||||||
|
|
||||||
size_t Dump::toQRString(char *output) const {
|
|
||||||
uint8_t compressed[MAX_QR_STRING_LENGTH];
|
|
||||||
size_t uncompLength = getDumpLength();
|
|
||||||
size_t compLength = MAX_QR_STRING_LENGTH;
|
|
||||||
|
|
||||||
int error = mz_compress2(
|
|
||||||
compressed, reinterpret_cast<mz_ulong *>(&compLength),
|
|
||||||
reinterpret_cast<const uint8_t *>(this), uncompLength,
|
|
||||||
MZ_BEST_COMPRESSION
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error != MZ_OK) {
|
|
||||||
LOG("compression error, code=%d", error);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
LOG(
|
|
||||||
"dump compressed, size=%d, ratio=%d%%", compLength,
|
|
||||||
compLength * 100 / uncompLength
|
|
||||||
);
|
|
||||||
|
|
||||||
compLength = util::encodeBase45(&output[5], compressed, compLength);
|
|
||||||
__builtin_memcpy(&output[0], "573::", 5);
|
|
||||||
__builtin_memcpy(&output[compLength + 5], "::", 3);
|
|
||||||
|
|
||||||
return compLength + 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Functions common to all cartridge drivers */
|
/* Functions common to all cartridge drivers */
|
||||||
|
|
||||||
static constexpr int _X76_MAX_ACK_POLLS = 5;
|
static constexpr int _X76_MAX_ACK_POLLS = 5;
|
||||||
static constexpr int _X76_WRITE_DELAY = 10000;
|
static constexpr int _X76_WRITE_DELAY = 10000;
|
||||||
static constexpr int _ZS01_PACKET_DELAY = 30000;
|
static constexpr int _ZS01_PACKET_DELAY = 30000;
|
||||||
|
|
||||||
CartError Cart::readSystemID(void) {
|
DriverError Driver::readSystemID(void) {
|
||||||
auto mask = setInterruptMask(0);
|
auto mask = setInterruptMask(0);
|
||||||
|
|
||||||
if (!io::dsDIOReset()) {
|
if (!io::dsDIOReset()) {
|
||||||
@ -110,7 +40,7 @@ CartError Cart::readSystemID(void) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError X76Cart::readCartID(void) {
|
DriverError X76Driver::readCartID(void) {
|
||||||
auto mask = setInterruptMask(0);
|
auto mask = setInterruptMask(0);
|
||||||
|
|
||||||
if (!io::dsCartReset()) {
|
if (!io::dsCartReset()) {
|
||||||
@ -136,7 +66,7 @@ CartError X76Cart::readCartID(void) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError X76Cart::_x76Command(
|
DriverError X76Driver::_x76Command(
|
||||||
uint8_t cmd, uint8_t param, uint8_t pollByte
|
uint8_t cmd, uint8_t param, uint8_t pollByte
|
||||||
) const {
|
) const {
|
||||||
io::i2cStartWithCS();
|
io::i2cStartWithCS();
|
||||||
@ -190,7 +120,7 @@ enum X76F041ConfigOp : uint8_t {
|
|||||||
_X76F041_CFG_ERASE = 0x70
|
_X76F041_CFG_ERASE = 0x70
|
||||||
};
|
};
|
||||||
|
|
||||||
CartError X76F041Cart::readPrivateData(void) {
|
DriverError X76F041Driver::readPrivateData(void) {
|
||||||
// Reads can be done with any block size, but a single read operation can't
|
// Reads can be done with any block size, but a single read operation can't
|
||||||
// cross 128-byte block boundaries.
|
// cross 128-byte block boundaries.
|
||||||
for (int i = 0; i < 512; i += 128) {
|
for (int i = 0; i < 512; i += 128) {
|
||||||
@ -216,7 +146,7 @@ CartError X76F041Cart::readPrivateData(void) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError X76F041Cart::writeData(void) {
|
DriverError X76F041Driver::writeData(void) {
|
||||||
// Writes can only be done in 8-byte blocks.
|
// Writes can only be done in 8-byte blocks.
|
||||||
for (int i = 0; i < 512; i += 8) {
|
for (int i = 0; i < 512; i += 8) {
|
||||||
auto error = _x76Command(
|
auto error = _x76Command(
|
||||||
@ -236,7 +166,7 @@ CartError X76F041Cart::writeData(void) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError X76F041Cart::erase(void) {
|
DriverError X76F041Driver::erase(void) {
|
||||||
auto error = _x76Command(
|
auto error = _x76Command(
|
||||||
_X76F041_CONFIG, _X76F041_CFG_ERASE, _X76F041_ACK_POLL
|
_X76F041_CONFIG, _X76F041_CFG_ERASE, _X76F041_ACK_POLL
|
||||||
);
|
);
|
||||||
@ -247,7 +177,7 @@ CartError X76F041Cart::erase(void) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError X76F041Cart::setDataKey(const uint8_t *key) {
|
DriverError X76F041Driver::setDataKey(const uint8_t *key) {
|
||||||
auto error = _x76Command(
|
auto error = _x76Command(
|
||||||
_X76F041_CONFIG, _X76F041_CFG_SET_DATA_KEY, _X76F041_ACK_POLL
|
_X76F041_CONFIG, _X76F041_CFG_SET_DATA_KEY, _X76F041_ACK_POLL
|
||||||
);
|
);
|
||||||
@ -283,25 +213,27 @@ enum X76F100Command : uint8_t {
|
|||||||
|
|
||||||
// TODO: actually implement this (even though no X76F100 carts were ever made)
|
// TODO: actually implement this (even though no X76F100 carts were ever made)
|
||||||
|
|
||||||
CartError X76F100Cart::readPrivateData(void) {
|
DriverError X76F100Driver::readPrivateData(void) {
|
||||||
return UNSUPPORTED_OP;
|
return UNSUPPORTED_OP;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError X76F100Cart::writeData(void) {
|
DriverError X76F100Driver::writeData(void) {
|
||||||
return UNSUPPORTED_OP;
|
return UNSUPPORTED_OP;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError X76F100Cart::erase(void) {
|
DriverError X76F100Driver::erase(void) {
|
||||||
return UNSUPPORTED_OP;
|
return UNSUPPORTED_OP;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError X76F100Cart::setDataKey(const uint8_t *key) {
|
DriverError X76F100Driver::setDataKey(const uint8_t *key) {
|
||||||
return UNSUPPORTED_OP;
|
return UNSUPPORTED_OP;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ZS01 driver */
|
/* ZS01 driver */
|
||||||
|
|
||||||
CartError ZS01Cart::_transact(zs01::Packet &request, zs01::Packet &response) {
|
DriverError ZS01Driver::_transact(
|
||||||
|
zs01::Packet &request, zs01::Packet &response
|
||||||
|
) {
|
||||||
io::i2cStart();
|
io::i2cStart();
|
||||||
|
|
||||||
if (!io::i2cWriteBytes(
|
if (!io::i2cWriteBytes(
|
||||||
@ -328,9 +260,9 @@ CartError ZS01Cart::_transact(zs01::Packet &request, zs01::Packet &response) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError ZS01Cart::readCartID(void) {
|
DriverError ZS01Driver::readCartID(void) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
CartError error;
|
DriverError error;
|
||||||
|
|
||||||
request.address = zs01::ADDR_ZS01_ID;
|
request.address = zs01::ADDR_ZS01_ID;
|
||||||
request.encodeReadRequest();
|
request.encodeReadRequest();
|
||||||
@ -360,14 +292,14 @@ CartError ZS01Cart::readCartID(void) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError ZS01Cart::readPublicData(void) {
|
DriverError ZS01Driver::readPublicData(void) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
|
|
||||||
for (int i = zs01::ADDR_PUBLIC; i < zs01::ADDR_PUBLIC_END; i++) {
|
for (int i = zs01::ADDR_PUBLIC; i < zs01::ADDR_PUBLIC_END; i++) {
|
||||||
request.address = i;
|
request.address = i;
|
||||||
request.encodeReadRequest();
|
request.encodeReadRequest();
|
||||||
|
|
||||||
CartError error = _transact(request, response);
|
DriverError error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
@ -378,7 +310,7 @@ CartError ZS01Cart::readPublicData(void) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError ZS01Cart::readPrivateData(void) {
|
DriverError ZS01Driver::readPrivateData(void) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
zs01::Key key;
|
zs01::Key key;
|
||||||
|
|
||||||
@ -388,7 +320,7 @@ CartError ZS01Cart::readPrivateData(void) {
|
|||||||
request.address = i;
|
request.address = i;
|
||||||
request.encodeReadRequest(key, _encoderState);
|
request.encodeReadRequest(key, _encoderState);
|
||||||
|
|
||||||
CartError error = _transact(request, response);
|
DriverError error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
@ -400,7 +332,7 @@ CartError ZS01Cart::readPrivateData(void) {
|
|||||||
request.address = zs01::ADDR_CONFIG;
|
request.address = zs01::ADDR_CONFIG;
|
||||||
request.encodeReadRequest(key, _encoderState);
|
request.encodeReadRequest(key, _encoderState);
|
||||||
|
|
||||||
CartError error = _transact(request, response);
|
DriverError error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
@ -410,7 +342,7 @@ CartError ZS01Cart::readPrivateData(void) {
|
|||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError ZS01Cart::writeData(void) {
|
DriverError ZS01Driver::writeData(void) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
zs01::Key key;
|
zs01::Key key;
|
||||||
|
|
||||||
@ -421,7 +353,7 @@ CartError ZS01Cart::writeData(void) {
|
|||||||
request.copyFrom(&_dump.data[i * sizeof(request.data)]);
|
request.copyFrom(&_dump.data[i * sizeof(request.data)]);
|
||||||
request.encodeWriteRequest(key, _encoderState);
|
request.encodeWriteRequest(key, _encoderState);
|
||||||
|
|
||||||
CartError error = _transact(request, response);
|
DriverError error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@ -433,7 +365,7 @@ CartError ZS01Cart::writeData(void) {
|
|||||||
return _transact(request, response);
|
return _transact(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError ZS01Cart::erase(void) {
|
DriverError ZS01Driver::erase(void) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
zs01::Key key;
|
zs01::Key key;
|
||||||
|
|
||||||
@ -446,7 +378,7 @@ CartError ZS01Cart::erase(void) {
|
|||||||
return _transact(request, response);
|
return _transact(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
CartError ZS01Cart::setDataKey(const uint8_t *key) {
|
DriverError ZS01Driver::setDataKey(const uint8_t *key) {
|
||||||
zs01::Packet request, response;
|
zs01::Packet request, response;
|
||||||
zs01::Key newKey;
|
zs01::Key newKey;
|
||||||
|
|
||||||
@ -456,7 +388,7 @@ CartError ZS01Cart::setDataKey(const uint8_t *key) {
|
|||||||
request.copyFrom(key);
|
request.copyFrom(key);
|
||||||
request.encodeWriteRequest(newKey, _encoderState);
|
request.encodeWriteRequest(newKey, _encoderState);
|
||||||
|
|
||||||
CartError error = _transact(request, response);
|
DriverError error = _transact(request, response);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
@ -473,30 +405,30 @@ enum ChipIdentifier : uint32_t {
|
|||||||
_ID_ZS01 = 0x5a530001
|
_ID_ZS01 = 0x5a530001
|
||||||
};
|
};
|
||||||
|
|
||||||
Cart *createCart(Dump &dump) {
|
Driver *newCartDriver(Dump &dump) {
|
||||||
if (!io::getCartInsertionStatus()) {
|
if (!io::getCartInsertionStatus()) {
|
||||||
LOG("DSR not asserted");
|
LOG("DSR not asserted");
|
||||||
return new Cart(dump);
|
return new Driver(dump);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t id1 = io::i2cResetZS01();
|
uint32_t id1 = io::i2cResetZS01();
|
||||||
LOG("id1=0x%08x", id1);
|
LOG("detecting ZS01, id1=0x%08x", id1);
|
||||||
|
|
||||||
if (id1 == _ID_ZS01)
|
if (id1 == _ID_ZS01)
|
||||||
return new ZS01Cart(dump);
|
return new ZS01Driver(dump);
|
||||||
|
|
||||||
uint32_t id2 = io::i2cResetX76();
|
uint32_t id2 = io::i2cResetX76();
|
||||||
LOG("id2=0x%08x", id2);
|
LOG("detecting X76, id2=0x%08x", id2);
|
||||||
|
|
||||||
switch (id2) {
|
switch (id2) {
|
||||||
case _ID_X76F041:
|
case _ID_X76F041:
|
||||||
return new X76F041Cart(dump);
|
return new X76F041Driver(dump);
|
||||||
|
|
||||||
//case _ID_X76F100:
|
//case _ID_X76F100:
|
||||||
//return new X76F100Cart(dump);
|
//return new X76F100Driver(dump);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return new Cart(dump);
|
return new Driver(dump);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
180
src/cartio.hpp
180
src/cartio.hpp
@ -1,16 +1,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
#include "zs01.hpp"
|
#include "zs01.hpp"
|
||||||
|
|
||||||
namespace cart {
|
namespace cart {
|
||||||
|
|
||||||
/* Definitions */
|
enum DriverError {
|
||||||
|
|
||||||
enum CartError {
|
|
||||||
NO_ERROR = 0,
|
NO_ERROR = 0,
|
||||||
UNSUPPORTED_OP = 1,
|
UNSUPPORTED_OP = 1,
|
||||||
DS2401_NO_RESP = 2,
|
DS2401_NO_RESP = 2,
|
||||||
@ -23,108 +20,14 @@ enum CartError {
|
|||||||
ZS01_CRC_MISMATCH = 9
|
ZS01_CRC_MISMATCH = 9
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ChipType : uint8_t {
|
|
||||||
NONE = 0,
|
|
||||||
X76F041 = 1,
|
|
||||||
X76F100 = 2,
|
|
||||||
ZS01 = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DumpFlag : uint8_t {
|
|
||||||
DUMP_HAS_SYSTEM_ID = 1 << 0,
|
|
||||||
DUMP_HAS_CART_ID = 1 << 1,
|
|
||||||
DUMP_CONFIG_OK = 1 << 2,
|
|
||||||
DUMP_SYSTEM_ID_OK = 1 << 3,
|
|
||||||
DUMP_CART_ID_OK = 1 << 4,
|
|
||||||
DUMP_ZS_ID_OK = 1 << 5,
|
|
||||||
DUMP_PUBLIC_DATA_OK = 1 << 6,
|
|
||||||
DUMP_PRIVATE_DATA_OK = 1 << 7
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr int NUM_CHIP_TYPES = 4;
|
|
||||||
static constexpr size_t MAX_QR_STRING_LENGTH = 0x600;
|
|
||||||
|
|
||||||
/* Common data structures */
|
|
||||||
|
|
||||||
class [[gnu::packed]] Identifier {
|
|
||||||
public:
|
|
||||||
uint8_t data[8];
|
|
||||||
|
|
||||||
inline void copyFrom(const uint8_t *source) {
|
|
||||||
__builtin_memcpy(data, source, sizeof(data));
|
|
||||||
}
|
|
||||||
inline void copyTo(uint8_t *dest) const {
|
|
||||||
__builtin_memcpy(dest, data, sizeof(data));
|
|
||||||
}
|
|
||||||
inline void clear(void) {
|
|
||||||
__builtin_memset(data, 0, sizeof(data));
|
|
||||||
}
|
|
||||||
inline bool isEmpty(void) const {
|
|
||||||
return (util::sum(data, sizeof(data)) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline size_t toString(char *output) const {
|
|
||||||
return util::hexToString(output, data, sizeof(data), '-');
|
|
||||||
}
|
|
||||||
inline size_t toSerialNumber(char *output) const {
|
|
||||||
return util::serialNumberToString(output, &data[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateChecksum(void);
|
|
||||||
bool validateChecksum(void) const;
|
|
||||||
void updateDSCRC(void);
|
|
||||||
bool validateDSCRC(void) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Dump structure and utilities */
|
|
||||||
|
|
||||||
struct ChipSize {
|
|
||||||
public:
|
|
||||||
size_t dataLength, publicDataOffset, publicDataLength;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern const ChipSize CHIP_SIZES[NUM_CHIP_TYPES];
|
|
||||||
|
|
||||||
class [[gnu::packed]] Dump {
|
|
||||||
public:
|
|
||||||
ChipType chipType;
|
|
||||||
uint8_t flags;
|
|
||||||
|
|
||||||
Identifier systemID, cartID, zsID;
|
|
||||||
|
|
||||||
uint8_t dataKey[8], config[8];
|
|
||||||
uint8_t data[512];
|
|
||||||
|
|
||||||
inline const ChipSize &getChipSize(void) const {
|
|
||||||
return CHIP_SIZES[chipType];
|
|
||||||
}
|
|
||||||
inline size_t getDumpLength(void) const {
|
|
||||||
return (sizeof(Dump) - sizeof(data)) + getChipSize().dataLength;
|
|
||||||
}
|
|
||||||
inline void clear(void) {
|
|
||||||
__builtin_memset(this, 0, sizeof(Dump));
|
|
||||||
}
|
|
||||||
inline void clearData(void) {
|
|
||||||
__builtin_memset(data, 0, getChipSize().dataLength);
|
|
||||||
}
|
|
||||||
inline bool isDataEmpty(void) const {
|
|
||||||
size_t length = getChipSize().dataLength;
|
|
||||||
auto sum = util::sum(data, length);
|
|
||||||
|
|
||||||
return (!sum || (sum == (0xff * length)));
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t toQRString(char *output) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Cartridge driver classes */
|
/* Cartridge driver classes */
|
||||||
|
|
||||||
class Cart {
|
class Driver {
|
||||||
protected:
|
protected:
|
||||||
Dump &_dump;
|
Dump &_dump;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline Cart(Dump &dump, ChipType chipType = NONE, uint8_t flags = 0)
|
inline Driver(Dump &dump, ChipType chipType = NONE, uint8_t flags = 0)
|
||||||
: _dump(dump) {
|
: _dump(dump) {
|
||||||
dump.clear();
|
dump.clear();
|
||||||
|
|
||||||
@ -132,67 +35,68 @@ public:
|
|||||||
dump.flags = flags;
|
dump.flags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual CartError readSystemID(void);
|
virtual ~Driver(void) {}
|
||||||
virtual CartError readCartID(void) { return UNSUPPORTED_OP; }
|
virtual DriverError readSystemID(void);
|
||||||
virtual CartError readPublicData(void) { return UNSUPPORTED_OP; }
|
virtual DriverError readCartID(void) { return UNSUPPORTED_OP; }
|
||||||
virtual CartError readPrivateData(void) { return UNSUPPORTED_OP; }
|
virtual DriverError readPublicData(void) { return UNSUPPORTED_OP; }
|
||||||
virtual CartError writeData(void) { return UNSUPPORTED_OP; }
|
virtual DriverError readPrivateData(void) { return UNSUPPORTED_OP; }
|
||||||
virtual CartError erase(void) { return UNSUPPORTED_OP; }
|
virtual DriverError writeData(void) { return UNSUPPORTED_OP; }
|
||||||
virtual CartError setDataKey(const uint8_t *key) { return UNSUPPORTED_OP; }
|
virtual DriverError erase(void) { return UNSUPPORTED_OP; }
|
||||||
|
virtual DriverError setDataKey(const uint8_t *key) { return UNSUPPORTED_OP; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class [[gnu::packed]] X76Cart : public Cart {
|
class [[gnu::packed]] X76Driver : public Driver {
|
||||||
protected:
|
protected:
|
||||||
CartError _readDS2401(void);
|
DriverError _readDS2401(void);
|
||||||
CartError _x76Command(uint8_t cmd, uint8_t param, uint8_t pollByte) const;
|
DriverError _x76Command(uint8_t cmd, uint8_t param, uint8_t pollByte) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline X76Cart(Dump &dump, ChipType chipType)
|
inline X76Driver(Dump &dump, ChipType chipType)
|
||||||
: Cart(dump, chipType) {}
|
: Driver(dump, chipType) {}
|
||||||
|
|
||||||
CartError readCartID(void);
|
DriverError readCartID(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
class [[gnu::packed]] X76F041Cart : public X76Cart {
|
class [[gnu::packed]] X76F041Driver : public X76Driver {
|
||||||
public:
|
public:
|
||||||
inline X76F041Cart(Dump &dump)
|
inline X76F041Driver(Dump &dump)
|
||||||
: X76Cart(dump, X76F041) {}
|
: X76Driver(dump, X76F041) {}
|
||||||
|
|
||||||
CartError readPrivateData(void);
|
DriverError readPrivateData(void);
|
||||||
CartError writeData(void);
|
DriverError writeData(void);
|
||||||
CartError erase(void);
|
DriverError erase(void);
|
||||||
CartError setDataKey(const uint8_t *key);
|
DriverError setDataKey(const uint8_t *key);
|
||||||
};
|
};
|
||||||
|
|
||||||
class [[gnu::packed]] X76F100Cart : public X76Cart {
|
class [[gnu::packed]] X76F100Driver : public X76Driver {
|
||||||
public:
|
public:
|
||||||
inline X76F100Cart(Dump &dump)
|
inline X76F100Driver(Dump &dump)
|
||||||
: X76Cart(dump, X76F100) {}
|
: X76Driver(dump, X76F100) {}
|
||||||
|
|
||||||
CartError readPrivateData(void);
|
DriverError readPrivateData(void);
|
||||||
CartError writeData(void);
|
DriverError writeData(void);
|
||||||
CartError erase(void);
|
DriverError erase(void);
|
||||||
CartError setDataKey(const uint8_t *key);
|
DriverError setDataKey(const uint8_t *key);
|
||||||
};
|
};
|
||||||
|
|
||||||
class [[gnu::packed]] ZS01Cart : public Cart {
|
class [[gnu::packed]] ZS01Driver : public Driver {
|
||||||
private:
|
private:
|
||||||
uint8_t _encoderState;
|
uint8_t _encoderState;
|
||||||
|
|
||||||
CartError _transact(zs01::Packet &request, zs01::Packet &response);
|
DriverError _transact(zs01::Packet &request, zs01::Packet &response);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline ZS01Cart(Dump &dump)
|
inline ZS01Driver(Dump &dump)
|
||||||
: Cart(dump, ZS01, DUMP_HAS_CART_ID), _encoderState(0) {}
|
: Driver(dump, ZS01, DUMP_HAS_CART_ID), _encoderState(0) {}
|
||||||
|
|
||||||
CartError readCartID(void);
|
DriverError readCartID(void);
|
||||||
CartError readPublicData(void);
|
DriverError readPublicData(void);
|
||||||
CartError readPrivateData(void);
|
DriverError readPrivateData(void);
|
||||||
CartError writeData(void);
|
DriverError writeData(void);
|
||||||
CartError erase(void);
|
DriverError erase(void);
|
||||||
CartError setDataKey(const uint8_t *key);
|
DriverError setDataKey(const uint8_t *key);
|
||||||
};
|
};
|
||||||
|
|
||||||
Cart *createCart(Dump &dump);
|
Driver *newCartDriver(Dump &dump);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
62
src/gpu.cpp
62
src/gpu.cpp
@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "ps1/gpucmd.h"
|
#include "ps1/gpucmd.h"
|
||||||
#include "ps1/registers.h"
|
#include "ps1/registers.h"
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
#include "gpu.hpp"
|
#include "gpu.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
|
||||||
@ -49,20 +51,6 @@ size_t upload(const RectWH &rect, const void *data, bool wait) {
|
|||||||
|
|
||||||
/* Rendering context */
|
/* Rendering context */
|
||||||
|
|
||||||
void Context::_flushLayer(void) {
|
|
||||||
if (_currentListPtr == _lastListPtr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto layer = _drawBuffer().layers.pushItem();
|
|
||||||
assert(layer);
|
|
||||||
|
|
||||||
*(_currentListPtr++) = gp0_endTag(1);
|
|
||||||
*(_currentListPtr++) = gp0_irq();
|
|
||||||
|
|
||||||
*layer = _lastListPtr;
|
|
||||||
_lastListPtr = _currentListPtr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Context::_applyResolution(VideoMode mode, int shiftX, int shiftY) const {
|
void Context::_applyResolution(VideoMode mode, int shiftX, int shiftY) const {
|
||||||
GP1HorizontalRes hres;
|
GP1HorizontalRes hres;
|
||||||
GP1VerticalRes vres = (height > 256) ? GP1_VRES_512 : GP1_VRES_256;
|
GP1VerticalRes vres = (height > 256) ? GP1_VRES_512 : GP1_VRES_256;
|
||||||
@ -98,42 +86,17 @@ void Context::flip(void) {
|
|||||||
auto &oldBuffer = _drawBuffer(), &newBuffer = _dispBuffer();
|
auto &oldBuffer = _drawBuffer(), &newBuffer = _dispBuffer();
|
||||||
|
|
||||||
// Ensure the GPU has finished drawing the previous frame.
|
// Ensure the GPU has finished drawing the previous frame.
|
||||||
while (newBuffer.layers.length)
|
while (!isIdle())
|
||||||
__asm__ volatile("");
|
__asm__ volatile("");
|
||||||
|
|
||||||
auto mask = setInterruptMask(0);
|
*_currentListPtr = gp0_endTag(0);
|
||||||
|
_currentListPtr = newBuffer.displayList;
|
||||||
|
_currentBuffer ^= 1;
|
||||||
|
|
||||||
_flushLayer();
|
GPU_GP1 = gp1_fbOffset(newBuffer.clip.x1, newBuffer.clip.y1);
|
||||||
_currentListPtr = newBuffer.displayList;
|
|
||||||
_lastListPtr = newBuffer.displayList;
|
|
||||||
_currentBuffer ^= 1;
|
|
||||||
|
|
||||||
GPU_GP1 = gp1_fbOffset(oldBuffer.clip.x1, oldBuffer.clip.y1);
|
|
||||||
|
|
||||||
// Kick off drawing.
|
|
||||||
drawNextLayer();
|
|
||||||
if (mask)
|
|
||||||
setInterruptMask(mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Context::drawNextLayer(void) {
|
|
||||||
//auto mask = setInterruptMask(0);
|
|
||||||
auto layer = _dispBuffer().layers.popItem();
|
|
||||||
|
|
||||||
//if (mask)
|
|
||||||
//setInterruptMask(mask);
|
|
||||||
if (!layer)
|
|
||||||
return;
|
|
||||||
|
|
||||||
while (DMA_CHCR(DMA_GPU) & DMA_CHCR_ENABLE)
|
|
||||||
__asm__ volatile("");
|
|
||||||
while (!(GPU_GP1 & GP1_STAT_CMD_READY))
|
|
||||||
__asm__ volatile("");
|
|
||||||
|
|
||||||
GPU_GP1 = gp1_acknowledge();
|
|
||||||
GPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_WRITE);
|
GPU_GP1 = gp1_dmaRequestMode(GP1_DREQ_GP0_WRITE);
|
||||||
|
|
||||||
DMA_MADR(DMA_GPU) = reinterpret_cast<uint32_t>(*layer);
|
DMA_MADR(DMA_GPU) = reinterpret_cast<uint32_t>(oldBuffer.displayList);
|
||||||
DMA_CHCR(DMA_GPU) = DMA_CHCR_WRITE | DMA_CHCR_MODE_LIST | DMA_CHCR_ENABLE;
|
DMA_CHCR(DMA_GPU) = DMA_CHCR_WRITE | DMA_CHCR_MODE_LIST | DMA_CHCR_ENABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +119,6 @@ void Context::setResolution(
|
|||||||
}
|
}
|
||||||
|
|
||||||
_currentListPtr = _buffers[0].displayList;
|
_currentListPtr = _buffers[0].displayList;
|
||||||
_lastListPtr = _buffers[0].displayList;
|
|
||||||
_currentBuffer = 0;
|
_currentBuffer = 0;
|
||||||
|
|
||||||
_applyResolution(mode);
|
_applyResolution(mode);
|
||||||
@ -175,13 +137,7 @@ uint32_t *Context::newPacket(size_t length) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Context::newLayer(int x, int y, int drawWidth, int drawHeight) {
|
void Context::newLayer(int x, int y, int drawWidth, int drawHeight) {
|
||||||
auto mask = setInterruptMask(0);
|
auto &clip = _drawBuffer().clip;
|
||||||
|
|
||||||
_flushLayer();
|
|
||||||
if (mask)
|
|
||||||
setInterruptMask(mask);
|
|
||||||
|
|
||||||
auto &clip = _dispBuffer().clip;
|
|
||||||
|
|
||||||
x += clip.x1;
|
x += clip.x1;
|
||||||
y += clip.y1;
|
y += clip.y1;
|
||||||
|
13
src/gpu.hpp
13
src/gpu.hpp
@ -5,7 +5,6 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "ps1/gpucmd.h"
|
#include "ps1/gpucmd.h"
|
||||||
#include "ps1/registers.h"
|
#include "ps1/registers.h"
|
||||||
#include "util.hpp"
|
|
||||||
|
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
|
||||||
@ -41,6 +40,12 @@ static inline void init(void) {
|
|||||||
TIMER_CTRL(1) = TIMER_CTRL_EXT_CLOCK;
|
TIMER_CTRL(1) = TIMER_CTRL_EXT_CLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool isIdle(void) {
|
||||||
|
return (
|
||||||
|
!(DMA_CHCR(DMA_GPU) & DMA_CHCR_ENABLE) && (GPU_GP1 & GP1_STAT_CMD_READY)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static inline void enableDisplay(bool enable) {
|
static inline void enableDisplay(bool enable) {
|
||||||
GPU_GP1 = gp1_dispBlank(!enable);
|
GPU_GP1 = gp1_dispBlank(!enable);
|
||||||
}
|
}
|
||||||
@ -56,14 +61,12 @@ struct Buffer {
|
|||||||
public:
|
public:
|
||||||
Rect clip;
|
Rect clip;
|
||||||
uint32_t displayList[DISPLAY_LIST_SIZE];
|
uint32_t displayList[DISPLAY_LIST_SIZE];
|
||||||
|
|
||||||
volatile util::RingBuffer<uint32_t *volatile, LAYER_STACK_SIZE> layers;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Context {
|
class Context {
|
||||||
private:
|
private:
|
||||||
Buffer _buffers[2];
|
Buffer _buffers[2];
|
||||||
uint32_t *_currentListPtr, *_lastListPtr;
|
uint32_t *_currentListPtr;
|
||||||
int _currentBuffer;
|
int _currentBuffer;
|
||||||
|
|
||||||
uint32_t _lastTexpage;
|
uint32_t _lastTexpage;
|
||||||
@ -75,7 +78,6 @@ private:
|
|||||||
return _buffers[_currentBuffer ^ 1];
|
return _buffers[_currentBuffer ^ 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
void _flushLayer(void);
|
|
||||||
void _applyResolution(VideoMode mode, int shiftX = 0, int shiftY = 0) const;
|
void _applyResolution(VideoMode mode, int shiftX = 0, int shiftY = 0) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -114,7 +116,6 @@ public:
|
|||||||
void setResolution(
|
void setResolution(
|
||||||
VideoMode mode, int width, int height, bool sideBySide = false
|
VideoMode mode, int width, int height, bool sideBySide = false
|
||||||
);
|
);
|
||||||
void drawNextLayer(void);
|
|
||||||
void flip(void);
|
void flip(void);
|
||||||
|
|
||||||
uint32_t *newPacket(size_t length);
|
uint32_t *newPacket(size_t length);
|
||||||
|
@ -12,10 +12,11 @@ should start in PAL or NTSC mode by default). Requires no external dependencies.
|
|||||||
__version__ = "0.1.0"
|
__version__ = "0.1.0"
|
||||||
__author__ = "spicyjpeg"
|
__author__ = "spicyjpeg"
|
||||||
|
|
||||||
from argparse import ArgumentParser, FileType, Namespace
|
from argparse import ArgumentParser, FileType, Namespace
|
||||||
from enum import IntEnum, IntFlag
|
from dataclasses import dataclass
|
||||||
from struct import Struct
|
from enum import IntEnum, IntFlag
|
||||||
from typing import BinaryIO, ByteString, Generator
|
from struct import Struct
|
||||||
|
from typing import BinaryIO, ByteString, Generator
|
||||||
|
|
||||||
## Utilities
|
## Utilities
|
||||||
|
|
||||||
@ -67,11 +68,11 @@ class ProgHeaderFlag(IntFlag):
|
|||||||
WRITE = 1 << 1
|
WRITE = 1 << 1
|
||||||
READ = 1 << 2
|
READ = 1 << 2
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Segment:
|
class Segment:
|
||||||
def __init__(self, address: int, data: ByteString, flags: int):
|
address: int
|
||||||
self.address: int = address
|
data: bytes
|
||||||
self.data: ByteString = data
|
flags: ProgHeaderFlag
|
||||||
self.flags: ProgHeaderFlag = ProgHeaderFlag(flags)
|
|
||||||
|
|
||||||
def isReadOnly(self) -> bool:
|
def isReadOnly(self) -> bool:
|
||||||
return not \
|
return not \
|
||||||
@ -79,41 +80,33 @@ class Segment:
|
|||||||
|
|
||||||
class ELF:
|
class ELF:
|
||||||
def __init__(self, _file: BinaryIO):
|
def __init__(self, _file: BinaryIO):
|
||||||
self.type: ELFType | None = None
|
|
||||||
self.architecture: ELFArchitecture | None = None
|
|
||||||
|
|
||||||
self.abi: int | None = None
|
|
||||||
self.entryPoint: int | None = None
|
|
||||||
self.flags: int | None = None
|
|
||||||
|
|
||||||
self.segments: list[Segment] = []
|
|
||||||
|
|
||||||
self._parse(_file)
|
|
||||||
|
|
||||||
def _parse(self, _file: BinaryIO):
|
|
||||||
# Parse the file header and perform some minimal validation.
|
# Parse the file header and perform some minimal validation.
|
||||||
_file.seek(0)
|
_file.seek(0)
|
||||||
|
|
||||||
magic, wordSize, endianness, _, self.abi, _type, architecture, _, \
|
magic, wordSize, endianness, _, abi, _type, architecture, _, \
|
||||||
self.entryPoint, progHeaderOffset, secHeaderOffset, self.flags, \
|
entryPoint, progHeaderOffset, secHeaderOffset, flags, elfHeaderSize, \
|
||||||
elfHeaderLength, progHeaderLength, progHeaderCount, secHeaderLength, \
|
progHeaderSize, progHeaderCount, secHeaderSize, secHeaderCount, _ = \
|
||||||
secHeaderCount, _ = \
|
|
||||||
parseStructFromFile(_file, ELF_HEADER_STRUCT)
|
parseStructFromFile(_file, ELF_HEADER_STRUCT)
|
||||||
|
|
||||||
self.type = ELFType(_type)
|
self.type: ELFType = ELFType(_type)
|
||||||
self.architecture = ELFArchitecture(architecture)
|
self.architecture: int = architecture
|
||||||
|
self.abi: int = abi
|
||||||
|
self.entryPoint: int = entryPoint
|
||||||
|
self.flags: int = flags
|
||||||
|
|
||||||
if magic != ELF_HEADER_MAGIC:
|
if magic != ELF_HEADER_MAGIC:
|
||||||
raise RuntimeError("file is not a valid ELF")
|
raise RuntimeError("file is not a valid ELF")
|
||||||
if wordSize != 1 or endianness != ELFEndianness.LITTLE:
|
if wordSize != 1 or endianness != ELFEndianness.LITTLE:
|
||||||
raise RuntimeError("ELF file must be 32-bit little-endian")
|
raise RuntimeError("ELF file must be 32-bit little-endian")
|
||||||
if (
|
if (
|
||||||
elfHeaderLength != ELF_HEADER_STRUCT.size or
|
elfHeaderSize != ELF_HEADER_STRUCT.size or
|
||||||
progHeaderLength != PROG_HEADER_STRUCT.size
|
progHeaderSize != PROG_HEADER_STRUCT.size
|
||||||
):
|
):
|
||||||
raise RuntimeError("unsupported ELF format")
|
raise RuntimeError("unsupported ELF format")
|
||||||
|
|
||||||
# Parse the program headers and extract all loadable segments.
|
# Parse the program headers and extract all loadable segments.
|
||||||
|
self.segments: list[Segment] = []
|
||||||
|
|
||||||
_file.seek(progHeaderOffset)
|
_file.seek(progHeaderOffset)
|
||||||
|
|
||||||
for (
|
for (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user