feat: USB hotswapping for replay storage (#39)

* initial hotswap impl

the general methodology of this change is as follows

1. copy the technique from HID.c to set up a thread and addition to main loop to manage the IOCTLs and keep track of devices being inserted or removed
2. copy enough of libogc/usb.c and libogc/usbstorage.c in to mount inserted devices
3. modify SlippiFileWriter logic to enable hotswap for us

* replays drive LED option

* clear cbw_buffer

I think we got away with this before cuz all __cycle invocations used the same command length. Not sure if this can actually cause errors but for correctness...
This commit is contained in:
Nicolet
2024-09-26 09:51:40 +09:00
committed by GitHub
parent 594fb6d24c
commit 6deec6c9e4
17 changed files with 755 additions and 263 deletions

View File

@@ -21,6 +21,8 @@
// Indicates the maximum number possible for ID. This is important in case the line items change,
// we don't want to persist setting values for a different line item
// THIS AFFECTS sizeof(NIN_CFG).
// IF YOU CHANGE THIS NUMBER, YOU MUST BUMP NIN_CFG_VERSION AND UPDATE LoadNinCFG.
#define MELEE_CODES_MAX_ID 7
#define MELEE_CODES_LINE_ITEM_COUNT 8

View File

@@ -5,10 +5,11 @@
#include "Metadata.h"
#include "../config/MeleeCodes.h"
#define NIN_CFG_VERSION 0x0000000C
#define NIN_CFG_VERSION 0x0000000D
#define NIN_CFG_MAXPAD 4
// IF YOU CHANGE THE SIZE OF THIS struct YOU MUST BUMP NIN_CFG_VERSION AND UPDATE LoadNinCFG.
typedef struct NIN_CFG
{
unsigned int Magicbytes; // 0x01070CF6
@@ -24,6 +25,7 @@ typedef struct NIN_CFG
unsigned char Unused;
unsigned int UseUSB; // 0 for SD, 1 for USB
unsigned int MeleeCodeOptions[MELEE_CODES_MAX_ID + 1]; // IDs are 0 indexed so add 1
unsigned int ReplaysLED; // 0: On, 1: Off
} NIN_CFG;
enum ninconfigbitpos
@@ -44,7 +46,7 @@ enum ninconfigbitpos
NIN_CFG_BIT_MC_MULTI = (11),
NIN_CFG_BIT_SKIP_IPL = (12), // Disabled in Slippi Nintendont
NIN_CFG_BIT_NETWORK = (13),
NIN_CFG_BIT_SLIPPI_FILE_WRITE = (14),
NIN_CFG_BIT_SLIPPI_REPLAYS = (14),
NIN_CFG_BIT_SLIPPI_PORT_A = (15),
// Internal kernel settings.
@@ -68,7 +70,7 @@ enum ninconfig
NIN_CFG_MC_MULTI = (1<<NIN_CFG_BIT_MC_MULTI),
NIN_CFG_SKIP_IPL = (1<<NIN_CFG_BIT_SKIP_IPL),
NIN_CFG_NETWORK = (1<<NIN_CFG_BIT_NETWORK),
NIN_CFG_SLIPPI_FILE_WRITE = (1<<NIN_CFG_BIT_SLIPPI_FILE_WRITE),
NIN_CFG_SLIPPI_REPLAYS = (1<<NIN_CFG_BIT_SLIPPI_REPLAYS),
NIN_CFG_SLIPPI_PORT_A = (1<<NIN_CFG_BIT_SLIPPI_PORT_A),
NIN_CFG_MC_SLOTB = (1<<NIN_CFG_BIT_MC_SLOTB),
@@ -92,7 +94,9 @@ enum ninextrasettings
enum ninslippisettings
{
NIN_SLIPPI_NETWORKING,
NIN_SLIPPI_FILE_WRITE,
NIN_SLIPPI_REPLAYS,
NIN_SLIPPI_REPLAYS_LED,
NIN_SLIPPI_BLANK_0,
NIN_SLIPPI_PORT_A,
NIN_SLIPPI_CUSTOM_CODES,
NIN_SLIPPI_BLANK_1,

View File

@@ -118,4 +118,9 @@ static inline u32 ConfigGetMeleeCodeValue(int identifier)
return ncfg->MeleeCodeOptions[identifier];
}
static inline u32 ConfigGetReplaysLED(void)
{
return ncfg->ReplaysLED;
}
#endif

View File

@@ -43,7 +43,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "ff_utf8.h"
static u8 DummyBuffer[0x1000] __attribute__((aligned(32)));
extern u32 s_cnt;
extern u32 usb_s_cnt;
#ifndef DEBUG_DI
#define dbgprintf(...)
@@ -865,7 +865,7 @@ u32 DIReadThread(void *arg)
case IOS_IOCTL:
if(di_msg->ioctl.command == 2)
{
USBStorage_ReadSectors(read32(HW_TIMER) % s_cnt, 1, DummyBuffer);
USBStorage_ReadSectors(read32(HW_TIMER) % usb_s_cnt, 1, DummyBuffer);
mqueue_ack( di_msg, 0 );
break;
}

View File

@@ -7,12 +7,15 @@
#include "net.h"
#include "Config.h"
#include "usbstorage.h"
// Game can transfer at most 784 bytes / frame
// That means 4704 bytes every 100 ms. Let's aim to handle
// double that, making our read buffer 10000 bytes
#define READ_BUF_SIZE 10000
#define THREAD_CYCLE_TIME_MS 100
#define THREAD_ERROR_TIME_MS 2000
#define LED_FLASH_TIME_MS 1000
#define FOOTER_BUFFER_LENGTH 200
@@ -34,13 +37,24 @@ u32 gameStartTime;
// timer for drive led
u32 driveTimer;
// flag for drive led timer
bool driveTimerSet;
// replays LED setting
bool replaysLED;
void SlippiFileWriterInit()
{
// Move to a more appropriate place later
// Enables Drive LED
set32(HW_GPIO_ENABLE, GPIO_SLOT_LED);
clear32(HW_GPIO_DIR, GPIO_SLOT_LED);
clear32(HW_GPIO_OWNER, GPIO_SLOT_LED);
replaysLED = ConfigGetReplaysLED() == 0;
if (replaysLED)
{
// Move to a more appropriate place later
// Enables Drive LED
set32(HW_GPIO_ENABLE, GPIO_SLOT_LED);
clear32(HW_GPIO_DIR, GPIO_SLOT_LED);
clear32(HW_GPIO_OWNER, GPIO_SLOT_LED);
}
Slippi_Thread = do_thread_create(
SlippiHandlerThread,
((u32 *)&__slippi_stack_addr),
@@ -49,11 +63,30 @@ void SlippiFileWriterInit()
thread_continue(Slippi_Thread);
}
void SlippiFileWriterUpdateRegisters()
{
if (driveTimerSet && TimerDiffMs(driveTimer) >= LED_FLASH_TIME_MS)
{
clear32(HW_GPIO_OUT, GPIO_SLOT_LED);
driveTimerSet = false;
}
}
void SlippiFileWriterShutdown()
{
thread_cancel(Slippi_Thread, 0);
}
void flashLED()
{
driveTimer = read32(HW_TIMER);
if (!driveTimerSet)
{
set32(HW_GPIO_OUT, GPIO_SLOT_LED);
driveTimerSet = true;
}
}
//we cant include time.h so hardcode what we need
struct tm
{
@@ -154,17 +187,12 @@ void completeFile(FIL *file, SlpGameReader *reader, u32 writtenByteCount)
// Write footer
u32 wrote;
u32 res;
f_write(file, footer, writePos, &wrote);
f_sync(file);
f_lseek(file, 11);
f_write(file, &writtenByteCount, 4, &wrote);
res = f_sync(file);
if (res == 0) {
set32(HW_GPIO_OUT, GPIO_SLOT_LED);
driveTimer = read32(HW_TIMER);
}
FRESULT fileWriteResult = f_write(file, &writtenByteCount, 4, &wrote);
f_sync(file);
}
static u32 SlippiHandlerThread(void *arg)
@@ -177,16 +205,50 @@ static u32 SlippiHandlerThread(void *arg)
u32 writtenByteCount = 0;
driveTimer = read32(HW_TIMER);
driveTimerSet = false;
FATFS device;
bool failedToMount = false;
bool hasFile = false;
bool mounted = true;
const bool use_usb = ConfigGetUseUSB() != 1;
while (1)
{
// Cycle time, look at const definition for more info
mdelay(THREAD_CYCLE_TIME_MS);
if (TimerDiffMs(driveTimer) > 1000) {
clear32(HW_GPIO_OUT, GPIO_SLOT_LED);
}
if (use_usb)
{
if (!USBStorage_IsInserted_SlippiThread())
{
if (mounted)
f_mount_char(NULL, "usb:", 1);
// TODO: Ensure connection to USB is correct
failedToMount = false;
hasFile = false;
mounted = false;
continue;
}
else if (!mounted && !failedToMount)
{
if (f_mount_char(&device, "usb:", 1) == FR_OK)
{
// ignore anything already in the buffer. users should not expect to record a
// game if the usb device is inserted after game start.
memReadPos = SlippiRestoreReadPos();
mounted = true;
}
else
{
// only attempt to mount once, user can retry by re-inserting the device.
failedToMount = true;
}
}
if (!mounted)
continue;
}
// Read from memory and write to file
SlpMemError err = SlippiMemoryRead(&reader, readBuf, READ_BUF_SIZE, memReadPos);
@@ -195,7 +257,7 @@ static u32 SlippiHandlerThread(void *arg)
if (err == SLP_READ_OVERFLOW)
memReadPos = SlippiRestoreReadPos();
mdelay(1000);
mdelay(LED_FLASH_TIME_MS + 1000); // we always want LED visibly off if this happens
// For specific errors, bytes will still be read. Not continueing to deal with those
}
@@ -209,27 +271,45 @@ static u32 SlippiHandlerThread(void *arg)
dbgprintf("Creating File...\r\n");
char *fileName = generateFileName(true);
// Need to open with FA_READ if network thread is going to share &currentFile
// Maybe can remove FA_READ since network thread doesn't share &currentFile
FRESULT fileOpenResult = f_open_secondary_drive(&currentFile, fileName, FA_CREATE_ALWAYS | FA_WRITE | FA_READ);
if (fileOpenResult != FR_OK)
{
dbgprintf("Slippi: failed to open file: %s, errno: %d\r\n", fileName, fileOpenResult);
mdelay(1000);
mdelay(LED_FLASH_TIME_MS - THREAD_CYCLE_TIME_MS - 100); // short enough so we can recover with running out of LED time.
continue;
}
if (replaysLED)
flashLED();
// dbgprintf("Bytes written: %d/%d...\r\n", wrote, currentBuffer->len);
hasFile = true;
writtenByteCount = 0;
writeHeader(&currentFile);
}
if (reader.lastReadResult.bytesRead == 0)
{
if (replaysLED)
flashLED();
continue;
}
// dbgprintf("Bytes read: %d\r\n", reader.lastReadResult.bytesRead);
if (!hasFile)
{
// we can reach this state if the user inserts a usb device during a game.
// skip over and don't write anything until we see the start of a new game
if (replaysLED)
flashLED();
memReadPos += reader.lastReadResult.bytesRead;
continue;
}
UINT wrote;
f_write(&currentFile, readBuf, reader.lastReadResult.bytesRead, &wrote);
FRESULT writeResult = f_write(&currentFile, readBuf, reader.lastReadResult.bytesRead, &wrote);
if (replaysLED && writeResult == FR_OK && wrote > 0)
flashLED();
f_sync(&currentFile);
if (wrote == 0)
@@ -244,6 +324,7 @@ static u32 SlippiHandlerThread(void *arg)
dbgprintf("Completing File...\r\n");
completeFile(&currentFile, &reader, writtenByteCount);
f_close(&currentFile);
hasFile = false;
}
}

