Adding cancellation callbacks to LinkCard and sending the right loader based on region

This commit is contained in:
Rodrigo Alfonso 2025-02-17 00:08:41 -03:00
parent ec14b25b37
commit 5cb96b5b69
2 changed files with 112 additions and 55 deletions

View File

@ -41,13 +41,13 @@ int main() {
;
}
bool a = true;
bool a = true, b = true;
while (true) {
std::string output = "LinkCard_demo (v8.0.0)\n\n";
output += "Device: ";
auto device = linkCard->getConnectedDevice();
auto device = linkCard->getConnectedDevice([]() { return false; });
switch (device) {
case LinkCard::ConnectedDevice::E_READER_JAP:
case LinkCard::ConnectedDevice::E_READER_USA: {
@ -55,11 +55,21 @@ int main() {
if (Common::didPress(KEY_A, a)) {
Common::log("Sending...\n\nPress B to cancel");
auto fileName = device == LinkCard::ConnectedDevice::E_READER_JAP
? JAP_LOADER
: USA_LOADER;
u32 loaderSize;
const u8* loader =
(const u8*)gbfs_get_nth_obj(fs, 0, NULL, &loaderSize);
auto res = linkCard->sendLoader(loader, loaderSize);
Common::log(std::to_string((int)res));
const u8* loader = (const u8*)gbfs_get_obj(fs, fileName, &loaderSize);
auto res = linkCard->sendLoader(loader, loaderSize, [&b]() {
return Common::didPress(KEY_B, b);
});
if (res == LinkCard::SendResult::SUCCESS)
Common::log("Success! Press DOWN");
else
Common::log("Error " + std::to_string((int)res) + "! Press DOWN");
Common::waitForKey(KEY_DOWN);
}

View File

@ -10,13 +10,21 @@
// - 2) Probe the connected device:
// auto device = getConnectedDevice();
// - 2) Send the DLC loader program:
// if (device == LinkCard::ConnectedDevice::E_READER_LOADER_NEEDED)
// LinkCard::SendResult result = linkCard->sendLoader(loader);
// if (device == LinkCard::ConnectedDevice::E_READER_USA)
// LinkCard::SendResult result = linkCard->sendLoader(loader, []() {
// u16 keys = ~REG_KEYS & KEY_ANY;
// return keys & KEY_START;
// }));
// - 3) Receive scanned cards:
// if (device == LinkCard::ConnectedDevice::CARD_SCANNER) {
// u8 card[2076]
// if (linkCard->receiveCard(card)) {
// // use card as DLC
// u8 card[1998];
// bool received = linkCard->receiveCard(card, []() {
// u16 keys = ~REG_KEYS & KEY_ANY;
// return keys & KEY_START;
// }));
//
// if (received) {
// // use `card` as DLC
// }
// }
// --------------------------------------------------------------------------
@ -59,6 +67,7 @@ class LinkCard {
SUCCESS,
UNALIGNED,
INVALID_SIZE,
CANCELED,
WRONG_DEVICE,
FAILURE_DURING_TRANSFER
};
@ -76,11 +85,24 @@ class LinkCard {
* with `sendLoader(...)`.
* If it returns `DLC_LOADER`, you should receive scanned cards with
* `receiveCard(...)`.
* If it returns `WRONG_CONNECTION`, the console is connected with the wrong
* end (it has to use the 1P end, aka playerId=0). If it returns
* `UNKNOWN_DEVICE`, the connected console uses another protocol.
* \warning Blocks the system until completion.
*/
ConnectedDevice getConnectedDevice() {
return getConnectedDevice([]() { return false; });
}
/**
* Returns the connected device.
* If it returns `E_READER_USA` or `E_READER_JAP`, you should send the loader
* with `sendLoader(...)`.
* If it returns `DLC_LOADER`, you should receive scanned cards with
* `receiveCard(...)`.
* @param cancel A function that will be continuously invoked. If it returns
* `true`, the transfer will be aborted.
* \warning Blocks the system until completion or cancellation.
*/
template <typename F>
ConnectedDevice getConnectedDevice(F cancel) {
linkRawCable.activate();
auto guard = Link::ScopeGuard([&]() { linkRawCable.deactivate(); });
@ -88,7 +110,7 @@ class LinkCard {
return ConnectedDevice::WRONG_CONNECTION;
u16 remoteValues[3];
for (u32 i = 0; i < 3; i++) {
remoteValues[i] = transferMulti(0);
remoteValues[i] = transferMulti(0, []() { return false; });
if (i > 0 && remoteValues[i] != remoteValues[i - 1])
return ConnectedDevice::UNKNOWN_DEVICE;
}
@ -107,15 +129,19 @@ class LinkCard {
* the scanned cards to the game. Must be 4-byte aligned.
* @param loaderSize Size of the loader program in bytes. Must be a multiple
* of 32.
* @param cancel A function that will be continuously invoked. If it returns
* `true`, the transfer will be aborted.
* \warning Blocks the system until completion or cancellation.
*/
SendResult sendLoader(const u8* loader, u32 loaderSize) {
template <typename F>
SendResult sendLoader(const u8* loader, u32 loaderSize, F cancel) {
if ((u32)loader % 4 != 0)
return SendResult::UNALIGNED;
if (loaderSize < MIN_LOADER_SIZE || loaderSize > MAX_LOADER_SIZE ||
(loaderSize % 0x20) != 0)
return SendResult::INVALID_SIZE;
auto device = getConnectedDevice();
auto device = getConnectedDevice(cancel);
if (device != ConnectedDevice::E_READER_USA &&
device != ConnectedDevice::E_READER_JAP)
return SendResult::WRONG_DEVICE;
@ -124,52 +150,71 @@ class LinkCard {
? DEVICE_E_READER_USA
: DEVICE_E_READER_JAP;
linkRawCable.activate();
Link::wait(MODE_SWITCH_WAIT);
{
linkRawCable.activate();
auto guard = Link::ScopeGuard([&]() { linkRawCable.deactivate(); });
retry:
transferMulti(HANDSHAKE_SEND);
if (transferMulti(HANDSHAKE_SEND) != deviceId)
goto retry;
if (transferMulti(deviceId) != deviceId)
goto retry;
Link::wait(MODE_SWITCH_WAIT);
if (cancel())
return SendResult::CANCELED;
linkRawCable.deactivate();
linkSPI.activate(LinkSPI::Mode::MASTER_256KBPS);
Link::wait(MODE_SWITCH_WAIT);
transferNormal(loaderSize); // cancel?
u32 checksum = 0;
u32* dataOut = (u32*)loader;
for (u32 i = 0; i < loaderSize / 4; i++) {
u32 data = dataOut[i];
checksum += data;
transferNormal(data);
// cancel?
retry:
transferMulti(HANDSHAKE_SEND, cancel);
if (transferMulti(HANDSHAKE_SEND, cancel) != deviceId)
goto retry;
if (transferMulti(deviceId, cancel) != deviceId)
goto retry;
}
transferNormal(0);
transferNormal(checksum);
transferNormal(checksum);
{
linkSPI.activate(LinkSPI::Mode::MASTER_256KBPS);
auto guard = Link::ScopeGuard([&]() { linkSPI.deactivate(); });
linkSPI.deactivate();
linkRawCable.activate();
Link::wait(MODE_SWITCH_WAIT);
Link::wait(MODE_SWITCH_WAIT);
if (cancel())
return SendResult::CANCELED;
if (transferMulti(deviceId) != deviceId ||
transferMulti(deviceId) != TRANSFER_SUCCESS) {
linkRawCable.deactivate();
return SendResult::FAILURE_DURING_TRANSFER;
transferNormal(loaderSize, cancel);
u32 checksum = 0;
u32* dataOut = (u32*)loader;
for (u32 i = 0; i < loaderSize / 4; i++) {
u32 data = dataOut[i];
checksum += data;
transferNormal(data, cancel);
}
transferNormal(0, cancel);
transferNormal(checksum, cancel);
transferNormal(checksum, cancel);
}
linkRawCable.deactivate();
{
linkRawCable.activate();
auto guard = Link::ScopeGuard([&]() { linkRawCable.deactivate(); });
Link::wait(MODE_SWITCH_WAIT);
if (cancel())
return SendResult::CANCELED;
if (transferMulti(deviceId, cancel) != deviceId ||
transferMulti(deviceId, cancel) != TRANSFER_SUCCESS)
return SendResult::FAILURE_DURING_TRANSFER;
}
return SendResult::SUCCESS;
}
bool receiveCard() {
/**
* Receives a 1998-byte `card` from the DLC Loader and returns `true` if it
* worked correctly.
* @param card A pointer to fill the card bytes.
* @param cancel A function that will be continuously invoked. If it returns
* `true`, the transfer will be aborted.
* \warning Blocks the system until completion or cancellation.
*/
template <typename F>
bool receiveCard(u8* card, F cancel) {
LINK_READ_TAG(LINK_CARD_VERSION);
// TODO: IMPLEMENT
return true;
@ -179,14 +224,16 @@ class LinkCard {
LinkRawCable linkRawCable;
LinkSPI linkSPI;
u16 transferMulti(u16 value) {
template <typename F>
u16 transferMulti(u16 value, F cancel) {
Link::wait(PRE_TRANSFER_WAIT);
return linkRawCable.transfer(value).data[1];
return linkRawCable.transfer(value, cancel).data[1];
}
void transferNormal(u32 value) {
template <typename F>
void transferNormal(u32 value, F cancel) {
Link::wait(PRE_TRANSFER_WAIT);
linkSPI.transfer(value);
linkSPI.transfer(value, cancel);
}
};