mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-03-01 07:20:42 +01:00
Add new cart drivers, prepare for app refactor
Some checks failed
Build / Run build (push) Failing after 6m3s
Some checks failed
Build / Run build (push) Failing after 6m3s
This commit is contained in:
parent
5ab549ce41
commit
1a486b0508
@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"line-length": {
|
||||||
|
"tables": false
|
||||||
|
},
|
||||||
|
|
||||||
"emphasis-style": false,
|
"emphasis-style": false,
|
||||||
"fenced-code-language": false,
|
"fenced-code-language": false,
|
||||||
"first-line-heading": false,
|
"first-line-heading": false,
|
||||||
|
@ -72,13 +72,13 @@ set(
|
|||||||
blkdevSources
|
blkdevSources
|
||||||
src/common/blkdev/ata.cpp
|
src/common/blkdev/ata.cpp
|
||||||
src/common/blkdev/atapi.cpp
|
src/common/blkdev/atapi.cpp
|
||||||
|
src/common/blkdev/device.cpp
|
||||||
src/common/blkdev/idebase.cpp
|
src/common/blkdev/idebase.cpp
|
||||||
src/common/blkdev/memorycard.cpp
|
src/common/blkdev/memorycard.cpp
|
||||||
)
|
)
|
||||||
set(
|
set(
|
||||||
cartSources
|
cartSources
|
||||||
src/common/cart/cart.cpp
|
src/common/cart/cart.cpp
|
||||||
src/common/cart/cartio.cpp
|
|
||||||
src/common/cart/x76.cpp
|
src/common/cart/x76.cpp
|
||||||
src/common/cart/zs01.cpp
|
src/common/cart/zs01.cpp
|
||||||
)
|
)
|
||||||
@ -104,6 +104,7 @@ set(
|
|||||||
src/common/nvram/bios.cpp
|
src/common/nvram/bios.cpp
|
||||||
src/common/nvram/flash.cpp
|
src/common/nvram/flash.cpp
|
||||||
src/common/nvram/flashdetect.cpp
|
src/common/nvram/flashdetect.cpp
|
||||||
|
src/common/nvram/region.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(
|
set(
|
||||||
|
@ -97,7 +97,7 @@ DeviceError ATADevice::_transfer(
|
|||||||
|
|
||||||
// Data must be transferred one sector at a time as the drive may
|
// Data must be transferred one sector at a time as the drive may
|
||||||
// deassert DRQ between sectors.
|
// deassert DRQ between sectors.
|
||||||
for (size_t i = chunkLength; i; i--, ptr += _SECTOR_LENGTH) {
|
for (size_t i = chunkLength; i > 0; i--) {
|
||||||
auto error = _waitForDRQ();
|
auto error = _waitForDRQ();
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
@ -107,6 +107,8 @@ DeviceError ATADevice::_transfer(
|
|||||||
_writeData(reinterpret_cast<const void *>(ptr), _SECTOR_LENGTH);
|
_writeData(reinterpret_cast<const void *>(ptr), _SECTOR_LENGTH);
|
||||||
else
|
else
|
||||||
_readData(reinterpret_cast<void *>(ptr), _SECTOR_LENGTH);
|
_readData(reinterpret_cast<void *>(ptr), _SECTOR_LENGTH);
|
||||||
|
|
||||||
|
ptr += _SECTOR_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
lba += chunkLength;
|
lba += chunkLength;
|
||||||
|
69
src/common/blkdev/device.cpp
Normal file
69
src/common/blkdev/device.cpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 573in1 - Copyright (C) 2022-2025 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 <stdint.h>
|
||||||
|
#include "common/blkdev/device.hpp"
|
||||||
|
#include "common/util/templates.hpp"
|
||||||
|
|
||||||
|
namespace blkdev {
|
||||||
|
|
||||||
|
/* CD-ROM definitions */
|
||||||
|
|
||||||
|
void MSF::fromLBA(uint32_t lba) {
|
||||||
|
lba += CDROM_TOC_PREGAP;
|
||||||
|
|
||||||
|
minute = lba / 4500;
|
||||||
|
second = (lba / 75) % 60;
|
||||||
|
frame = lba % 75;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t MSF::toLBA(void) const {
|
||||||
|
return -CDROM_TOC_PREGAP
|
||||||
|
+ minute * 4500
|
||||||
|
+ second * 75
|
||||||
|
+ frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BCDMSF::fromLBA(uint32_t lba) {
|
||||||
|
lba += CDROM_TOC_PREGAP;
|
||||||
|
|
||||||
|
minute = util::encodeBCD(lba / 4500);
|
||||||
|
second = util::encodeBCD((lba / 75) % 60);
|
||||||
|
frame = util::encodeBCD(lba % 75);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t BCDMSF::toLBA(void) const {
|
||||||
|
return -CDROM_TOC_PREGAP
|
||||||
|
+ util::decodeBCD(minute) * 4500
|
||||||
|
+ util::decodeBCD(second) * 75
|
||||||
|
+ util::decodeBCD(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities */
|
||||||
|
|
||||||
|
const char *const DEVICE_ERROR_NAMES[]{
|
||||||
|
"NO_ERROR",
|
||||||
|
"UNSUPPORTED_OP",
|
||||||
|
"NO_DRIVE",
|
||||||
|
"NOT_YET_READY",
|
||||||
|
"STATUS_TIMEOUT",
|
||||||
|
"CHECKSUM_MISMATCH",
|
||||||
|
"DRIVE_ERROR",
|
||||||
|
"DISC_ERROR",
|
||||||
|
"DISC_CHANGED"
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* 573in1 - Copyright (C) 2022-2024 spicyjpeg
|
* 573in1 - Copyright (C) 2022-2025 spicyjpeg
|
||||||
*
|
*
|
||||||
* 573in1 is free software: you can redistribute it and/or modify it under the
|
* 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
|
* terms of the GNU General Public License as published by the Free Software
|
||||||
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "common/util/templates.hpp"
|
|
||||||
|
|
||||||
namespace blkdev {
|
namespace blkdev {
|
||||||
|
|
||||||
@ -30,38 +29,16 @@ class MSF {
|
|||||||
public:
|
public:
|
||||||
uint8_t minute, second, frame;
|
uint8_t minute, second, frame;
|
||||||
|
|
||||||
inline void fromLBA(uint32_t lba) {
|
void fromLBA(uint32_t lba);
|
||||||
lba += CDROM_TOC_PREGAP;
|
uint32_t toLBA(void) const;
|
||||||
|
|
||||||
minute = lba / 4500;
|
|
||||||
second = (lba / 75) % 60;
|
|
||||||
frame = lba % 75;
|
|
||||||
}
|
|
||||||
inline uint32_t toLBA(void) const {
|
|
||||||
return -CDROM_TOC_PREGAP
|
|
||||||
+ minute * 4500
|
|
||||||
+ second * 75
|
|
||||||
+ frame;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class BCDMSF {
|
class BCDMSF {
|
||||||
public:
|
public:
|
||||||
uint8_t minute, second, frame;
|
uint8_t minute, second, frame;
|
||||||
|
|
||||||
inline void fromLBA(uint32_t lba) {
|
void fromLBA(uint32_t lba);
|
||||||
lba += CDROM_TOC_PREGAP;
|
uint32_t toLBA(void) const;
|
||||||
|
|
||||||
minute = util::encodeBCD(lba / 4500);
|
|
||||||
second = util::encodeBCD((lba / 75) % 60);
|
|
||||||
frame = util::encodeBCD(lba % 75);
|
|
||||||
}
|
|
||||||
inline uint32_t toLBA(void) const {
|
|
||||||
return -CDROM_TOC_PREGAP
|
|
||||||
+ util::decodeBCD(minute) * 4500
|
|
||||||
+ util::decodeBCD(second) * 75
|
|
||||||
+ util::decodeBCD(frame);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Base block device class */
|
/* Base block device class */
|
||||||
|
@ -27,18 +27,6 @@
|
|||||||
|
|
||||||
namespace blkdev {
|
namespace blkdev {
|
||||||
|
|
||||||
const char *const DEVICE_ERROR_NAMES[]{
|
|
||||||
"NO_ERROR",
|
|
||||||
"UNSUPPORTED_OP",
|
|
||||||
"NO_DRIVE",
|
|
||||||
"NOT_YET_READY",
|
|
||||||
"STATUS_TIMEOUT",
|
|
||||||
"CHECKSUM_MISMATCH",
|
|
||||||
"DRIVE_ERROR",
|
|
||||||
"DISC_ERROR",
|
|
||||||
"DISC_CHANGED"
|
|
||||||
};
|
|
||||||
|
|
||||||
/* IDE identification block utilities */
|
/* IDE identification block utilities */
|
||||||
|
|
||||||
static void _copyString(char *output, const uint16_t *input, size_t length) {
|
static void _copyString(char *output, const uint16_t *input, size_t length) {
|
||||||
|
@ -313,7 +313,27 @@ enum OneWireCommand : uint8_t {
|
|||||||
_CMD_SEARCH_ROM = 0xf0
|
_CMD_SEARCH_ROM = 0xf0
|
||||||
};
|
};
|
||||||
|
|
||||||
bool OneWireDriver::readID(uint8_t *output) const {
|
void OneWireID::updateChecksum(void) {
|
||||||
|
crc = util::dsCRC8(&familyCode, sizeof(OneWireID) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OneWireID::validateChecksum(void) const {
|
||||||
|
if (!familyCode || (familyCode == 0xff)) {
|
||||||
|
LOG_DATA("invalid 1-wire family 0x%02x", familyCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t value = util::dsCRC8(&familyCode, sizeof(OneWireID) - 1);
|
||||||
|
|
||||||
|
if (value != crc) {
|
||||||
|
LOG_DATA("mismatch, exp=0x%02x, got=0x%02x", value, crc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OneWireDriver::readID(OneWireID *output) const {
|
||||||
util::CriticalSection sec;
|
util::CriticalSection sec;
|
||||||
|
|
||||||
if (!reset()) {
|
if (!reset()) {
|
||||||
@ -323,22 +343,12 @@ bool OneWireDriver::readID(uint8_t *output) const {
|
|||||||
|
|
||||||
writeByte(_CMD_READ_ROM);
|
writeByte(_CMD_READ_ROM);
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++)
|
auto ptr = reinterpret_cast<uint8_t *>(output);
|
||||||
output[i] = readByte();
|
|
||||||
|
|
||||||
auto code = output[0];
|
for (int i = 8; i > 0; i--)
|
||||||
auto crc = util::dsCRC8(output, 7);
|
*(ptr++) = readByte();
|
||||||
|
|
||||||
if (!code || (code == 0xff)) {
|
return output->validateChecksum();
|
||||||
LOG_IO("invalid 1-wire code 0x%02x", code);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (crc != output[7]) {
|
|
||||||
LOG_IO("CRC mismatch, exp=0x%02x, got=0x%02x", crc, output[7]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "common/util/string.hpp"
|
||||||
#include "ps1/system.h"
|
#include "ps1/system.h"
|
||||||
|
|
||||||
namespace bus {
|
namespace bus {
|
||||||
@ -138,6 +139,22 @@ public:
|
|||||||
|
|
||||||
/* Bitbanged 1-wire driver */
|
/* Bitbanged 1-wire driver */
|
||||||
|
|
||||||
|
class OneWireID {
|
||||||
|
public:
|
||||||
|
uint8_t familyCode, id[6];
|
||||||
|
uint8_t crc;
|
||||||
|
|
||||||
|
inline size_t toString(char *output) const {
|
||||||
|
return util::hexToString(output, &familyCode, sizeof(OneWireID), '-');
|
||||||
|
}
|
||||||
|
inline size_t toSerialNumber(char *output) const {
|
||||||
|
return util::serialNumberToString(output, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateChecksum(void);
|
||||||
|
bool validateChecksum(void) const;
|
||||||
|
};
|
||||||
|
|
||||||
class OneWireDriver {
|
class OneWireDriver {
|
||||||
private:
|
private:
|
||||||
inline void _set(bool value, int delay) const {
|
inline void _set(bool value, int delay) const {
|
||||||
@ -154,7 +171,7 @@ public:
|
|||||||
uint8_t readByte(void) const;
|
uint8_t readByte(void) const;
|
||||||
void writeByte(uint8_t value) const;
|
void writeByte(uint8_t value) const;
|
||||||
|
|
||||||
bool readID(uint8_t *output) const;
|
bool readID(OneWireID *output) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
88
src/common/cart/cart.cpp
Normal file
88
src/common/cart/cart.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* 573in1 - Copyright (C) 2022-2025 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 <stdint.h>
|
||||||
|
#include "common/cart/cart.hpp"
|
||||||
|
#include "common/cart/x76.hpp"
|
||||||
|
#include "common/cart/zs01.hpp"
|
||||||
|
#include "common/sys573/base.hpp"
|
||||||
|
#include "common/util/log.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
/* Base security cartridge driver class */
|
||||||
|
|
||||||
|
CartError Cart::readID(bus::OneWireID *output) {
|
||||||
|
return sys573::cartDS2401.readID(output)
|
||||||
|
? NO_ERROR
|
||||||
|
: NO_DEVICE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Security cartridge detection and constructor */
|
||||||
|
|
||||||
|
enum ChipIdentifier : uint32_t {
|
||||||
|
_ID_X76F041 = 0x55aa5519,
|
||||||
|
_ID_X76F100 = 0x55aa0019,
|
||||||
|
_ID_ZS01 = 0x5a530001
|
||||||
|
};
|
||||||
|
|
||||||
|
Cart *newCartDriver(const bus::I2CDriver &i2c) {
|
||||||
|
// The X76F041/X76F100 and ZS01 use different reset sequences and output
|
||||||
|
// their IDs in different bit orders.
|
||||||
|
auto zs01ID = sys573::cartI2C.resetZS01();
|
||||||
|
|
||||||
|
if (zs01ID == _ID_ZS01)
|
||||||
|
return new ZS01Cart(i2c);
|
||||||
|
|
||||||
|
LOG_CART("unknown ZS01 ID: 0x%08x", zs01ID);
|
||||||
|
|
||||||
|
auto x76ID = sys573::cartI2C.resetX76();
|
||||||
|
|
||||||
|
switch (x76ID) {
|
||||||
|
case _ID_X76F041:
|
||||||
|
return new X76F041Cart(i2c);
|
||||||
|
|
||||||
|
case _ID_X76F100:
|
||||||
|
return new X76F100Cart(i2c);
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOG_CART("unknown X76 ID: 0x%08x", x76ID);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cart *newCartDriver(void) {
|
||||||
|
if (!sys573::getCartInsertionStatus()) {
|
||||||
|
LOG_CART("DSR not asserted");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newCartDriver(sys573::cartI2C);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities */
|
||||||
|
|
||||||
|
const char *const CART_ERROR_NAMES[]{
|
||||||
|
"NO_ERROR",
|
||||||
|
"UNSUPPORTED_OP",
|
||||||
|
"NO_DEVICE",
|
||||||
|
"CHIP_TIMEOUT",
|
||||||
|
"CHIP_ERROR",
|
||||||
|
"VERIFY_MISMATCH",
|
||||||
|
"CHECKSUM_MISMATCH"
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
110
src/common/cart/cart.hpp
Normal file
110
src/common/cart/cart.hpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 573in1 - Copyright (C) 2022-2025 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 <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "common/bus.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
static constexpr size_t SECTOR_LENGTH = 8;
|
||||||
|
static constexpr size_t KEY_LENGTH = 8;
|
||||||
|
static constexpr size_t CONFIG_LENGTH = 8;
|
||||||
|
|
||||||
|
/* Base security cartridge driver class */
|
||||||
|
|
||||||
|
enum ChipType : uint8_t {
|
||||||
|
NONE = 0,
|
||||||
|
X76F041 = 1,
|
||||||
|
X76F100 = 2,
|
||||||
|
ZS01 = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum CartError {
|
||||||
|
NO_ERROR = 0,
|
||||||
|
UNSUPPORTED_OP = 1,
|
||||||
|
NO_DEVICE = 2,
|
||||||
|
CHIP_TIMEOUT = 3,
|
||||||
|
CHIP_ERROR = 4,
|
||||||
|
VERIFY_MISMATCH = 5,
|
||||||
|
CHECKSUM_MISMATCH = 6,
|
||||||
|
INVALID_ID = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
class Cart {
|
||||||
|
protected:
|
||||||
|
const bus::I2CDriver &_i2c;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ChipType type;
|
||||||
|
uint16_t capacity;
|
||||||
|
|
||||||
|
inline Cart(
|
||||||
|
const bus::I2CDriver &i2c,
|
||||||
|
ChipType _type,
|
||||||
|
uint16_t _capacity
|
||||||
|
) :
|
||||||
|
_i2c(i2c),
|
||||||
|
type(_type),
|
||||||
|
capacity(_capacity) {}
|
||||||
|
|
||||||
|
virtual CartError read(
|
||||||
|
void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key = nullptr
|
||||||
|
) { return UNSUPPORTED_OP; }
|
||||||
|
virtual CartError write(
|
||||||
|
const void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key
|
||||||
|
) { return UNSUPPORTED_OP; }
|
||||||
|
virtual CartError erase(const uint8_t *key) { return UNSUPPORTED_OP; }
|
||||||
|
|
||||||
|
virtual CartError readConfig(
|
||||||
|
uint8_t *config,
|
||||||
|
const uint8_t *key
|
||||||
|
) { return UNSUPPORTED_OP; }
|
||||||
|
virtual CartError writeConfig(
|
||||||
|
const uint8_t *config,
|
||||||
|
const uint8_t *key
|
||||||
|
) { return UNSUPPORTED_OP; }
|
||||||
|
virtual CartError setKey(
|
||||||
|
const uint8_t *newKey,
|
||||||
|
const uint8_t *oldKey
|
||||||
|
) { return UNSUPPORTED_OP; }
|
||||||
|
|
||||||
|
virtual CartError readID(bus::OneWireID *output);
|
||||||
|
virtual CartError readInternalID(bus::OneWireID *output) {
|
||||||
|
return UNSUPPORTED_OP;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Cart *newCartDriver(const bus::I2CDriver &i2c);
|
||||||
|
Cart *newCartDriver(void);
|
||||||
|
|
||||||
|
/* Utilities */
|
||||||
|
|
||||||
|
extern const char *const CART_ERROR_NAMES[];
|
||||||
|
|
||||||
|
static inline const char *getErrorString(CartError error) {
|
||||||
|
return CART_ERROR_NAMES[error];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
431
src/common/cart/x76.cpp
Normal file
431
src/common/cart/x76.cpp
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
/*
|
||||||
|
* 573in1 - Copyright (C) 2022-2025 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 "common/cart/x76.hpp"
|
||||||
|
#include "common/util/log.hpp"
|
||||||
|
#include "common/util/templates.hpp"
|
||||||
|
#include "common/bus.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
static constexpr int _X76_MAX_ACK_POLLS = 5;
|
||||||
|
static constexpr int _X76_WRITE_DELAY = 12000;
|
||||||
|
static constexpr int _X76_PACKET_DELAY = 12000;
|
||||||
|
|
||||||
|
/* Utilities */
|
||||||
|
|
||||||
|
CartError _issueX76Command(
|
||||||
|
const bus::I2CDriver &i2c,
|
||||||
|
const uint8_t *data,
|
||||||
|
size_t length,
|
||||||
|
const uint8_t *key,
|
||||||
|
uint8_t pollByte
|
||||||
|
) {
|
||||||
|
delayMicroseconds(_X76_PACKET_DELAY);
|
||||||
|
i2c.startWithCS();
|
||||||
|
|
||||||
|
if (!i2c.writeBytes(data, length)) {
|
||||||
|
i2c.stopWithCS();
|
||||||
|
|
||||||
|
LOG_CART("NACK while sending command");
|
||||||
|
return CHIP_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
if (!i2c.writeBytes(key, KEY_LENGTH)) {
|
||||||
|
i2c.stopWithCS();
|
||||||
|
|
||||||
|
LOG_CART("NACK while sending key");
|
||||||
|
return CHIP_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = _X76_MAX_ACK_POLLS; i; i--) {
|
||||||
|
delayMicroseconds(_X76_WRITE_DELAY);
|
||||||
|
i2c.start();
|
||||||
|
i2c.writeByte(pollByte);
|
||||||
|
|
||||||
|
if (i2c.getACK())
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c.stopWithCS();
|
||||||
|
|
||||||
|
LOG_CART("ACK poll timeout (wrong key?)");
|
||||||
|
return CHIP_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* X76F041 security cartridge driver */
|
||||||
|
|
||||||
|
enum X76F041Command : uint8_t {
|
||||||
|
_X76F041_WRITE = 0x40,
|
||||||
|
_X76F041_READ = 0x60,
|
||||||
|
_X76F041_CONFIG = 0x80,
|
||||||
|
_X76F041_ACK_POLL = 0xc0
|
||||||
|
};
|
||||||
|
|
||||||
|
enum X76F041ConfigCommand : uint8_t {
|
||||||
|
_X76F041_CFG_SET_WRITE_KEY = 0x00,
|
||||||
|
_X76F041_CFG_SET_READ_KEY = 0x10,
|
||||||
|
_X76F041_CFG_SET_CONFIG_KEY = 0x20,
|
||||||
|
_X76F041_CFG_CLEAR_WRITE_KEY = 0x30,
|
||||||
|
_X76F041_CFG_CLEAR_READ_KEY = 0x40,
|
||||||
|
_X76F041_CFG_WRITE_CONFIG = 0x50,
|
||||||
|
_X76F041_CFG_READ_CONFIG = 0x60,
|
||||||
|
_X76F041_CFG_MASS_PROGRAM = 0x70,
|
||||||
|
_X76F041_CFG_MASS_ERASE = 0x80
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr size_t _X76F041_SECTORS_PER_BLOCK = 16;
|
||||||
|
static constexpr size_t _X76F041_CONFIG_LENGTH = 5;
|
||||||
|
|
||||||
|
CartError X76F041Cart::read(
|
||||||
|
void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key
|
||||||
|
) {
|
||||||
|
// Even though the X76F041 supports unprivileged reads, attempting to
|
||||||
|
// perform one on a privileged block will trigger the failed attempt counter
|
||||||
|
// (as if the wrong key was provided). Since different games protect
|
||||||
|
// different blocks and there is no other way to tell which blocks are
|
||||||
|
// privileged, this renders unprivileged reads virtually useless.
|
||||||
|
if (!key)
|
||||||
|
return UNSUPPORTED_OP;
|
||||||
|
|
||||||
|
auto ptr = reinterpret_cast<uint8_t *>(data);
|
||||||
|
|
||||||
|
while (count > 0) {
|
||||||
|
// A single read operation may span multiple sectors but can't cross
|
||||||
|
// 128-byte block boundaries.
|
||||||
|
auto blockOffset = lba % _X76F041_SECTORS_PER_BLOCK;
|
||||||
|
auto readCount =
|
||||||
|
util::min(count, _X76F041_SECTORS_PER_BLOCK - blockOffset);
|
||||||
|
auto readLength = SECTOR_LENGTH * readCount;
|
||||||
|
|
||||||
|
const uint8_t packet[2]{
|
||||||
|
uint8_t((lba >> 8) | _X76F041_READ),
|
||||||
|
uint8_t((lba >> 0) & 0xff)
|
||||||
|
};
|
||||||
|
|
||||||
|
auto error = _issueX76Command(
|
||||||
|
_i2c,
|
||||||
|
packet,
|
||||||
|
sizeof(packet),
|
||||||
|
key,
|
||||||
|
_X76F041_ACK_POLL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
_i2c.readByte(); // Ignore "secure read setup" byte
|
||||||
|
_i2c.start();
|
||||||
|
_i2c.writeByte(packet[1]);
|
||||||
|
|
||||||
|
if (!_i2c.getACK()) {
|
||||||
|
_i2c.stopWithCS();
|
||||||
|
|
||||||
|
LOG_CART("NACK after resending address");
|
||||||
|
return CHIP_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
_i2c.readBytes(ptr, readLength);
|
||||||
|
_i2c.stopWithCS();
|
||||||
|
|
||||||
|
ptr += readLength;
|
||||||
|
lba += readCount;
|
||||||
|
count -= readCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError X76F041Cart::write(
|
||||||
|
const void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key
|
||||||
|
) {
|
||||||
|
auto ptr = reinterpret_cast<const uint8_t *>(data);
|
||||||
|
|
||||||
|
while (count > 0) {
|
||||||
|
const uint8_t packet[2]{
|
||||||
|
uint8_t((lba >> 8) | _X76F041_WRITE),
|
||||||
|
uint8_t((lba >> 0) & 0xff)
|
||||||
|
};
|
||||||
|
|
||||||
|
auto error = _issueX76Command(
|
||||||
|
_i2c,
|
||||||
|
packet,
|
||||||
|
sizeof(packet),
|
||||||
|
key,
|
||||||
|
_X76F041_ACK_POLL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
auto ok = _i2c.writeBytes(ptr, SECTOR_LENGTH);
|
||||||
|
_i2c.stopWithCS(_X76_WRITE_DELAY);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
LOG_CART("NACK while sending data");
|
||||||
|
return CHIP_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += SECTOR_LENGTH;
|
||||||
|
lba++;
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError X76F041Cart::erase(const uint8_t *key) {
|
||||||
|
const uint8_t packet[2]{
|
||||||
|
_X76F041_CONFIG,
|
||||||
|
_X76F041_CFG_MASS_PROGRAM
|
||||||
|
};
|
||||||
|
|
||||||
|
auto error = _issueX76Command(
|
||||||
|
_i2c,
|
||||||
|
packet,
|
||||||
|
sizeof(packet),
|
||||||
|
key,
|
||||||
|
_X76F041_ACK_POLL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
_i2c.stopWithCS(_X76_WRITE_DELAY);
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError X76F041Cart::readConfig(uint8_t *config, const uint8_t *key) {
|
||||||
|
const uint8_t packet[2]{
|
||||||
|
_X76F041_CONFIG,
|
||||||
|
_X76F041_CFG_READ_CONFIG
|
||||||
|
};
|
||||||
|
|
||||||
|
auto error = _issueX76Command(
|
||||||
|
_i2c,
|
||||||
|
packet,
|
||||||
|
sizeof(packet),
|
||||||
|
key,
|
||||||
|
_X76F041_ACK_POLL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
__builtin_memset(config, 0, CONFIG_LENGTH);
|
||||||
|
_i2c.readBytes(config, _X76F041_CONFIG_LENGTH);
|
||||||
|
_i2c.stopWithCS();
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError X76F041Cart::writeConfig(const uint8_t *config, const uint8_t *key) {
|
||||||
|
const uint8_t packet[2]{
|
||||||
|
_X76F041_CONFIG,
|
||||||
|
_X76F041_CFG_WRITE_CONFIG
|
||||||
|
};
|
||||||
|
|
||||||
|
auto error = _issueX76Command(
|
||||||
|
_i2c,
|
||||||
|
packet,
|
||||||
|
sizeof(packet),
|
||||||
|
key,
|
||||||
|
_X76F041_ACK_POLL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
auto ok = _i2c.writeBytes(config, _X76F041_CONFIG_LENGTH);
|
||||||
|
_i2c.stopWithCS(_X76_WRITE_DELAY);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
LOG_CART("NACK while sending new config");
|
||||||
|
return CHIP_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError X76F041Cart::setKey(const uint8_t *newKey, const uint8_t *oldKey) {
|
||||||
|
// All known games use the configuration key for all commands and leave the
|
||||||
|
// read and write keys unused.
|
||||||
|
const uint8_t packet[2]{
|
||||||
|
_X76F041_CONFIG,
|
||||||
|
_X76F041_CFG_SET_CONFIG_KEY
|
||||||
|
};
|
||||||
|
|
||||||
|
auto error = _issueX76Command(
|
||||||
|
_i2c,
|
||||||
|
packet,
|
||||||
|
sizeof(packet),
|
||||||
|
oldKey,
|
||||||
|
_X76F041_ACK_POLL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
// The chip requires the new key to be sent twice as a way of ensuring it
|
||||||
|
// gets received correctly.
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
if (!_i2c.writeBytes(newKey, KEY_LENGTH)) {
|
||||||
|
_i2c.stopWithCS(_X76_WRITE_DELAY);
|
||||||
|
|
||||||
|
LOG_CART("NACK while sending new key, i=%d", i);
|
||||||
|
return CHIP_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_i2c.stopWithCS(_X76_WRITE_DELAY);
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* X76F100 security cartridge driver */
|
||||||
|
|
||||||
|
enum X76F100Command : uint8_t {
|
||||||
|
_X76F100_ACK_POLL = 0x55,
|
||||||
|
_X76F100_WRITE = 0x80,
|
||||||
|
_X76F100_READ = 0x81,
|
||||||
|
_X76F100_SET_WRITE_KEY = 0xfc,
|
||||||
|
_X76F100_SET_READ_KEY = 0xfe
|
||||||
|
};
|
||||||
|
|
||||||
|
CartError X76F100Cart::read(
|
||||||
|
void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key
|
||||||
|
) {
|
||||||
|
// The X76F100 does not support unprivileged reads.
|
||||||
|
if (!key)
|
||||||
|
return UNSUPPORTED_OP;
|
||||||
|
|
||||||
|
const uint8_t cmd = _X76F100_READ | (lba << 1);
|
||||||
|
|
||||||
|
auto error = _issueX76Command(
|
||||||
|
_i2c,
|
||||||
|
&cmd,
|
||||||
|
sizeof(cmd),
|
||||||
|
key,
|
||||||
|
_X76F100_ACK_POLL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
_i2c.start();
|
||||||
|
#endif
|
||||||
|
_i2c.readBytes(reinterpret_cast<uint8_t *>(data), SECTOR_LENGTH * count);
|
||||||
|
_i2c.stopWithCS();
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError X76F100Cart::write(
|
||||||
|
const void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key
|
||||||
|
) {
|
||||||
|
auto ptr = reinterpret_cast<const uint8_t *>(data);
|
||||||
|
|
||||||
|
while (count > 0) {
|
||||||
|
const uint8_t cmd = _X76F100_WRITE | (lba << 1);
|
||||||
|
|
||||||
|
auto error = _issueX76Command(
|
||||||
|
_i2c,
|
||||||
|
&cmd,
|
||||||
|
sizeof(cmd),
|
||||||
|
key,
|
||||||
|
_X76F100_ACK_POLL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
auto ok = _i2c.writeBytes(ptr, SECTOR_LENGTH);
|
||||||
|
_i2c.stopWithCS(_X76_WRITE_DELAY);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
LOG_CART("NACK while sending data");
|
||||||
|
return CHIP_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += SECTOR_LENGTH;
|
||||||
|
lba++;
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError X76F100Cart::erase(const uint8_t *key) {
|
||||||
|
// The chip does not have an erase command, so erasing must be performed
|
||||||
|
// manually one block at a time. The keys must also be explicitly cleared.
|
||||||
|
const uint8_t dummy[SECTOR_LENGTH]{ 0 };
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < capacity; i++) {
|
||||||
|
auto error = write(dummy, i, sizeof(dummy), key);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return setKey(dummy, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError X76F100Cart::setKey(const uint8_t *newKey, const uint8_t *oldKey) {
|
||||||
|
// All known games use the same key for both reading and writing.
|
||||||
|
const uint8_t packets[2]{
|
||||||
|
_X76F100_SET_WRITE_KEY,
|
||||||
|
_X76F100_SET_READ_KEY
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto &cmd : packets) {
|
||||||
|
auto error = _issueX76Command(
|
||||||
|
_i2c,
|
||||||
|
&cmd,
|
||||||
|
sizeof(cmd),
|
||||||
|
oldKey,
|
||||||
|
_X76F100_ACK_POLL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
auto ok = _i2c.writeBytes(newKey, KEY_LENGTH);
|
||||||
|
_i2c.stopWithCS(_X76_WRITE_DELAY);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
LOG_CART("NACK while sending new key, cmd=0x%02x", cmd);
|
||||||
|
return CHIP_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
74
src/common/cart/x76.hpp
Normal file
74
src/common/cart/x76.hpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 573in1 - Copyright (C) 2022-2025 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 <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "common/cart/cart.hpp"
|
||||||
|
#include "common/bus.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
/* X76F041 and X76F100 security cartridge drivers */
|
||||||
|
|
||||||
|
class X76F041Cart : public Cart {
|
||||||
|
public:
|
||||||
|
inline X76F041Cart(const bus::I2CDriver &i2c)
|
||||||
|
: Cart(i2c, X76F041, 512) {}
|
||||||
|
|
||||||
|
CartError read(
|
||||||
|
void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key = nullptr
|
||||||
|
);
|
||||||
|
CartError write(
|
||||||
|
const void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key
|
||||||
|
);
|
||||||
|
CartError erase(const uint8_t *key);
|
||||||
|
|
||||||
|
CartError readConfig(uint8_t *config, const uint8_t *key);
|
||||||
|
CartError writeConfig(const uint8_t *config, const uint8_t *key);
|
||||||
|
CartError setKey(const uint8_t *newKey, const uint8_t *oldKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
class X76F100Cart : public Cart {
|
||||||
|
public:
|
||||||
|
inline X76F100Cart(const bus::I2CDriver &i2c)
|
||||||
|
: Cart(i2c, X76F100, 112) {}
|
||||||
|
|
||||||
|
CartError read(
|
||||||
|
void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key = nullptr
|
||||||
|
);
|
||||||
|
CartError write(
|
||||||
|
const void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key
|
||||||
|
);
|
||||||
|
CartError erase(const uint8_t *key);
|
||||||
|
|
||||||
|
CartError setKey(const uint8_t *newKey, const uint8_t *oldKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
369
src/common/cart/zs01.cpp
Normal file
369
src/common/cart/zs01.cpp
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
/*
|
||||||
|
* 573in1 - Copyright (C) 2022-2025 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 "common/cart/zs01.hpp"
|
||||||
|
#include "common/util/hash.hpp"
|
||||||
|
#include "common/util/log.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
/* ZS01 packet scrambling */
|
||||||
|
|
||||||
|
// This key is fixed across all ZS01 cartridges and used to scramble command
|
||||||
|
// packets.
|
||||||
|
static const ZS01Key _COMMAND_KEY{
|
||||||
|
.add = { 237, 8, 16, 11, 6, 4, 8, 30 },
|
||||||
|
.shift = { 0, 3, 2, 2, 6, 2, 2, 1 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// This key is provided by the 573 to the ZS01 and used to scramble response
|
||||||
|
// packets. Konami's driver generates random response keys for each transaction,
|
||||||
|
// however the ZS01 does not impose any requirements on it.
|
||||||
|
static const ZS01Key _RESPONSE_KEY{
|
||||||
|
.add = { 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
|
.shift = { 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
void ZS01Key::unpackFrom(const uint8_t *key) {
|
||||||
|
add[0] = key[0];
|
||||||
|
shift[0] = 0;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < KEY_LENGTH; i++) {
|
||||||
|
add[i] = key[i] & 0x1f;
|
||||||
|
shift[i] = key[i] >> 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZS01Key::packInto(uint8_t *key) const {
|
||||||
|
key[0] = add[0];
|
||||||
|
|
||||||
|
for (size_t i = 1; i < KEY_LENGTH; i++)
|
||||||
|
key[i] = (add[i] & 0x1f) | (shift[i] << 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZS01Key::scramblePacket(
|
||||||
|
uint8_t *data,
|
||||||
|
size_t length,
|
||||||
|
uint8_t state
|
||||||
|
) const {
|
||||||
|
for (data += length; length; length--) {
|
||||||
|
uint8_t value = *(--data) ^ state;
|
||||||
|
value = (value + add[0]) & 0xff;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < KEY_LENGTH; i++) {
|
||||||
|
int newValue;
|
||||||
|
newValue = static_cast<int>(value) << shift[i];
|
||||||
|
newValue |= static_cast<int>(value) >> (8 - shift[i]);
|
||||||
|
newValue &= 0xff;
|
||||||
|
|
||||||
|
value = (newValue + add[i]) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = value;
|
||||||
|
*data = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZS01Key::unscramblePacket(
|
||||||
|
uint8_t *data,
|
||||||
|
size_t length,
|
||||||
|
uint8_t state
|
||||||
|
) const {
|
||||||
|
for (data += length; length; length--) {
|
||||||
|
uint8_t value = *(--data), prevState = state;
|
||||||
|
state = value;
|
||||||
|
|
||||||
|
for (int i = KEY_LENGTH - 1; i; i--) {
|
||||||
|
int newValue = (value - add[i]) & 0xff;
|
||||||
|
value = static_cast<int>(newValue) >> shift[i];
|
||||||
|
value |= static_cast<int>(newValue) << (8 - shift[i]);
|
||||||
|
value &= 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = (value - add[0]) & 0xff;
|
||||||
|
*data = value ^ prevState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZS01Key::scramblePayload(
|
||||||
|
uint8_t *data,
|
||||||
|
size_t length,
|
||||||
|
uint8_t state
|
||||||
|
) const {
|
||||||
|
for (; length; length--) {
|
||||||
|
uint8_t value = *data ^ state;
|
||||||
|
value = (value + add[0]) & 0xff;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < KEY_LENGTH; i++) {
|
||||||
|
int newValue;
|
||||||
|
newValue = static_cast<int>(value) << shift[i];
|
||||||
|
newValue |= static_cast<int>(value) >> (8 - shift[i]);
|
||||||
|
newValue &= 0xff;
|
||||||
|
|
||||||
|
value = (newValue + add[i]) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = value;
|
||||||
|
*(data++) = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ZS01 packet structure */
|
||||||
|
|
||||||
|
void ZS01Packet::updateChecksum(void) {
|
||||||
|
uint16_t value = util::zsCRC16(&command, sizeof(ZS01Packet) - sizeof(crc));
|
||||||
|
crc = __builtin_bswap16(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ZS01Packet::validateChecksum(void) const {
|
||||||
|
uint16_t _crc = __builtin_bswap16(crc);
|
||||||
|
uint16_t value = util::zsCRC16(&command, sizeof(ZS01Packet) - sizeof(crc));
|
||||||
|
|
||||||
|
if (value != _crc) {
|
||||||
|
LOG_CART("mismatch, exp=0x%04x, got=0x%04x", value, _crc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZS01Packet::setRead(uint16_t _address) {
|
||||||
|
command = ZS01_REQ_READ;
|
||||||
|
address = uint8_t(_address & 0xff);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if (_address & (1 << 8))
|
||||||
|
command |= ZS01_REQ_ADDRESS_MSB;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_RESPONSE_KEY.packInto(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZS01Packet::setWrite(uint16_t _address, const uint8_t *_data) {
|
||||||
|
command = ZS01_REQ_WRITE;
|
||||||
|
address = uint8_t(_address & 0xff);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if (_address & (1 << 8))
|
||||||
|
command |= ZS01_REQ_ADDRESS_MSB;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
__builtin_memcpy(data, _data, sizeof(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ZS01Packet::encodeRequest(const uint8_t *key, uint8_t state) {
|
||||||
|
if (key)
|
||||||
|
command |= ZS01_REQ_PRIVILEGED;
|
||||||
|
else
|
||||||
|
command &= ~ZS01_REQ_PRIVILEGED;
|
||||||
|
|
||||||
|
updateChecksum();
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
ZS01Key payloadKey;
|
||||||
|
|
||||||
|
payloadKey.unpackFrom(key);
|
||||||
|
payloadKey.scramblePayload(data, sizeof(data), state);
|
||||||
|
}
|
||||||
|
|
||||||
|
_COMMAND_KEY.scramblePacket(&command, sizeof(ZS01Packet));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ZS01Packet::decodeResponse(void) {
|
||||||
|
// NOTE: the ZS01 may scramble the response to a read request with either
|
||||||
|
// the key provided in the request payload *or* the last response key
|
||||||
|
// provided beforehand (Konami's driver attempts unscrambling the response
|
||||||
|
// using either key before giving up). Responses to write requests are
|
||||||
|
// always scrambled using the last read request's response key, as write
|
||||||
|
// packets contain data to be written in place of the key. Confused yet?
|
||||||
|
_RESPONSE_KEY.unscramblePacket(&command, sizeof(ZS01Packet));
|
||||||
|
|
||||||
|
return validateChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ZS01 security cartridge driver */
|
||||||
|
|
||||||
|
// _ZS01_SEND_DELAY and _ZS01_PACKET_DELAY are set to rather conservative values
|
||||||
|
// here. While it is likely possible to use lower delays, setting either to
|
||||||
|
// ~30000 is known to result in key corruption (rendering the cartridge
|
||||||
|
// inaccessible and thus soft-bricking it).
|
||||||
|
static constexpr int _ZS01_SEND_DELAY = 100000;
|
||||||
|
static constexpr int _ZS01_PACKET_DELAY = 300000;
|
||||||
|
|
||||||
|
CartError ZS01Cart::_transact(
|
||||||
|
const ZS01Packet &request,
|
||||||
|
ZS01Packet &response
|
||||||
|
) {
|
||||||
|
delayMicroseconds(_ZS01_PACKET_DELAY);
|
||||||
|
_i2c.start();
|
||||||
|
|
||||||
|
if (!_i2c.writeBytes(
|
||||||
|
&request.command,
|
||||||
|
sizeof(ZS01Packet),
|
||||||
|
_ZS01_SEND_DELAY
|
||||||
|
)) {
|
||||||
|
_i2c.stop();
|
||||||
|
|
||||||
|
LOG_CART("NACK while sending request");
|
||||||
|
return CHIP_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
_i2c.readBytes(&response.command, sizeof(ZS01Packet));
|
||||||
|
_i2c.stop();
|
||||||
|
|
||||||
|
if (!response.decodeResponse())
|
||||||
|
return CHECKSUM_MISMATCH;
|
||||||
|
|
||||||
|
_scramblerState = response.address;
|
||||||
|
|
||||||
|
if (response.command != ZS01_RESP_NO_ERROR) {
|
||||||
|
LOG_CART("ZS01 error, code=0x%02x", response.command);
|
||||||
|
return CHIP_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError ZS01Cart::read(
|
||||||
|
void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key
|
||||||
|
) {
|
||||||
|
auto ptr = reinterpret_cast<uint8_t *>(data);
|
||||||
|
|
||||||
|
while (count > 0) {
|
||||||
|
ZS01Packet packet;
|
||||||
|
|
||||||
|
packet.setRead(lba);
|
||||||
|
packet.encodeRequest(key, _scramblerState);
|
||||||
|
|
||||||
|
auto error = _transact(packet, packet);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
__builtin_memcpy(ptr, packet.data, SECTOR_LENGTH);
|
||||||
|
ptr += SECTOR_LENGTH;
|
||||||
|
lba++;
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError ZS01Cart::write(
|
||||||
|
const void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key
|
||||||
|
) {
|
||||||
|
auto ptr = reinterpret_cast<const uint8_t *>(data);
|
||||||
|
|
||||||
|
while (count > 0) {
|
||||||
|
ZS01Packet packet;
|
||||||
|
|
||||||
|
packet.setWrite(lba, ptr);
|
||||||
|
packet.encodeRequest(key, _scramblerState);
|
||||||
|
|
||||||
|
auto error = _transact(packet, packet);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
ptr += SECTOR_LENGTH;
|
||||||
|
lba++;
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError ZS01Cart::erase(const uint8_t *key) {
|
||||||
|
const uint8_t dummy[SECTOR_LENGTH]{ 0 };
|
||||||
|
ZS01Packet packet;
|
||||||
|
|
||||||
|
packet.setWrite(ZS01_ADDR_ERASE, dummy);
|
||||||
|
packet.encodeRequest(key, _scramblerState);
|
||||||
|
|
||||||
|
return _transact(packet, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError ZS01Cart::readConfig(uint8_t *config, const uint8_t *key) {
|
||||||
|
ZS01Packet packet;
|
||||||
|
|
||||||
|
packet.setRead(ZS01_ADDR_CONFIG);
|
||||||
|
packet.encodeRequest(key, _scramblerState);
|
||||||
|
|
||||||
|
auto error = _transact(packet, packet);
|
||||||
|
|
||||||
|
if (!error)
|
||||||
|
__builtin_memcpy(config, packet.data, CONFIG_LENGTH);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError ZS01Cart::writeConfig(const uint8_t *config, const uint8_t *key) {
|
||||||
|
ZS01Packet packet;
|
||||||
|
|
||||||
|
packet.setWrite(ZS01_ADDR_CONFIG, config);
|
||||||
|
packet.encodeRequest(key, _scramblerState);
|
||||||
|
|
||||||
|
return _transact(packet, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError ZS01Cart::setKey(const uint8_t *newKey, const uint8_t *oldKey) {
|
||||||
|
ZS01Packet packet;
|
||||||
|
|
||||||
|
packet.setWrite(ZS01_ADDR_SET_KEY, newKey);
|
||||||
|
packet.encodeRequest(oldKey, _scramblerState);
|
||||||
|
|
||||||
|
return _transact(packet, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError ZS01Cart::readID(bus::OneWireID *output) {
|
||||||
|
ZS01Packet packet;
|
||||||
|
|
||||||
|
packet.setRead(ZS01_ADDR_DS2401_ID);
|
||||||
|
packet.encodeRequest();
|
||||||
|
|
||||||
|
auto error = _transact(packet, packet);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
__builtin_memcpy(output, packet.data, sizeof(bus::OneWireID));
|
||||||
|
return output->validateChecksum() ? NO_ERROR : INVALID_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
CartError ZS01Cart::readInternalID(bus::OneWireID *output) {
|
||||||
|
ZS01Packet packet;
|
||||||
|
|
||||||
|
packet.setRead(ZS01_ADDR_ZS01_ID);
|
||||||
|
packet.encodeRequest();
|
||||||
|
|
||||||
|
auto error = _transact(packet, packet);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
__builtin_memcpy(output, packet.data, sizeof(bus::OneWireID));
|
||||||
|
return output->validateChecksum() ? NO_ERROR : INVALID_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
138
src/common/cart/zs01.hpp
Normal file
138
src/common/cart/zs01.hpp
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* 573in1 - Copyright (C) 2022-2025 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 <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "common/cart/cart.hpp"
|
||||||
|
#include "common/bus.hpp"
|
||||||
|
|
||||||
|
namespace cart {
|
||||||
|
|
||||||
|
/* ZS01 definitions */
|
||||||
|
|
||||||
|
enum ZS01Address : uint16_t {
|
||||||
|
ZS01_ADDR_UNPRIVILEGED = 0x0000,
|
||||||
|
ZS01_ADDR_UNPRIVILEGED_END = 0x0004,
|
||||||
|
ZS01_ADDR_PRIVILEGED = 0x0004,
|
||||||
|
ZS01_ADDR_PRIVILEGED_END = 0x000e,
|
||||||
|
ZS01_ADDR_ZS01_ID = 0x00fc, // Unprivileged, read-only
|
||||||
|
ZS01_ADDR_DS2401_ID = 0x00fd, // Unprivileged, read-only
|
||||||
|
ZS01_ADDR_ERASE = 0x00fd, // Privileged, write-only
|
||||||
|
ZS01_ADDR_CONFIG = 0x00fe, // Privileged
|
||||||
|
ZS01_ADDR_SET_KEY = 0x00ff // Privileged, write-only
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ZS01RequestFlag : uint8_t {
|
||||||
|
ZS01_REQ_WRITE = 0 << 0,
|
||||||
|
ZS01_REQ_READ = 1 << 0,
|
||||||
|
ZS01_REQ_ADDRESS_MSB = 1 << 1, // Unused
|
||||||
|
ZS01_REQ_PRIVILEGED = 1 << 2
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ZS01ResponseCode : uint8_t {
|
||||||
|
// The meaning of these codes is currently unknown. Presumably:
|
||||||
|
// - one of the "security errors" is a CRC validation failure, the other
|
||||||
|
// could be data key related, the third one could be DS2401 related;
|
||||||
|
// - one of the unknown errors is for invalid commands or addresses;
|
||||||
|
// - one of the unknown errors is for actual read/write failures.
|
||||||
|
ZS01_RESP_NO_ERROR = 0x00,
|
||||||
|
ZS01_RESP_UNKNOWN_ERROR1 = 0x01,
|
||||||
|
ZS01_RESP_SECURITY_ERROR1 = 0x02,
|
||||||
|
ZS01_RESP_SECURITY_ERROR2 = 0x03,
|
||||||
|
ZS01_RESP_UNKNOWN_ERROR2 = 0x04,
|
||||||
|
ZS01_RESP_SECURITY_ERROR3 = 0x05
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ZS01 packet scrambling */
|
||||||
|
|
||||||
|
class ZS01Key {
|
||||||
|
public:
|
||||||
|
uint8_t add[KEY_LENGTH];
|
||||||
|
uint8_t shift[KEY_LENGTH];
|
||||||
|
|
||||||
|
void unpackFrom(const uint8_t *key);
|
||||||
|
void packInto(uint8_t *key) const;
|
||||||
|
|
||||||
|
void scramblePacket(
|
||||||
|
uint8_t *data,
|
||||||
|
size_t length,
|
||||||
|
uint8_t state = 0xff
|
||||||
|
) const;
|
||||||
|
void unscramblePacket(
|
||||||
|
uint8_t *data,
|
||||||
|
size_t length,
|
||||||
|
uint8_t state = 0xff
|
||||||
|
) const;
|
||||||
|
void scramblePayload(
|
||||||
|
uint8_t *data,
|
||||||
|
size_t length,
|
||||||
|
uint8_t state = 0xff
|
||||||
|
) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ZS01 packet structure */
|
||||||
|
|
||||||
|
class ZS01Packet {
|
||||||
|
public:
|
||||||
|
uint8_t command, address, data[8];
|
||||||
|
uint16_t crc;
|
||||||
|
|
||||||
|
void updateChecksum(void);
|
||||||
|
bool validateChecksum(void) const;
|
||||||
|
|
||||||
|
void setRead(uint16_t _address);
|
||||||
|
void setWrite(uint16_t _address, const uint8_t *_data);
|
||||||
|
void encodeRequest(const uint8_t *key = nullptr, uint8_t state = 0xff);
|
||||||
|
bool decodeResponse(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ZS01 security cartridge driver */
|
||||||
|
|
||||||
|
class ZS01Cart : public Cart {
|
||||||
|
private:
|
||||||
|
uint8_t _scramblerState;
|
||||||
|
|
||||||
|
CartError _transact(const ZS01Packet &request, ZS01Packet &response);
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline ZS01Cart(const bus::I2CDriver &i2c)
|
||||||
|
: Cart(i2c, ZS01, 112) {}
|
||||||
|
|
||||||
|
CartError read(
|
||||||
|
void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key = nullptr
|
||||||
|
);
|
||||||
|
CartError write(
|
||||||
|
const void *data,
|
||||||
|
uint16_t lba,
|
||||||
|
size_t count,
|
||||||
|
const uint8_t *key
|
||||||
|
);
|
||||||
|
CartError erase(const uint8_t *key);
|
||||||
|
|
||||||
|
CartError readConfig(uint8_t *config, const uint8_t *key);
|
||||||
|
CartError writeConfig(const uint8_t *config, const uint8_t *key);
|
||||||
|
CartError setKey(const uint8_t *newKey, const uint8_t *oldKey);
|
||||||
|
|
||||||
|
CartError readID(bus::OneWireID *output);
|
||||||
|
CartError readInternalID(bus::OneWireID *output);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -421,7 +421,7 @@ bool ISO9660Provider::init(blkdev::Device &dev) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_copyPVDString(volumeLabel, pvd->volume, sizeof(pvd->volume));
|
_copyPVDString(volumeLabel, pvd->volume, sizeof(pvd->volume));
|
||||||
__builtin_memcpy(&_root, &(pvd->root), sizeof(_root));
|
util::copy(_root, pvd->root);
|
||||||
|
|
||||||
type = ISO9660;
|
type = ISO9660;
|
||||||
capacity = uint64_t(pvd->volumeLength.le) * _dev->sectorLength;
|
capacity = uint64_t(pvd->volumeLength.le) * _dev->sectorLength;
|
||||||
|
@ -138,6 +138,29 @@ size_t receive(void *data, size_t length, bool wait) {
|
|||||||
|
|
||||||
/* Asynchronous MDEC-to-VRAM image uploader */
|
/* Asynchronous MDEC-to-VRAM image uploader */
|
||||||
|
|
||||||
|
static void _uploadBlock(int16_t x, int16_t y, size_t blockWidth) {
|
||||||
|
while (!(GPU_GP1 & GP1_STAT_CMD_READY))
|
||||||
|
__asm__ volatile("");
|
||||||
|
|
||||||
|
GPU_GP0 = gp0_vramWrite();
|
||||||
|
GPU_GP0 = gp0_xy(x, y);
|
||||||
|
GPU_GP0 = gp0_xy(blockWidth, MACROBLOCK_SIZE);
|
||||||
|
|
||||||
|
for (size_t i = blockWidth; i > 0; i -= 2) {
|
||||||
|
// In order to maximize efficiency, saturate the GP0 FIFO by copying
|
||||||
|
// 16 words (32 pixels in 16bpp mode) at a time.
|
||||||
|
while (!(GPU_GP1 & GP1_STAT_WRITE_READY))
|
||||||
|
__asm__ volatile("");
|
||||||
|
|
||||||
|
for (int j = 4; j > 0; j--) {
|
||||||
|
GPU_GP0 = MDEC0;
|
||||||
|
GPU_GP0 = MDEC0;
|
||||||
|
GPU_GP0 = MDEC0;
|
||||||
|
GPU_GP0 = MDEC0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VRAMUploader::VRAMUploader(const gpu::Rect &rect, GP1ColorDepth colorDepth) {
|
VRAMUploader::VRAMUploader(const gpu::Rect &rect, GP1ColorDepth colorDepth) {
|
||||||
_x = rect.x1;
|
_x = rect.x1;
|
||||||
_y = rect.y1;
|
_y = rect.y1;
|
||||||
@ -166,26 +189,7 @@ bool VRAMUploader::pollRowMajor(void) {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
while (MDEC1 & MDEC_STAT_DREQ_OUT) {
|
while (MDEC1 & MDEC_STAT_DREQ_OUT) {
|
||||||
while (!(GPU_GP1 & GP1_STAT_CMD_READY))
|
_uploadBlock(_x, _y, _blockWidth);
|
||||||
__asm__ volatile("");
|
|
||||||
|
|
||||||
GPU_GP0 = gp0_vramWrite();
|
|
||||||
GPU_GP0 = gp0_xy(_x, _y);
|
|
||||||
GPU_GP0 = gp0_xy(_blockWidth, MACROBLOCK_SIZE);
|
|
||||||
|
|
||||||
for (size_t i = _blockWidth; i > 0; i -= 2) {
|
|
||||||
// In order to maximize efficiency, saturate the GP0 FIFO by copying
|
|
||||||
// 16 words (32 pixels in 16bpp mode) at a time.
|
|
||||||
while (!(GPU_GP1 & GP1_STAT_WRITE_READY))
|
|
||||||
__asm__ volatile("");
|
|
||||||
|
|
||||||
for (int j = 4; j > 0; j--) {
|
|
||||||
GPU_GP0 = MDEC0;
|
|
||||||
GPU_GP0 = MDEC0;
|
|
||||||
GPU_GP0 = MDEC0;
|
|
||||||
GPU_GP0 = MDEC0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_x += _blockWidth;
|
_x += _blockWidth;
|
||||||
|
|
||||||
@ -203,24 +207,7 @@ bool VRAMUploader::pollColumnMajor(void) {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
while (MDEC1 & MDEC_STAT_DREQ_OUT) {
|
while (MDEC1 & MDEC_STAT_DREQ_OUT) {
|
||||||
while (!(GPU_GP1 & GP1_STAT_CMD_READY))
|
_uploadBlock(_x, _y, _blockWidth);
|
||||||
__asm__ volatile("");
|
|
||||||
|
|
||||||
GPU_GP0 = gp0_vramWrite();
|
|
||||||
GPU_GP0 = gp0_xy(_x, _y);
|
|
||||||
GPU_GP0 = gp0_xy(_blockWidth, MACROBLOCK_SIZE);
|
|
||||||
|
|
||||||
for (size_t i = _blockWidth; i > 0; i -= 2) {
|
|
||||||
while (!(GPU_GP1 & GP1_STAT_WRITE_READY))
|
|
||||||
__asm__ volatile("");
|
|
||||||
|
|
||||||
for (int j = 4; j > 0; j--) {
|
|
||||||
GPU_GP0 = MDEC0;
|
|
||||||
GPU_GP0 = MDEC0;
|
|
||||||
GPU_GP0 = MDEC0;
|
|
||||||
GPU_GP0 = MDEC0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_y += MACROBLOCK_SIZE;
|
_y += MACROBLOCK_SIZE;
|
||||||
|
|
||||||
@ -366,9 +353,7 @@ static const BSHuffmanTable _HUFFMAN_TABLE{
|
|||||||
};
|
};
|
||||||
|
|
||||||
void initBSHuffmanTable(void) {
|
void initBSHuffmanTable(void) {
|
||||||
auto table = reinterpret_cast<BSHuffmanTable *>(CACHE_BASE);
|
util::copy(*reinterpret_cast<BSHuffmanTable *>(CACHE_BASE), _HUFFMAN_TABLE);
|
||||||
|
|
||||||
__builtin_memcpy(table, &_HUFFMAN_TABLE, sizeof(BSHuffmanTable));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "common/nvram/bios.hpp"
|
#include "common/nvram/bios.hpp"
|
||||||
#include "common/util/hash.hpp"
|
#include "common/util/hash.hpp"
|
||||||
#include "common/util/log.hpp"
|
#include "common/util/log.hpp"
|
||||||
|
#include "common/util/templates.hpp"
|
||||||
#include "ps1/registers.h"
|
#include "ps1/registers.h"
|
||||||
#include "ps1/registers573.h"
|
#include "ps1/registers573.h"
|
||||||
|
|
||||||
@ -129,7 +130,7 @@ bool getShellInfo(ShellInfo &output) {
|
|||||||
if (!shell.validateHash())
|
if (!shell.validateHash())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
__builtin_memcpy(&output, &shell, sizeof(ShellInfo));
|
util::copy(output, shell);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,27 +25,10 @@
|
|||||||
|
|
||||||
namespace nvram {
|
namespace nvram {
|
||||||
|
|
||||||
const char *const REGION_ERROR_NAMES[]{
|
|
||||||
"NO_ERROR",
|
|
||||||
"UNSUPPORTED_OP",
|
|
||||||
"NO_DEVICE",
|
|
||||||
"CHIP_TIMEOUT",
|
|
||||||
"CHIP_ERROR",
|
|
||||||
"VERIFY_MISMATCH",
|
|
||||||
"WRITE_PROTECTED"
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr int _FLASH_WRITE_TIMEOUT = 10000000;
|
static constexpr int _FLASH_WRITE_TIMEOUT = 10000000;
|
||||||
static constexpr int _FLASH_ERASE_TIMEOUT = 20000000;
|
static constexpr int _FLASH_ERASE_TIMEOUT = 20000000;
|
||||||
|
|
||||||
/* Internal and PCMCIA flash base class */
|
/* Internal and PCMCIA flash base class */
|
||||||
/*
|
|
||||||
#if 0
|
|
||||||
ptrOffset -= offset % getChipLength();
|
|
||||||
#else
|
|
||||||
ptrOffset &= ~(getChipLength() - 1);
|
|
||||||
#endif
|
|
||||||
*/
|
|
||||||
|
|
||||||
static inline volatile uint16_t *_toFlashPtr(uint32_t ptrOffset) {
|
static inline volatile uint16_t *_toFlashPtr(uint32_t ptrOffset) {
|
||||||
return reinterpret_cast<volatile uint16_t *>(DEV0_BASE | ptrOffset);
|
return reinterpret_cast<volatile uint16_t *>(DEV0_BASE | ptrOffset);
|
||||||
|
33
src/common/nvram/region.cpp
Normal file
33
src/common/nvram/region.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* 573in1 - Copyright (C) 2022-2025 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 "common/nvram/region.hpp"
|
||||||
|
|
||||||
|
namespace nvram {
|
||||||
|
|
||||||
|
/* Utilities */
|
||||||
|
|
||||||
|
const char *const REGION_ERROR_NAMES[]{
|
||||||
|
"NO_ERROR",
|
||||||
|
"UNSUPPORTED_OP",
|
||||||
|
"NO_DEVICE",
|
||||||
|
"CHIP_TIMEOUT",
|
||||||
|
"CHIP_ERROR",
|
||||||
|
"VERIFY_MISMATCH",
|
||||||
|
"WRITE_PROTECTED"
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -47,19 +47,23 @@ static constexpr uint8_t _MAS_I2C_ADDR = 0x1d;
|
|||||||
bool MAS3507DDriver::_issueCommand(const uint8_t *data, size_t length) const {
|
bool MAS3507DDriver::_issueCommand(const uint8_t *data, size_t length) const {
|
||||||
if (!_i2c.startDeviceWrite(_MAS_I2C_ADDR)) {
|
if (!_i2c.startDeviceWrite(_MAS_I2C_ADDR)) {
|
||||||
_i2c.stop();
|
_i2c.stop();
|
||||||
|
|
||||||
LOG_IO("chip not responding");
|
LOG_IO("chip not responding");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_i2c.writeByte(_MAS_PACKET_COMMAND);
|
_i2c.writeByte(_MAS_PACKET_COMMAND);
|
||||||
|
|
||||||
if (!_i2c.getACK()) {
|
if (!_i2c.getACK()) {
|
||||||
_i2c.stop();
|
_i2c.stop();
|
||||||
|
|
||||||
LOG_IO("NACK while sending type");
|
LOG_IO("NACK while sending type");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_i2c.writeBytes(data, length)) {
|
if (!_i2c.writeBytes(data, length)) {
|
||||||
_i2c.stop();
|
_i2c.stop();
|
||||||
|
|
||||||
LOG_IO("NACK while sending data");
|
LOG_IO("NACK while sending data");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -74,19 +78,22 @@ bool MAS3507DDriver::_issueRead(uint8_t *data, size_t length) const {
|
|||||||
// read packet and actually reading the data.
|
// read packet and actually reading the data.
|
||||||
if (!_i2c.startDeviceWrite(_MAS_I2C_ADDR)) {
|
if (!_i2c.startDeviceWrite(_MAS_I2C_ADDR)) {
|
||||||
_i2c.stop();
|
_i2c.stop();
|
||||||
|
|
||||||
LOG_IO("chip not responding");
|
LOG_IO("chip not responding");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_i2c.writeByte(_MAS_PACKET_READ);
|
_i2c.writeByte(_MAS_PACKET_READ);
|
||||||
|
|
||||||
if (!_i2c.getACK()) {
|
if (!_i2c.getACK()) {
|
||||||
_i2c.stop();
|
_i2c.stop();
|
||||||
|
|
||||||
LOG_IO("NACK while sending type");
|
LOG_IO("NACK while sending type");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_i2c.startDeviceRead(_MAS_I2C_ADDR)) {
|
if (!_i2c.startDeviceRead(_MAS_I2C_ADDR)) {
|
||||||
_i2c.stop();
|
_i2c.stop();
|
||||||
|
|
||||||
LOG_IO("chip not responding");
|
LOG_IO("chip not responding");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -107,7 +114,7 @@ int MAS3507DDriver::readFrameCount(void) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int MAS3507DDriver::readMemory(int bank, uint16_t offset) const {
|
int MAS3507DDriver::readMemory(int bank, uint16_t offset) const {
|
||||||
uint8_t packet[6]{
|
const uint8_t packet[6]{
|
||||||
bank ? _MAS_CMD_READ_D1 : _MAS_CMD_READ_D0,
|
bank ? _MAS_CMD_READ_D1 : _MAS_CMD_READ_D0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@ -131,7 +138,7 @@ int MAS3507DDriver::readMemory(int bank, uint16_t offset) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MAS3507DDriver::writeMemory(int bank, uint16_t offset, int value) const {
|
bool MAS3507DDriver::writeMemory(int bank, uint16_t offset, int value) const {
|
||||||
uint8_t packet[10]{
|
const uint8_t packet[10]{
|
||||||
bank ? _MAS_CMD_WRITE_D1 : _MAS_CMD_WRITE_D0,
|
bank ? _MAS_CMD_WRITE_D1 : _MAS_CMD_WRITE_D0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@ -148,7 +155,7 @@ bool MAS3507DDriver::writeMemory(int bank, uint16_t offset, int value) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int MAS3507DDriver::readReg(uint8_t offset) const {
|
int MAS3507DDriver::readReg(uint8_t offset) const {
|
||||||
uint8_t packet[2]{
|
const uint8_t packet[2]{
|
||||||
uint8_t(((offset >> 4) & 0x0f) | _MAS_CMD_READ_REG),
|
uint8_t(((offset >> 4) & 0x0f) | _MAS_CMD_READ_REG),
|
||||||
uint8_t( (offset << 4) & 0xf0)
|
uint8_t( (offset << 4) & 0xf0)
|
||||||
};
|
};
|
||||||
@ -168,7 +175,7 @@ int MAS3507DDriver::readReg(uint8_t offset) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool MAS3507DDriver::writeReg(uint8_t offset, int value) const {
|
bool MAS3507DDriver::writeReg(uint8_t offset, int value) const {
|
||||||
uint8_t packet[4]{
|
const uint8_t packet[4]{
|
||||||
uint8_t(((offset >> 4) & 0x0f) | _MAS_CMD_WRITE_REG),
|
uint8_t(((offset >> 4) & 0x0f) | _MAS_CMD_WRITE_REG),
|
||||||
uint8_t(((value >> 0) & 0x0f) | ((offset << 4) & 0xf0)),
|
uint8_t(((value >> 0) & 0x0f) | ((offset << 4) & 0xf0)),
|
||||||
uint8_t( (value >> 12) & 0xff),
|
uint8_t( (value >> 12) & 0xff),
|
||||||
@ -182,7 +189,7 @@ bool MAS3507DDriver::runFunction(uint16_t func) const {
|
|||||||
if (func > 0x1fff)
|
if (func > 0x1fff)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
uint8_t packet[2]{
|
const uint8_t packet[2]{
|
||||||
uint8_t((func >> 8) & 0xff),
|
uint8_t((func >> 8) & 0xff),
|
||||||
uint8_t((func >> 0) & 0xff)
|
uint8_t((func >> 0) & 0xff)
|
||||||
};
|
};
|
||||||
|
@ -209,7 +209,7 @@ static const MD5Round _MD5_ROUNDS[]{
|
|||||||
|
|
||||||
MD5::MD5(void)
|
MD5::MD5(void)
|
||||||
: _blockCount(0), _bufferLength(0) {
|
: _blockCount(0), _bufferLength(0) {
|
||||||
__builtin_memcpy(_state, _MD5_SEED, sizeof(_state));
|
util::copy(_state, _MD5_SEED);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MD5::_flushBlock(const void *data) {
|
void MD5::_flushBlock(const void *data) {
|
||||||
|
@ -27,7 +27,7 @@ namespace util {
|
|||||||
template<typename T> static inline uint32_t sum(const T *data, size_t length) {
|
template<typename T> static inline uint32_t sum(const T *data, size_t length) {
|
||||||
uint32_t value = 0;
|
uint32_t value = 0;
|
||||||
|
|
||||||
for (; length; length--)
|
for (; length > 0; length--)
|
||||||
value += uint32_t(*(data++));
|
value += uint32_t(*(data++));
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
@ -36,12 +36,25 @@ template<typename T> static inline uint32_t sum(const T *data, size_t length) {
|
|||||||
template<typename T> static inline T bitwiseXOR(const T *data, size_t length) {
|
template<typename T> static inline T bitwiseXOR(const T *data, size_t length) {
|
||||||
T value = 0;
|
T value = 0;
|
||||||
|
|
||||||
for (; length; length--)
|
for (; length > 0; length--)
|
||||||
value ^= *(data++);
|
value ^= *(data++);
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T> static inline bool isEmpty(
|
||||||
|
const T *data,
|
||||||
|
size_t length,
|
||||||
|
T value = 0
|
||||||
|
) {
|
||||||
|
for (; length > 0; length--) {
|
||||||
|
if (*(data++) != value)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T> static constexpr inline T min(T a, T b) {
|
template<typename T> static constexpr inline T min(T a, T b) {
|
||||||
return (a < b) ? a : b;
|
return (a < b) ? a : b;
|
||||||
}
|
}
|
||||||
@ -53,8 +66,12 @@ template<typename T> static constexpr inline T max(T a, T b) {
|
|||||||
template<typename T> static constexpr inline T clamp(
|
template<typename T> static constexpr inline T clamp(
|
||||||
T value, T minValue, T maxValue
|
T value, T minValue, T maxValue
|
||||||
) {
|
) {
|
||||||
return (value < minValue) ? minValue :
|
if (value < minValue)
|
||||||
((value > maxValue) ? maxValue : value);
|
return minValue;
|
||||||
|
if (value > maxValue)
|
||||||
|
return maxValue;
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T> static constexpr inline T rotateLeft(T value, int amount) {
|
template<typename T> static constexpr inline T rotateLeft(T value, int amount) {
|
||||||
@ -87,10 +104,23 @@ template<typename T> static inline void clear(T &obj, uint8_t value = 0) {
|
|||||||
__builtin_memset(&obj, value, sizeof(obj));
|
__builtin_memset(&obj, value, sizeof(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T> static inline void copy(T &dest, const T &source) {
|
||||||
|
static_assert(
|
||||||
|
sizeof(dest) == sizeof(source),
|
||||||
|
"source and destination sizes do not match"
|
||||||
|
);
|
||||||
|
|
||||||
|
__builtin_memcpy(&dest, &source, sizeof(source));
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T> static constexpr inline size_t countOf(T &array) {
|
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> static constexpr inline auto *endOf(T &array) {
|
||||||
|
return &array[countOf(array)];
|
||||||
|
}
|
||||||
|
|
||||||
/* Concatenation and BCD conversion */
|
/* Concatenation and BCD conversion */
|
||||||
|
|
||||||
template<typename T, typename V, typename... A>
|
template<typename T, typename V, typename... A>
|
||||||
|
@ -404,7 +404,7 @@ int FileBrowserScreen::loadDirectory(
|
|||||||
? (directories++)
|
? (directories++)
|
||||||
: (files++);
|
: (files++);
|
||||||
|
|
||||||
__builtin_memcpy(ptr, &info, sizeof(fs::FileInfo));
|
util::copy(*ptr, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
directory->close();
|
directory->close();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user