View File

@@ -4,6 +4,7 @@
#include "global.h"
void SlippiFileWriterInit();
void SlippiFileWriterUpdateRegisters();
void SlippiFileWriterShutdown();
#endif

View File

@@ -12,7 +12,6 @@
#include "string.h"
#include "debug.h"
#include "net.h"
#include "ff_utf8.h"
#include "Config.h"

View File

@@ -18,8 +18,9 @@
#include "common.h"
extern bool access_led;
u32 s_size; // Sector size.
u32 s_cnt; // Sector count.
u32 usb_s_cnt; // USB Sector count.
u32 usb_s_size; // USB Sector size.
u32 sd_s_cnt; // SD Sector count.
/*-----------------------------------------------------------------------*/
@@ -213,7 +214,12 @@ DRESULT disk_ioctl (
)
{
if(cmd == GET_SECTOR_SIZE)
*(WORD*)buff = s_size;
{
if (pdrv == DEV_SD)
*(WORD*)buff = PAGE_SIZE512;
else if (pdrv == DEV_USB)
*(WORD*)buff = usb_s_size;
}
return RES_OK;
}

View File

@@ -36,6 +36,9 @@ __slippi_network_broadcast_stack_addr = __slippi_stack_addr + __slippi_network_b
__slippi_debug_stack_size = 0x400;
__slippi_debug_stack_addr = __slippi_network_broadcast_stack_addr + __slippi_debug_stack_size;
__ven_change_stack_size = 0x400;
__ven_change_stack_addr = __slippi_debug_stack_addr + __ven_change_stack_size;
MEMORY {
code : ORIGIN = 0x12F00000, LENGTH = 0x60000

View File

@@ -58,9 +58,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
extern char __bss_start, __bss_end;
extern char __di_stack_addr, __di_stack_size;
// Reference to SD sector count
extern u32 sd_s_cnt;
// References to USB sector size and sector count
extern u32 s_size;
extern u32 s_cnt;
extern u32 usb_s_size;
extern u32 usb_s_cnt;
// Global state for network connectivity, from kernel/net.c
extern u32 NetworkStarted;
@@ -186,7 +189,7 @@ int _main( int argc, char *argv[] )
*/
BootStatus(STORAGE_INIT, 0, 0);
u32 SlippiFileWrite = ConfigGetConfig(NIN_CFG_SLIPPI_FILE_WRITE);
u32 SlippiFileWrite = ConfigGetConfig(NIN_CFG_SLIPPI_REPLAYS);
u32 UseUSB = ConfigGetUseUSB(); // Returns 0 for SD, 1 for USB
SetDiskFunctions(UseUSB);
@@ -197,8 +200,8 @@ int _main( int argc, char *argv[] )
// Boot up USB if it is being used for writing slp files OR booting a game
if (shouldBootUsb)
{
ret = USBStorage_Startup();
dbgprintf("USB:Drive size: %dMB SectorSize:%d\r\n", s_cnt / 1024 * s_size / 1024, s_size);
ret = USBStorage_Startup(!UseUSB);
dbgprintf("USB:Drive size: %dMB SectorSize:%d\r\n", usb_s_cnt / 1024 * usb_s_size / 1024, usb_s_size);
if(ret != 1)
{
dbgprintf("USB Device Init failed:%d\r\n", ret );
@@ -211,7 +214,6 @@ int _main( int argc, char *argv[] )
// Boot up SD if it is being used to boot a game
if (shouldBootSd)
{
s_size = PAGE_SIZE512; //manually set s_size
ret = SDHCInit();
if(ret != 1)
{
@@ -239,7 +241,7 @@ int _main( int argc, char *argv[] )
res = f_mount( devices[0], fatSdName, 1 );
if( res != FR_OK )
{
dbgprintf("ES:f_mount() failed:%d\r\n", res );
dbgprintf("SD ES:f_mount() failed:%d\r\n", res );
BootStatusError(-3, res);
mdelay(4000);
Shutdown();
@@ -253,7 +255,7 @@ int _main( int argc, char *argv[] )
res = f_mount( devices[1], fatUsbName, 1 );
if( res != FR_OK )
{
dbgprintf("ES:f_mount() failed:%d\r\n", res );
dbgprintf("USB ES:f_mount() failed:%d\r\n", res );
BootStatusError(-3, res);
mdelay(4000);
Shutdown();
@@ -298,8 +300,11 @@ int _main( int argc, char *argv[] )
break;
}
if(!UseUSB) //Use FAT values for SD
s_cnt = devices[0]->n_fatent * devices[0]->csize;
if(shouldBootSd) //Use FAT values for SD
sd_s_cnt = devices[0]->n_fatent * devices[0]->csize;
u32 s_size = UseUSB ? usb_s_size : PAGE_SIZE512;
u32 s_cnt = UseUSB ? usb_s_cnt : sd_s_cnt;
/* NETWORK_INIT BOOT STAGE.
* Initialize the MAC address before initializing networking or file writing.
@@ -328,7 +333,8 @@ int _main( int argc, char *argv[] )
BootStatus(CONFIG_INIT, s_size, s_cnt);
ConfigInit();
access_led = ConfigGetConfig(NIN_CFG_LED);
bool slippi_replays_led = ConfigGetConfig(NIN_CFG_SLIPPI_REPLAYS) && ConfigGetReplaysLED() == 0;
access_led = ConfigGetConfig(NIN_CFG_LED) && !slippi_replays_led;
if (ConfigGetConfig(NIN_CFG_SLIPPI_PORT_A))
slippi_use_port_a = 1;
@@ -399,8 +405,6 @@ int _main( int argc, char *argv[] )
PatchInit();
SlippiMemoryInit();
// If we are using USB for writting slp files and USB is not the
// primary device, initialize the file writer
if (SlippiFileWrite == 1)
SlippiFileWriterInit();
@@ -583,6 +587,16 @@ int _main( int argc, char *argv[] )
BTUpdateRegisters();
HIDUpdateRegisters(0);
if (SlippiFileWrite == 1)
{
SlippiFileWriterUpdateRegisters();
if (!UseUSB)
{
// Must consistently call to enable USB hotswap
USBStorage_UpdateRegisters_MainThread();
}
}
// Native SI is always enabled in Slippi Nintendont
//if (DisableSIPatch == 0) SIUpdateRegisters();
@@ -687,7 +701,7 @@ int _main( int argc, char *argv[] )
}
// make sure drive led is off before quitting
if( access_led ) clear32(HW_GPIO_OUT, GPIO_SLOT_LED);
if( access_led || slippi_replays_led ) clear32(HW_GPIO_OUT, GPIO_SLOT_LED);
// make sure we set that back to the original
write32(HW_PPCSPEED, ori_ppcspeed);

View File

@@ -42,10 +42,8 @@ distribution.
#include "vsprintf.h"
#define USBV5_IOCTL_GETVERSION 0 // should return 0x50001
#define USBV5_IOCTL_GETDEVICECHANGE 1
#define USBV5_IOCTL_SHUTDOWN 2
#define USBV5_IOCTL_GETDEVPARAMS 3
#define USBV5_IOCTL_ATTACHFINISH 6
#define USBV5_IOCTL_SETALTERNATE 7
#define USBV5_IOCTL_SUSPEND_RESUME 16
#define USBV5_IOCTL_CANCELENDPOINT 17
@@ -122,7 +120,7 @@ s32 USB_Initialize()
if (ven_fd < 0)
ven_fd = IOS_Open(__ven_path, IPC_OPEN_NONE);
return IPC_OK;
return ven_fd;
}
s32 USB_Deinitialize()

View File

@@ -91,6 +91,11 @@
#define USB_MAX_DEVICES 32
#define USBV5_IOCTL_GETDEVICECHANGE 1
#define USBV5_IOCTL_GETDEVPARAMS 3
#define USBV5_IOCTL_ATTACHFINISH 6
#define USBV5_IOCTL_SUSPEND_RESUME 16
typedef struct _usb_device_entry {
s32 device_id;
u16 vid;

View File

@@ -81,143 +81,66 @@ distribution.
#define DEVLIST_MAXSIZE 8
extern u32 s_size, s_cnt;
#define GETDEVPARAMS_DESC_OFFSET 20
#define GETDEVPARAMS_OUT_SIZE 0xC0
static bool __inited = false;
static bool __mounted = false;
static u8 __lun = 0;
static u8 __ep_in = 0;
static u8 __ep_out = 0;
static u16 __vid = 0;
static u16 __pid = 0;
static u32 __tag = 0;
static u32 __interface = 0;
static s32 __usb_fd = -1;
static u8 *cbw_buffer = NULL;
static u8 *transferbuffer = NULL;
static s32 __usbstorage_reset();
static s32 __send_cbw(u8 lun, u32 len, u8 flags, const u8 *cb, u8 cbLen)
typedef struct _usbendpointdesc
{
s32 retval = USBSTORAGE_OK;
u8 bLength;
u8 bDescriptorType;
u8 bEndpointAddress;
u8 bmAttributes;
u16 wMaxPacketSize;
u8 bInterval;
} __attribute__((packed)) usb_endpointdesc;
if(cbLen == 0 || cbLen > 16)
return IPC_EINVAL;
write32(((u32)cbw_buffer),bswap32(CBW_SIGNATURE));
write32(((u32)cbw_buffer)+4,bswap32(++__tag));
write32(((u32)cbw_buffer)+8,bswap32(len));
cbw_buffer[12] = flags;
cbw_buffer[13] = lun;
cbw_buffer[14] = (cbLen > 6 ? 10 : 6);
memcpy(cbw_buffer + 15, cb, cbLen);
retval = USB_WriteBlkMsg(__usb_fd, __ep_out, CBW_SIZE, (void *)cbw_buffer);
if(retval == CBW_SIZE) return USBSTORAGE_OK;
else if(retval > 0) return USBSTORAGE_ESHORTWRITE;
return retval;
}
static s32 __read_csw(u8 *status, u32 *dataResidue)
typedef struct _usbinterfacedesc
{
s32 retval = USBSTORAGE_OK;
u32 signature, tag, _dataResidue, _status;
u8 bLength;
u8 bDescriptorType;
u8 bInterfaceNumber;
u8 bAlternateSetting;
u8 bNumEndpoints;
u8 bInterfaceClass;
u8 bInterfaceSubClass;
u8 bInterfaceProtocol;
u8 iInterface;
u8 *extra;
u16 extra_size;
struct _usbendpointdesc *endpoints;
} __attribute__((packed)) usb_interfacedesc;
retval = USB_WriteBlkMsg(__usb_fd, __ep_in, CSW_SIZE, cbw_buffer);
if(retval > 0 && retval != CSW_SIZE) return USBSTORAGE_ESHORTREAD;
else if(retval < 0) return retval;
signature = bswap32(read32(((u32)cbw_buffer)));
tag = bswap32(read32(((u32)cbw_buffer)+4));
_dataResidue = bswap32(read32(((u32)cbw_buffer)+8));
_status = cbw_buffer[12];
if(signature != CSW_SIGNATURE) return USBSTORAGE_ESIGNATURE;
if(dataResidue != NULL)
*dataResidue = _dataResidue;
if(status != NULL)
*status = _status;
if(tag != __tag) return USBSTORAGE_ETAG;
return USBSTORAGE_OK;
}
static s32 __cycle(u8 lun, u8 *buffer, u32 len, u8 *cb, u8 cbLen, u8 write, u8 *_status, u32 *_dataResidue)
typedef struct _usbconfdesc
{
s32 retval = USBSTORAGE_OK;
u8 bLength;
u8 bDescriptorType;
u16 wTotalLength;
u8 bNumInterfaces;
u8 bConfigurationValue;
u8 iConfiguration;
u8 bmAttributes;
u8 bMaxPower;
struct _usbinterfacedesc *interfaces;
} __attribute__((packed)) usb_configurationdesc;
u8 status=0;
u32 dataResidue = 0;
u32 max_size = MAX_TRANSFER_SIZE_V5;
u8 ep = write ? __ep_out : __ep_in;
s8 retries = USBSTORAGE_CYCLE_RETRIES + 1;
do
{
u8 *_buffer = buffer;
u32 _len = len;
retries--;
if(retval == USBSTORAGE_ETIMEDOUT)
break;
retval = __send_cbw(lun, len, (write ? CBW_OUT:CBW_IN), cb, cbLen);
while(_len > 0 && retval >= 0)
{
u32 thisLen = _len > max_size ? max_size : _len;
if ((u32)_buffer&0x1F || !((u32)_buffer&0x10000000))
{
if(write) memcpy(transferbuffer, _buffer, thisLen);
retval = USB_WriteBlkMsg(__usb_fd, ep, thisLen, transferbuffer);
if (!write && retval > 0) memcpy(_buffer, transferbuffer, retval);
}
else
retval = USB_WriteBlkMsg(__usb_fd, ep, thisLen, _buffer);
if (retval == thisLen)
{
_len -= retval;
_buffer += retval;
}
else if (retval != USBSTORAGE_ETIMEDOUT)
retval = USBSTORAGE_EDATARESIDUE;
}
if (retval >= 0)
retval = __read_csw(&status, &dataResidue);
if (retval < 0) {
if (__usbstorage_reset() == USBSTORAGE_ETIMEDOUT)
retval = USBSTORAGE_ETIMEDOUT;
}
} while (retval < 0 && retries > 0);
if(_status != NULL)
*_status = status;
if(_dataResidue != NULL)
*_dataResidue = dataResidue;
return retval;
}
static s32 __usbstorage_reset()
typedef struct _usbdevdesc
{
s32 retval = USB_WriteCtrlMsg(__usb_fd, (USB_CTRLTYPE_DIR_HOST2DEVICE | USB_CTRLTYPE_TYPE_CLASS | USB_CTRLTYPE_REC_INTERFACE), USBSTORAGE_RESET, 0, __interface, 0, NULL);
udelay(60*1000);
USB_ClearHalt(__usb_fd, __ep_in);udelay(10000); //from http://www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf
USB_ClearHalt(__usb_fd, __ep_out);udelay(10000);
return retval;
}
u8 bLength;
u8 bDescriptorType;
u16 bcdUSB;
u8 bDeviceClass;
u8 bDeviceSubClass;
u8 bDeviceProtocol;
u8 bMaxPacketSize0;
u16 idVendor;
u16 idProduct;
u16 bcdDevice;
u8 iManufacturer;
u8 iProduct;
u8 iSerialNumber;
u8 bNumConfigurations;
struct _usbconfdesc *configurations;
} __attribute__((packed)) usb_devdesc;
typedef struct
{
@@ -235,35 +158,200 @@ typedef struct
u8 ep_out;
} important_storage_data;
extern u32 usb_s_size, usb_s_cnt;
static bool __inited = false;
static bool __mounted = false;
static important_storage_data __mounted_device;
static u8 *cbw_buffer = NULL;
static u8 *transferbuffer = NULL;
static s32 ven_fd = -1;
static usb_device_entry AttachedDevices[32] ALIGNED(32);
static struct ipcmessage *venchangemsg = NULL;
static u32 venchange_thread = 0;
static u8 *venchangeheap = NULL;
static s32 venchangequeue = -1;
extern char __ven_change_stack_addr, __ven_change_stack_size;
static bool __ioctl_running = false;
static bool __main_thread_dirty = false;
static bool __slippi_thread_dirty = false;
static s32 __usbstorage_reset(important_storage_data *dev);
static s32 __send_cbw(important_storage_data *dev, u8 lun, u32 len, u8 flags, const u8 *cb, u8 cbLen)
{
s32 retval = USBSTORAGE_OK;
if(cbLen == 0 || cbLen > 16)
return IPC_EINVAL;
memset(cbw_buffer, 0, CBW_SIZE);
write32(((u32)cbw_buffer),bswap32(CBW_SIGNATURE));
write32(((u32)cbw_buffer)+4,bswap32(++dev->tag));
write32(((u32)cbw_buffer)+8,bswap32(len));
cbw_buffer[12] = flags;
cbw_buffer[13] = lun;
cbw_buffer[14] = (cbLen > 6 ? 10 : 6);
memcpy(cbw_buffer + 15, cb, cbLen);
retval = USB_WriteBlkMsg(dev->usb_fd, dev->ep_out, CBW_SIZE, (void *)cbw_buffer);
if(retval == CBW_SIZE) return USBSTORAGE_OK;
else if(retval > 0) return USBSTORAGE_ESHORTWRITE;
return retval;
}
static s32 __read_csw(important_storage_data *dev, u8 *status, u32 *dataResidue)
{
s32 retval = USBSTORAGE_OK;
u32 signature, tag, _dataResidue, _status;
retval = USB_WriteBlkMsg(dev->usb_fd, dev->ep_in, CSW_SIZE, cbw_buffer);
if(retval > 0 && retval != CSW_SIZE) return USBSTORAGE_ESHORTREAD;
else if(retval < 0) return retval;
signature = bswap32(read32(((u32)cbw_buffer)));
tag = bswap32(read32(((u32)cbw_buffer)+4));
_dataResidue = bswap32(read32(((u32)cbw_buffer)+8));
_status = cbw_buffer[12];
if(signature != CSW_SIGNATURE) return USBSTORAGE_ESIGNATURE;
if(dataResidue != NULL)
*dataResidue = _dataResidue;
if(status != NULL)
*status = _status;
if(tag != dev->tag) return USBSTORAGE_ETAG;
return USBSTORAGE_OK;
}
static s32 __cycle(important_storage_data *dev, u8 lun, u8 *buffer, u32 len, u8 *cb, u8 cbLen, u8 write, u8 *_status, u32 *_dataResidue)
{
s32 retval = USBSTORAGE_OK;
u8 status=0;
u32 dataResidue = 0;
u32 max_size = MAX_TRANSFER_SIZE_V5;
u8 ep = write ? dev->ep_out : dev->ep_in;
s8 retries = USBSTORAGE_CYCLE_RETRIES + 1;
do
{
u8 *_buffer = buffer;
u32 _len = len;
retries--;
if(retval == USBSTORAGE_ETIMEDOUT)
break;
retval = __send_cbw(dev, lun, len, (write ? CBW_OUT:CBW_IN), cb, cbLen);
while(_len > 0 && retval >= 0)
{
u32 thisLen = _len > max_size ? max_size : _len;
if ((u32)_buffer&0x1F || !((u32)_buffer&0x10000000))
{
if(write) memcpy(transferbuffer, _buffer, thisLen);
retval = USB_WriteBlkMsg(dev->usb_fd, ep, thisLen, transferbuffer);
if (!write && retval > 0) memcpy(_buffer, transferbuffer, retval);
}
else
retval = USB_WriteBlkMsg(dev->usb_fd, ep, thisLen, _buffer);
if (retval == thisLen)
{
_len -= retval;
_buffer += retval;
}
else if (retval != USBSTORAGE_ETIMEDOUT)
retval = USBSTORAGE_EDATARESIDUE;
}
if (retval >= 0)
retval = __read_csw(dev, &status, &dataResidue);
if (retval < 0) {
if (__usbstorage_reset(dev) == USBSTORAGE_ETIMEDOUT)
retval = USBSTORAGE_ETIMEDOUT;
}
} while (retval < 0 && retries > 0);
if(_status != NULL)
*_status = status;
if(_dataResidue != NULL)
*_dataResidue = dataResidue;
return retval;
}
static s32 __usbstorage_reset(important_storage_data *dev)
{
u8 bmRequestType = USB_CTRLTYPE_DIR_HOST2DEVICE | USB_CTRLTYPE_TYPE_CLASS | USB_CTRLTYPE_REC_INTERFACE;
s32 retval = USB_WriteCtrlMsg(dev->usb_fd, bmRequestType, USBSTORAGE_RESET, 0, dev->interface, 0, NULL);
udelay(60*1000);
USB_ClearHalt(dev->usb_fd, dev->ep_in);udelay(10000); //from http://www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf
USB_ClearHalt(dev->usb_fd, dev->ep_out);udelay(10000);
return retval;
}
void USBStorage_Open()
{
sync_before_read((void*)0x132C1000, sizeof(important_storage_data));
important_storage_data *d = (important_storage_data*)0x132C1000;
s_size = d->sector_size;
s_cnt = d->sector_count;
__mounted_device.sector_size = d->sector_size;
__mounted_device.sector_count = d->sector_count;
__mounted_device.lun = d->lun;
__mounted_device.vid = d->vid;
__mounted_device.pid = d->pid;
__mounted_device.tag = d->tag;
__mounted_device.interface = d->interface;
__mounted_device.usb_fd = d->usb_fd;
__mounted_device.ep_in = d->ep_in;
__mounted_device.ep_out = d->ep_out;
__lun = d->lun;
__vid = d->vid;
__pid = d->pid;
__tag = d->tag;
__interface = d->interface;
__usb_fd = d->usb_fd;
__ep_in = d->ep_in;
__ep_out = d->ep_out;
usb_s_size = __mounted_device.sector_size;
usb_s_cnt = __mounted_device.sector_count;
__mounted = true;
if(transferbuffer == NULL)
transferbuffer = (u8*)malloca(MAX_TRANSFER_SIZE_V5, 32);
__mounted = true;
}
bool USBStorage_Startup(void)
static u32 __ven_change_thread()
{
struct ipcmessage *msg = NULL;
while(1)
{
mqueue_recv(venchangequeue, &msg, 0);
mqueue_ack(msg, 0);
__ioctl_running = false;
// order actually matters here for thread safety
__slippi_thread_dirty = true;
__main_thread_dirty = true;
}
return 0;
}
bool USBStorage_Startup(bool hotswap)
{
if(__inited)
return true;
if(USB_Initialize() < 0)
ven_fd = USB_Initialize();
if(ven_fd < 0)
return false;
if(cbw_buffer == NULL)
@@ -271,6 +359,16 @@ bool USBStorage_Startup(void)
USBStorage_Open();
if (hotswap)
{
memset32(AttachedDevices, 0, sizeof(usb_device_entry)*32);
venchangeheap = (u8*)malloca(32,32);
venchangequeue = mqueue_create(venchangeheap, 1);
venchangemsg = (struct ipcmessage*)malloca(sizeof(struct ipcmessage), 32);
venchange_thread = do_thread_create(__ven_change_thread, ((u32*)&__ven_change_stack_addr), ((u32)(&__ven_change_stack_size)), 0x78);
thread_continue(venchange_thread);
}
__inited = true;
return __inited;
}
@@ -284,7 +382,7 @@ bool USBStorage_ReadSectors(u32 sector, u32 numSectors, void *buffer)
s32 retval;
u8 cmd[] = {
SCSI_READ_10,
__lun << 5,
__mounted_device.lun << 5,
sector >> 24,
sector >> 16,
sector >> 8,
@@ -295,7 +393,7 @@ bool USBStorage_ReadSectors(u32 sector, u32 numSectors, void *buffer)
0
};
retval = __cycle(__lun, buffer, numSectors * s_size, cmd, sizeof(cmd), 0, &status, NULL);
retval = __cycle(&__mounted_device, __mounted_device.lun, buffer, __mounted_device.sector_size, cmd, sizeof(cmd), 0, &status, NULL);
if(retval > 0 && status != 0)
retval = USBSTORAGE_ESTATUS;
@@ -311,7 +409,7 @@ bool USBStorage_WriteSectors(u32 sector, u32 numSectors, const void *buffer)
s32 retval;
u8 cmd[] = {
SCSI_WRITE_10,
__lun << 5,
__mounted_device.lun << 5,
sector >> 24,
sector >> 16,
sector >> 8,
@@ -322,7 +420,7 @@ bool USBStorage_WriteSectors(u32 sector, u32 numSectors, const void *buffer)
0
};
retval = __cycle(__lun, (u8*)buffer, numSectors * s_size, cmd, sizeof(cmd), 1, &status, NULL);
retval = __cycle(&__mounted_device, __mounted_device.lun, (u8*)buffer, numSectors * __mounted_device.sector_size, cmd, sizeof(cmd), 1, &status, NULL);
if(retval > 0 && status != 0)
retval = USBSTORAGE_ESTATUS;
@@ -331,17 +429,16 @@ bool USBStorage_WriteSectors(u32 sector, u32 numSectors, const void *buffer)
void USBStorage_Close()
{
__mounted = false;
__lun = 0;
__vid = 0;
__pid = 0;
__mounted_device.lun = 0;
__mounted_device.vid = 0;
__mounted_device.pid = 0;
if(transferbuffer != NULL)
{
free(transferbuffer);
transferbuffer = NULL;
}
__usb_fd = -1;
__mounted = false;
}
void USBStorage_Shutdown(void)
@@ -349,7 +446,7 @@ void USBStorage_Shutdown(void)
if(__inited == false)
return;
if (__vid != 0 || __pid != 0)
if (__mounted_device.vid != 0 || __mounted_device.pid != 0)
USBStorage_Close();
USB_Deinitialize();
@@ -361,3 +458,245 @@ void USBStorage_Shutdown(void)
}
__inited = false;
}
// see libogc/usb.c: __find_next_endpoint
static u32 __find_next_endpoint(u8 *buffer,s32 size,u8 align)
{
u8 *ptr = buffer;
while(size>2 && buffer[0]) { // abort if buffer[0]==0 to avoid getting stuck
if(buffer[1]==USB_DT_ENDPOINT || buffer[1]==USB_DT_INTERFACE)
break;
size -= (buffer[0]+align)&~align;
buffer += (buffer[0]+align)&~align;
}
return (buffer - ptr);
}
static bool __setValidLun(important_storage_data *dev, int max_lun)
{
s32 retval;
int lun;
// max_lun is the maximum LUN index, not the number of LUNs
for (lun = 0; lun <= max_lun; lun++)
{
udelay(50);
// see libogc/usbstorage.c: __usbstorage_clearerrors
u8 test_cmd[] = {SCSI_TEST_UNIT_READY, 0, 0, 0, 0, 0};
retval = __cycle(dev, lun, NULL, 0, test_cmd, 6, 0, NULL, NULL);
if (retval < 0)
continue;
u8 sense_cmd[] = {SCSI_REQUEST_SENSE, lun << 5, 0, 0, SCSI_SENSE_REPLY_SIZE, 0};
u8 sense_response[SCSI_SENSE_REPLY_SIZE];
memset(sense_response, 0, SCSI_SENSE_REPLY_SIZE);
retval = __cycle(dev, lun, sense_response, SCSI_SENSE_REPLY_SIZE, sense_cmd, 6, 0, NULL, NULL);
if (retval < 0)
continue;
u8 sense_key = sense_response[2] & 0xF;
if (sense_key == SCSI_SENSE_NOT_READY || sense_key == SCSI_SENSE_MEDIUM_ERROR || sense_key == SCSI_SENSE_HARDWARE_ERROR)
continue;
// see libogc/usbstorage.c: USBStorage_Inquiry
u8 inquiry_cmd[] = {SCSI_INQUIRY, lun << 5,0,0,36,0};
u8 inquiry_response[36];
int j;
for (j = 0; j < 2; j++)
{
memset(inquiry_response, 0, 36);
retval = __cycle(dev, lun, inquiry_response, 36, inquiry_cmd, 6, 0, NULL, NULL);
if (retval >= 0) break;
}
// see libogc/usbstorage.c: USBStorage_ReadCapacity
u8 read_capacity_cmd[10] = {SCSI_READ_CAPACITY, lun << 5, 0, 0, 0, 0, 0, 0, 0, 0};
u32 read_capacity_response[2];
memset(read_capacity_response, 0, 8);
retval = __cycle(dev, lun, (u8*)read_capacity_response, 8, read_capacity_cmd, 10, 0, NULL, NULL);
if (retval >= 0 && read_capacity_response[0] > 0 && read_capacity_response[1] >= 512)
{
dev->sector_count = read_capacity_response[0];
dev->sector_size = read_capacity_response[1];
dev->lun = lun;
return true;
}
}
return false;
}
// see libogc/usbstorage.c: __usbstorage_IsInserted
bool __has_device_after_change()
{
int i;
u32 num_attached_devices = venchangemsg->result;
if (num_attached_devices == 0)
{
__mounted = false;
return false;
}
// If already have a device, return if it's still present
if (__mounted)
{
for (i = 0; i < num_attached_devices; i++)
{
if (AttachedDevices[i].device_id == __mounted_device.usb_fd) {
udelay(50);
return true;
}
}
__mounted = false;
}
s32 suspend_resume_buf[8] ALIGNED(32);
memset(suspend_resume_buf, 0, sizeof(s32) * 8);
u32 get_dev_params_in[8] ALIGNED(32);
memset(get_dev_params_in, 0, sizeof(u32) * 8);
u8 get_dev_params_out[GETDEVPARAMS_OUT_SIZE] ALIGNED(32);
usb_devdesc *udd = NULL;
usb_configurationdesc *ucd = NULL;
usb_interfacedesc *uid = NULL;
usb_endpointdesc *ued = NULL;
for (i = 0; i < num_attached_devices; i++)
{
// known device USB LAN
if (AttachedDevices[i].vid == 0x0b95 && AttachedDevices[i].pid == 0x7720)
continue;
// dbgprintf("USBStorage: fd: %d, vid: 0x%04X, pid: 0x%04X\n", AttachedDevices[i].device_id, AttachedDevices[i].vid, AttachedDevices[i].pid);
// see libogc/usb.c: USBV5_SuspendResume
suspend_resume_buf[0] = AttachedDevices[i].device_id;
suspend_resume_buf[2] = 1;
IOS_Ioctl(ven_fd, USBV5_IOCTL_SUSPEND_RESUME, suspend_resume_buf, 32, NULL, 0);
// see libogc/usb.c: USBV5_GetDescriptors
get_dev_params_in[0] = AttachedDevices[i].device_id;
get_dev_params_in[2] = 0;
memset(get_dev_params_out, 0, GETDEVPARAMS_OUT_SIZE);
s32 retval = IOS_Ioctl(ven_fd, USBV5_IOCTL_GETDEVPARAMS, get_dev_params_in, 32, get_dev_params_out, GETDEVPARAMS_OUT_SIZE);
if (retval == IPC_OK)
{
u8 *next = get_dev_params_out + GETDEVPARAMS_DESC_OFFSET;
udd = (usb_devdesc*)next;
next += (udd->bLength+3)&~3;
// "very few devices have more than 1 configuration" - https://www.beyondlogic.org/usbnutshell/usb5.shtml
// also, GETDEVPARAMS_OUT_SIZE 0xC0 is sized exactly for 1 configuration with 1 interface with up to 16 endpoints
// so assume only one configuration
ucd = (usb_configurationdesc*)next;
next += (ucd->bLength+3)&~3;
if (ucd->bNumInterfaces == 0)
continue;
// "IOS presents each interface as a different device" - libogc/usb.c
// so assume only one interface
uid = (usb_interfacedesc*)next;
next += (uid->bLength+3)&~3;
// dbgprintf("USBStorage: bInterfaceClass: 0x%02X, bInterfaceProtocol: 0x%02X, bNumEndpoints: %d\n", uid->bInterfaceClass, uid->bInterfaceProtocol, uid->bNumEndpoints);
if (uid->bInterfaceClass == USB_CLASS_MASS_STORAGE && uid->bInterfaceProtocol == MASS_STORAGE_BULK_ONLY && uid->bNumEndpoints >= 2)
{
u16 extra_size = __find_next_endpoint(next, get_dev_params_out + GETDEVPARAMS_OUT_SIZE - next, 3);
if (extra_size > 0)
next += extra_size;
u8 endpoint_in = 0;
u8 endpoint_out = 0;
int iEndpoint;
for (iEndpoint = 0; iEndpoint < uid->bNumEndpoints; iEndpoint++)
{
ued = (usb_endpointdesc*)next;
next += (ued->bLength+3)&~3;
if (ued->bmAttributes != USB_ENDPOINT_BULK)
continue;
if (ued->bEndpointAddress & USB_ENDPOINT_IN)
endpoint_in = ued->bEndpointAddress;
else
endpoint_out = ued->bEndpointAddress;
}
if (endpoint_in != 0 && endpoint_out != 0)
{
important_storage_data new_device;
new_device.vid = AttachedDevices[i].vid;
new_device.pid = AttachedDevices[i].pid;
new_device.tag = TAG_START;
new_device.interface = uid->bInterfaceNumber;
new_device.usb_fd = AttachedDevices[i].device_id;
new_device.ep_in = endpoint_in;
new_device.ep_out = endpoint_out;
// Even though (we assume) the device has only one configuration, we need to explicitly select it.
u8 bmRequestType = USB_CTRLTYPE_DIR_HOST2DEVICE | USB_CTRLTYPE_TYPE_STANDARD | USB_CTRLTYPE_REC_DEVICE;
retval = USB_WriteCtrlMsg(new_device.usb_fd, bmRequestType, USB_REQ_SETCONFIG, ucd->bConfigurationValue, 0, 0, NULL);
bmRequestType = USB_CTRLTYPE_DIR_DEVICE2HOST | USB_CTRLTYPE_TYPE_CLASS | USB_CTRLTYPE_REC_INTERFACE;
u8 max_lun = 0;
retval = USB_ReadCtrlMsg(new_device.usb_fd, bmRequestType, USBSTORAGE_GET_MAX_LUN, 0, new_device.interface, 1, &max_lun);
if (__setValidLun(&new_device, max_lun))
{
memcpy(&__mounted_device, &new_device, sizeof(important_storage_data));
usb_s_size = __mounted_device.sector_size;
usb_s_cnt = __mounted_device.sector_count;
__mounted = true;
/*
dbgprintf(
"USBStorage: sector_count: %d, sector_size: %d, lun: %d, ep_out: 0x%02X, ep_in: 0x%02X, interface: %d\n",
__mounted_device.sector_count,
__mounted_device.sector_size,
__mounted_device.lun,
__mounted_device.ep_out,
__mounted_device.ep_in,
__mounted_device.interface);
*/
udelay(10000);
return true;
}
}
}
}
}
return false;
}
// Call periodically from only the main thread
void USBStorage_UpdateRegisters_MainThread(void)
{
if (__main_thread_dirty)
{
IOS_Ioctl(ven_fd, USBV5_IOCTL_ATTACHFINISH, NULL, 0, NULL, 0);
__main_thread_dirty = false;
}
if (!__main_thread_dirty && !__slippi_thread_dirty && !__ioctl_running)
{
IOS_IoctlAsync(ven_fd, USBV5_IOCTL_GETDEVICECHANGE, NULL, 0, AttachedDevices, 0x180, venchangequeue, venchangemsg);
__ioctl_running = true;
}
}
// Call periodically from only the slippi thread
bool USBStorage_IsInserted_SlippiThread(void)
{
if (__slippi_thread_dirty)
{
bool retval = __has_device_after_change();
// if (!retval) dbgprintf("USBStorage: device removed, fd: %d\n", __mounted_device.usb_fd);
__slippi_thread_dirty = false;
return retval;
}
return __mounted;
}

