Add new cart drivers, prepare for app refactor
Some checks failed
Build / Run build (push) Failing after 6m3s

This commit is contained in:
spicyjpeg 2025-02-25 00:41:08 +01:00
parent 5ab549ce41
commit 1a486b0508
No known key found for this signature in database
GPG Key ID: 5CC87404C01DF393
23 changed files with 1447 additions and 130 deletions

View File

@ -1,4 +1,8 @@
{
"line-length": {
"tables": false
},
"emphasis-style": false,
"fenced-code-language": false,
"first-line-heading": false,

View File

@ -72,13 +72,13 @@ set(
blkdevSources
src/common/blkdev/ata.cpp
src/common/blkdev/atapi.cpp
src/common/blkdev/device.cpp
src/common/blkdev/idebase.cpp
src/common/blkdev/memorycard.cpp
)
set(
cartSources
src/common/cart/cart.cpp
src/common/cart/cartio.cpp
src/common/cart/x76.cpp
src/common/cart/zs01.cpp
)
@ -104,6 +104,7 @@ set(
src/common/nvram/bios.cpp
src/common/nvram/flash.cpp
src/common/nvram/flashdetect.cpp
src/common/nvram/region.cpp
)
set(

View File

@ -97,7 +97,7 @@ DeviceError ATADevice::_transfer(
// Data must be transferred one sector at a time as the drive may
// 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();
if (error)
@ -107,6 +107,8 @@ DeviceError ATADevice::_transfer(
_writeData(reinterpret_cast<const void *>(ptr), _SECTOR_LENGTH);
else
_readData(reinterpret_cast<void *>(ptr), _SECTOR_LENGTH);
ptr += _SECTOR_LENGTH;
}
lba += chunkLength;

View 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"
};
}

View File

@ -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
* terms of the GNU General Public License as published by the Free Software
@ -18,7 +18,6 @@
#include <stddef.h>
#include <stdint.h>
#include "common/util/templates.hpp"
namespace blkdev {
@ -30,38 +29,16 @@ class MSF {
public:
uint8_t minute, second, frame;
inline void fromLBA(uint32_t lba) {
lba += CDROM_TOC_PREGAP;
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;
}
void fromLBA(uint32_t lba);
uint32_t toLBA(void) const;
};
class BCDMSF {
public:
uint8_t minute, second, frame;
inline void fromLBA(uint32_t lba) {
lba += CDROM_TOC_PREGAP;
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);
}
void fromLBA(uint32_t lba);
uint32_t toLBA(void) const;
};
/* Base block device class */

View File

@ -27,18 +27,6 @@
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 */
static void _copyString(char *output, const uint16_t *input, size_t length) {

View File

@ -313,7 +313,27 @@ enum OneWireCommand : uint8_t {
_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;
if (!reset()) {
@ -323,22 +343,12 @@ bool OneWireDriver::readID(uint8_t *output) const {
writeByte(_CMD_READ_ROM);
for (int i = 0; i < 8; i++)
output[i] = readByte();
auto ptr = reinterpret_cast<uint8_t *>(output);
auto code = output[0];
auto crc = util::dsCRC8(output, 7);
for (int i = 8; i > 0; i--)
*(ptr++) = readByte();
if (!code || (code == 0xff)) {
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;
return output->validateChecksum();
}
}

View File

@ -18,6 +18,7 @@
#include <stddef.h>
#include <stdint.h>
#include "common/util/string.hpp"
#include "ps1/system.h"
namespace bus {
@ -138,6 +139,22 @@ public:
/* 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 {
private:
inline void _set(bool value, int delay) const {
@ -154,7 +171,7 @@ public:
uint8_t readByte(void) 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
View 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
View 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
View 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
View 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
View 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
View 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);
};
}

View File

@ -421,7 +421,7 @@ bool ISO9660Provider::init(blkdev::Device &dev) {
}
_copyPVDString(volumeLabel, pvd->volume, sizeof(pvd->volume));
__builtin_memcpy(&_root, &(pvd->root), sizeof(_root));
util::copy(_root, pvd->root);
type = ISO9660;
capacity = uint64_t(pvd->volumeLength.le) * _dev->sectorLength;

View File

@ -138,6 +138,29 @@ size_t receive(void *data, size_t length, bool wait) {
/* 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) {
_x = rect.x1;
_y = rect.y1;
@ -166,26 +189,7 @@ bool VRAMUploader::pollRowMajor(void) {
return false;
while (MDEC1 & MDEC_STAT_DREQ_OUT) {
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;
}
}
_uploadBlock(_x, _y, _blockWidth);
_x += _blockWidth;
@ -203,24 +207,7 @@ bool VRAMUploader::pollColumnMajor(void) {
return false;
while (MDEC1 & MDEC_STAT_DREQ_OUT) {
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) {
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;
}
}
_uploadBlock(_x, _y, _blockWidth);
_y += MACROBLOCK_SIZE;
@ -366,9 +353,7 @@ static const BSHuffmanTable _HUFFMAN_TABLE{
};
void initBSHuffmanTable(void) {
auto table = reinterpret_cast<BSHuffmanTable *>(CACHE_BASE);
__builtin_memcpy(table, &_HUFFMAN_TABLE, sizeof(BSHuffmanTable));
util::copy(*reinterpret_cast<BSHuffmanTable *>(CACHE_BASE), _HUFFMAN_TABLE);
}
}

View File

@ -19,6 +19,7 @@
#include "common/nvram/bios.hpp"
#include "common/util/hash.hpp"
#include "common/util/log.hpp"
#include "common/util/templates.hpp"
#include "ps1/registers.h"
#include "ps1/registers573.h"
@ -129,7 +130,7 @@ bool getShellInfo(ShellInfo &output) {
if (!shell.validateHash())
continue;
__builtin_memcpy(&output, &shell, sizeof(ShellInfo));
util::copy(output, shell);
return true;
}

View File

@ -25,27 +25,10 @@
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_ERASE_TIMEOUT = 20000000;
/* 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) {
return reinterpret_cast<volatile uint16_t *>(DEV0_BASE | ptrOffset);

View 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"
};
}

View File

@ -47,19 +47,23 @@ static constexpr uint8_t _MAS_I2C_ADDR = 0x1d;
bool MAS3507DDriver::_issueCommand(const uint8_t *data, size_t length) const {
if (!_i2c.startDeviceWrite(_MAS_I2C_ADDR)) {
_i2c.stop();
LOG_IO("chip not responding");
return false;
}
_i2c.writeByte(_MAS_PACKET_COMMAND);
if (!_i2c.getACK()) {
_i2c.stop();
LOG_IO("NACK while sending type");
return false;
}
if (!_i2c.writeBytes(data, length)) {
_i2c.stop();
LOG_IO("NACK while sending data");
return false;
}
@ -74,19 +78,22 @@ bool MAS3507DDriver::_issueRead(uint8_t *data, size_t length) const {
// read packet and actually reading the data.
if (!_i2c.startDeviceWrite(_MAS_I2C_ADDR)) {
_i2c.stop();
LOG_IO("chip not responding");
return false;
}
_i2c.writeByte(_MAS_PACKET_READ);
if (!_i2c.getACK()) {
_i2c.stop();
LOG_IO("NACK while sending type");
return false;
}
if (!_i2c.startDeviceRead(_MAS_I2C_ADDR)) {
_i2c.stop();
LOG_IO("chip not responding");
return false;
}
@ -107,7 +114,7 @@ int MAS3507DDriver::readFrameCount(void) 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,
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 {
uint8_t packet[10]{
const uint8_t packet[10]{
bank ? _MAS_CMD_WRITE_D1 : _MAS_CMD_WRITE_D0,
0,
0,
@ -148,7 +155,7 @@ bool MAS3507DDriver::writeMemory(int bank, uint16_t offset, int value) 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) & 0xf0)
};
@ -168,7 +175,7 @@ int MAS3507DDriver::readReg(uint8_t offset) 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(((value >> 0) & 0x0f) | ((offset << 4) & 0xf0)),
uint8_t( (value >> 12) & 0xff),
@ -182,7 +189,7 @@ bool MAS3507DDriver::runFunction(uint16_t func) const {
if (func > 0x1fff)
return false;
uint8_t packet[2]{
const uint8_t packet[2]{
uint8_t((func >> 8) & 0xff),
uint8_t((func >> 0) & 0xff)
};

View File

@ -209,7 +209,7 @@ static const MD5Round _MD5_ROUNDS[]{
MD5::MD5(void)
: _blockCount(0), _bufferLength(0) {
__builtin_memcpy(_state, _MD5_SEED, sizeof(_state));
util::copy(_state, _MD5_SEED);
}
void MD5::_flushBlock(const void *data) {

View File

@ -27,7 +27,7 @@ namespace util {
template<typename T> static inline uint32_t sum(const T *data, size_t length) {
uint32_t value = 0;
for (; length; length--)
for (; length > 0; length--)
value += uint32_t(*(data++));
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) {
T value = 0;
for (; length; length--)
for (; length > 0; length--)
value ^= *(data++);
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) {
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(
T value, T minValue, T maxValue
) {
return (value < minValue) ? minValue :
((value > maxValue) ? maxValue : value);
if (value < minValue)
return minValue;
if (value > maxValue)
return maxValue;
return value;
}
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));
}
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) {
return sizeof(array) / sizeof(array[0]);
}
template<typename T> static constexpr inline auto *endOf(T &array) {
return &array[countOf(array)];
}
/* Concatenation and BCD conversion */
template<typename T, typename V, typename... A>

View File

@ -404,7 +404,7 @@ int FileBrowserScreen::loadDirectory(
? (directories++)
: (files++);
__builtin_memcpy(ptr, &info, sizeof(fs::FileInfo));
util::copy(*ptr, info);
}
directory->close();