View File

@@ -26,9 +26,12 @@ typedef struct {
size_t data_length;
} raw_device_command;
bool USBStorage_Startup(void);
bool USBStorage_Startup(bool hotswap);
bool USBStorage_ReadSectors(u32 sector, u32 numSectors, void *buffer);
bool USBStorage_WriteSectors(u32 sector, u32 numSectors, const void *buffer);
void USBStorage_Shutdown(void);
void USBStorage_UpdateRegisters_MainThread(void);
bool USBStorage_IsInserted_SlippiThread(void);
#endif /* __USBSTORAGE_H__ */

View File

@@ -322,15 +322,26 @@ bool LoadNinCFG(void)
f_close(&cfg);
switch( ncfg->Version ) {
case 2:
if (BytesRead != 540)
case 8:
if (BytesRead != 292) // 1.2
ConfigLoaded = false;
break;
default:
if (BytesRead != sizeof(NIN_CFG))
case 9:
if (BytesRead != 292) // 1.4, 1.5, 1.6, 1.8
ConfigLoaded = false;
break;
case 0xC:
if (BytesRead != 316 && // 1.9.0, 1.9.1, 1.9.2
BytesRead != 320) // 1.9.3, 1.9.4, 1.10.1, 1.10.2, 1.11.0, 1.11.1
ConfigLoaded = false;
break;
case 0xD:
if (BytesRead != sizeof(NIN_CFG)) // 324
ConfigLoaded = false;
break;
default:
ConfigLoaded = false;
break;
}
if (ncfg->Magicbytes != 0x01070CF6)
@@ -430,42 +441,6 @@ bool IsGCGame(u8 *Buffer)
void UpdateNinCFG()
{
if (ncfg->Version == 2)
{ //251 blocks, used to be there
ncfg->Unused = 0x2;
ncfg->Version = 3;
}
if (ncfg->Version == 3)
{ //new memcard setting space
ncfg->MemCardBlocks = ncfg->Unused;
ncfg->VideoScale = 0;
ncfg->VideoOffset = 0;
ncfg->Version = 4;
}
if (ncfg->Version == 4)
{ //Option got changed so not confuse it
ncfg->Version = 5;
}
if (ncfg->Version == 5)
{ //New Video Mode option
ncfg->VideoMode &= ~NIN_VID_PATCH_PAL50;
ncfg->Version = 6;
}
if (ncfg->Version == 6)
{ //New flag, disabled by default
ncfg->Version = 7;
}
if (ncfg->Version == 7)
{ // Wiimote CC Rumble, disabled by default;
// don't skip IPL by default.
//ncfg->Config &= ~NIN_CFG_CC_RUMBLE;
ncfg->Config &= ~NIN_CFG_SKIP_IPL;
// Use Slippi on port B by default
ncfg->Config &= ~NIN_CFG_SLIPPI_PORT_A;
ncfg->Version = 8;
}
if (ncfg->Version == 8)
{
// shift bits at indicies 9 - 16 by 2 to make room
@@ -476,6 +451,24 @@ void UpdateNinCFG()
ncfg->Version = 9;
}
if (ncfg->Version == 9)
{
switch (ncfg->MeleeCodeOptions[0])
{
case 1: // UCF
case 2: // Toggle
ncfg->MeleeCodeOptions[0] = 2; // UCF
break;
default:
ncfg->MeleeCodeOptions[0] = 1; // Off
}
ncfg->Version = 0xC;
}
if (ncfg->Version == 0xC)
{
// 0 defaults are fine
ncfg->Version = 0xD;
}
}
int CreateNewFile(const char *Path, unsigned int size)

View File

@@ -1091,7 +1091,7 @@ int main(int argc, char **argv)
PrintFormat(DEFAULT_SIZE, BLACK, MENU_POS_X, MENU_POS_Y + 20*9, "Mounting USB/SD device...");
if(abs(STATUS_LOADING) > STORAGE_MOUNT && abs(STATUS_LOADING) < 20)
PrintFormat(DEFAULT_SIZE, BLACK, MENU_POS_X, MENU_POS_Y + 20*9, "Mounting USB/SD device... Done!");
if(STATUS_LOADING == -3 && (ncfg->Config & (NIN_CFG_SLIPPI_FILE_WRITE))) {
if(STATUS_LOADING == -3 && (ncfg->Config & (NIN_CFG_SLIPPI_REPLAYS))) {
// Special error message when file writes are enabled
PrintFormat(DEFAULT_SIZE, MAROON, MENU_POS_X, MENU_POS_Y + 20*9, "SLP File Writing requires BOTH SD and USB devices");
PrintFormat(DEFAULT_SIZE, MAROON, MENU_POS_X, MENU_POS_Y + 20*10, "Plug devices into both slots and try again");

View File

@@ -168,13 +168,21 @@ static const char *desc_networking[] = {
"in order to use this feature.",
NULL
};
static const char *desc_slippi_file_write[] = {
static const char *desc_slippi_replays[] = {
"Write Slippi replays to a",
"secondary storage device.",
"Requires a USB AND SD device",
"to be plugged in.",
NULL
};
static const char *desc_slippi_replays_led[] = {
"Use the Disc Drive LED to",
"indicate replay device status.",
"The LED will remain on while",
"the replay device is inserted",
"and working properly.",
NULL
};
static const char *desc_slippi_port_a[] = {
"When enabled, emulate Slippi",
"on Port A instead of Port B.",
@@ -808,17 +816,17 @@ static void Menu_GameSelection_Redraw(MenuCtx *ctx)
{
gamelist_y += 20;
PrintFormat(DEFAULT_SIZE, BLACK, MENU_POS_X+340, gamelist_y, "[Slippi] ");
PrintFormat(DEFAULT_SIZE, BLACK, MENU_POS_X+340+(9*10), gamelist_y, "NET: ");
PrintFormat(DEFAULT_SIZE, BLACK, MENU_POS_X+320, gamelist_y, "[Slippi] ");
PrintFormat(DEFAULT_SIZE, BLACK, MENU_POS_X+320+(9*10), gamelist_y, "NET: ");
PrintFormat(DEFAULT_SIZE, (ncfg->Config & (NIN_CFG_NETWORK)) ? GREEN : RED,
MENU_POS_X+340+(13*10), gamelist_y, "%-3s", (ncfg->Config & (NIN_CFG_NETWORK)) ? "ON" : "OFF");
MENU_POS_X+320+(13*10), gamelist_y, "%-3s", (ncfg->Config & (NIN_CFG_NETWORK)) ? "ON" : "OFF");
PrintFormat(DEFAULT_SIZE, BLACK, MENU_POS_X+340+(17*10), gamelist_y, "FILE: ");
PrintFormat(DEFAULT_SIZE, (ncfg->Config & (NIN_CFG_SLIPPI_FILE_WRITE)) ? GREEN : RED, MENU_POS_X+340+(22*10),
gamelist_y, "%-3s", (ncfg->Config & (NIN_CFG_SLIPPI_FILE_WRITE)) ? "ON" : "OFF");
PrintFormat(DEFAULT_SIZE, BLACK, MENU_POS_X+320+(17*10), gamelist_y, "REPLAY: ");
PrintFormat(DEFAULT_SIZE, (ncfg->Config & (NIN_CFG_SLIPPI_REPLAYS)) ? GREEN : RED, MENU_POS_X+320+(24*10),
gamelist_y, "%-3s", (ncfg->Config & (NIN_CFG_SLIPPI_REPLAYS)) ? "ON" : "OFF");
// Warn the user if they're running low on USB disk space
if ((usb_attached == 1) && (ncfg->Config & (NIN_CFG_SLIPPI_FILE_WRITE)))
if ((usb_attached == 1) && (ncfg->Config & (NIN_CFG_SLIPPI_REPLAYS)))
{
int lowUsbWarnThreshold = 500;
int lowUsbErrorThreshold = 50;
@@ -958,8 +966,10 @@ static const char *const *GetSettingsDescription(const MenuCtx *ctx)
switch (ctx->settings.posX) {
case NIN_SLIPPI_NETWORKING:
return desc_networking;
case NIN_SLIPPI_FILE_WRITE:
return desc_slippi_file_write;
case NIN_SLIPPI_REPLAYS:
return desc_slippi_replays;
case NIN_SLIPPI_REPLAYS_LED:
return desc_slippi_replays_led;
case NIN_SLIPPI_PORT_A:
return desc_slippi_port_a;
case NIN_SLIPPI_CUSTOM_CODES:
@@ -1012,13 +1022,22 @@ static void Menu_Settings_InputHandler(MenuCtx *ctx)
case PAGE_SLIPPI_SETTINGS: ;
const MeleeCodeConfig *codeConfig = GetMeleeCodeConfig();
// Skip replays led line if replays are off
if (ctx->settings.posX == NIN_SLIPPI_REPLAYS_LED && !(ncfg->Config & NIN_CFG_SLIPPI_REPLAYS))
ctx->settings.posX++;
// Handle slippi page position
if (ctx->settings.posX > NIN_SLIPPI_DYNAMIC_CODES_START + codeConfig->lineItemCount - 1)
ctx->settings.posX = 0;
else if (ctx->settings.posX == NIN_SLIPPI_BLANK_1 ||
else if (ctx->settings.posX == NIN_SLIPPI_BLANK_0)
{
// skip
ctx->settings.posX++;
}
else if (ctx->settings.posX == NIN_SLIPPI_BLANK_1 ||
ctx->settings.posX == NIN_SLIPPI_BLANK_2)
{
// Settings 3 and 4 are skipped
// skip
ctx->settings.posX = NIN_SLIPPI_DYNAMIC_CODES_START;
}
break;
@@ -1055,15 +1074,22 @@ static void Menu_Settings_InputHandler(MenuCtx *ctx)
// Handle slippi page positioning
if (ctx->settings.posX < 0)
{
ctx->settings.posX = NIN_SLIPPI_DYNAMIC_CODES_START + codeConfig->lineItemCount - 1;
else if (ctx->settings.posX == NIN_SLIPPI_BLANK_0)
{
// skip
ctx->settings.posX--;
}
if (ctx->settings.posX == NIN_SLIPPI_BLANK_1 || ctx->settings.posX == NIN_SLIPPI_BLANK_2)
else if (ctx->settings.posX == NIN_SLIPPI_BLANK_1 || ctx->settings.posX == NIN_SLIPPI_BLANK_2)
{
// Blank spots are skipped
ctx->settings.posX = NIN_SLIPPI_DYNAMIC_CODES_START - 3;
}
// Skip replays led line if replays are off
if (ctx->settings.posX == NIN_SLIPPI_REPLAYS_LED && !(ncfg->Config & NIN_CFG_SLIPPI_REPLAYS))
ctx->settings.posX--;
break;
}
ctx->redraw = true;
@@ -1240,8 +1266,13 @@ static void Menu_Settings_InputHandler(MenuCtx *ctx)
case NIN_SLIPPI_NETWORKING:
ncfg->Config ^= (NIN_CFG_NETWORK);
break;
case NIN_SLIPPI_FILE_WRITE:
ncfg->Config ^= (NIN_CFG_SLIPPI_FILE_WRITE);
case NIN_SLIPPI_REPLAYS:
ncfg->Config ^= (NIN_CFG_SLIPPI_REPLAYS);
break;
case NIN_SLIPPI_REPLAYS_LED:
ncfg->ReplaysLED++;
if (ncfg->ReplaysLED > 1)
ncfg->ReplaysLED = 0;
break;
case NIN_SLIPPI_PORT_A:
ncfg->Config ^= (NIN_CFG_SLIPPI_PORT_A);
@@ -1409,11 +1440,19 @@ static void Menu_Settings_Redraw(MenuCtx *ctx)
"%-18s:%-4s", "Slippi Networking", (ncfg->Config & (NIN_CFG_NETWORK)) ? "Yes" : "No ");
ListLoopIndex++;
// Slippi USB
// Slippi Replays
PrintFormat(MENU_SIZE, BLACK, MENU_POS_X + SETTINGS_X_START, SettingY(ListLoopIndex),
"%-18s:%-4s", "Slippi File Write", (ncfg->Config & (NIN_CFG_SLIPPI_FILE_WRITE)) ? "Yes" : "No ");
"%-18s:%-4s", "Slippi Replays", (ncfg->Config & (NIN_CFG_SLIPPI_REPLAYS)) ? "Yes" : "No ");
ListLoopIndex++;
if (ncfg->Config & (NIN_CFG_SLIPPI_REPLAYS))
{
// Slippi Replays LED
PrintFormat(MENU_SIZE, BLACK, MENU_POS_X + SETTINGS_X_START, SettingY(ListLoopIndex),
"%-18s:%-4s", "Slippi Replays LED", ncfg->ReplaysLED == 0 ? "Yes" : "No");
}
ListLoopIndex += 2;
// Slippi Port A
PrintFormat(MENU_SIZE, BLACK, MENU_POS_X + SETTINGS_X_START, SettingY(ListLoopIndex),
"%-18s:%-4s", "Slippi on Port A", (ncfg->Config & (NIN_CFG_SLIPPI_PORT_A)) ? "Yes" : "No ");