diff --git a/common/config/MeleeCodes.h b/common/config/MeleeCodes.h index a3c7720b..7a59a8a3 100644 --- a/common/config/MeleeCodes.h +++ b/common/config/MeleeCodes.h @@ -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 diff --git a/common/include/CommonConfig.h b/common/include/CommonConfig.h index 0c453e90..f1434a63 100644 --- a/common/include/CommonConfig.h +++ b/common/include/CommonConfig.h @@ -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<MeleeCodeOptions[identifier]; } +static inline u32 ConfigGetReplaysLED(void) +{ + return ncfg->ReplaysLED; +} + #endif diff --git a/kernel/DI.c b/kernel/DI.c index 2b1f6578..19db276c 100644 --- a/kernel/DI.c +++ b/kernel/DI.c @@ -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; } diff --git a/kernel/SlippiFileWriter.c b/kernel/SlippiFileWriter.c index a80e8ec4..048bee47 100644 --- a/kernel/SlippiFileWriter.c +++ b/kernel/SlippiFileWriter.c @@ -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 ¤tFile + // Maybe can remove FA_READ since network thread doesn't share ¤tFile FRESULT fileOpenResult = f_open_secondary_drive(¤tFile, 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(¤tFile); } 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(¤tFile, readBuf, reader.lastReadResult.bytesRead, &wrote); + FRESULT writeResult = f_write(¤tFile, readBuf, reader.lastReadResult.bytesRead, &wrote); + if (replaysLED && writeResult == FR_OK && wrote > 0) + flashLED(); f_sync(¤tFile); if (wrote == 0) @@ -244,6 +324,7 @@ static u32 SlippiHandlerThread(void *arg) dbgprintf("Completing File...\r\n"); completeFile(¤tFile, &reader, writtenByteCount); f_close(¤tFile); + hasFile = false; } } diff --git a/kernel/SlippiFileWriter.h b/kernel/SlippiFileWriter.h index 353d6f63..da6ed28c 100644 --- a/kernel/SlippiFileWriter.h +++ b/kernel/SlippiFileWriter.h @@ -4,6 +4,7 @@ #include "global.h" void SlippiFileWriterInit(); +void SlippiFileWriterUpdateRegisters(); void SlippiFileWriterShutdown(); #endif diff --git a/kernel/SlippiNetwork.c b/kernel/SlippiNetwork.c index b8e05264..4bb26cd9 100644 --- a/kernel/SlippiNetwork.c +++ b/kernel/SlippiNetwork.c @@ -12,7 +12,6 @@ #include "string.h" #include "debug.h" #include "net.h" -#include "ff_utf8.h" #include "Config.h" diff --git a/kernel/diskio.c b/kernel/diskio.c index 62bf5b70..05e968ed 100644 --- a/kernel/diskio.c +++ b/kernel/diskio.c @@ -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; } diff --git a/kernel/kernel.ld b/kernel/kernel.ld index 53c50973..25b5a290 100644 --- a/kernel/kernel.ld +++ b/kernel/kernel.ld @@ -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 diff --git a/kernel/main.c b/kernel/main.c index 31e78695..8f84bbc8 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -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); diff --git a/kernel/usb.c b/kernel/usb.c index 1141f262..64f15528 100644 --- a/kernel/usb.c +++ b/kernel/usb.c @@ -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() diff --git a/kernel/usb.h b/kernel/usb.h index 7b5ecf34..e1500873 100644 --- a/kernel/usb.h +++ b/kernel/usb.h @@ -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; diff --git a/kernel/usbstorage.c b/kernel/usbstorage.c index 48381096..1557cb25 100644 --- a/kernel/usbstorage.c +++ b/kernel/usbstorage.c @@ -81,34 +81,118 @@ distribution. #define DEVLIST_MAXSIZE 8 -extern u32 s_size, s_cnt; +#define GETDEVPARAMS_DESC_OFFSET 20 +#define GETDEVPARAMS_OUT_SIZE 0xC0 + +typedef struct _usbendpointdesc +{ + u8 bLength; + u8 bDescriptorType; + u8 bEndpointAddress; + u8 bmAttributes; + u16 wMaxPacketSize; + u8 bInterval; +} __attribute__((packed)) usb_endpointdesc; + +typedef struct _usbinterfacedesc +{ + 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; + +typedef struct _usbconfdesc +{ + u8 bLength; + u8 bDescriptorType; + u16 wTotalLength; + u8 bNumInterfaces; + u8 bConfigurationValue; + u8 iConfiguration; + u8 bmAttributes; + u8 bMaxPower; + struct _usbinterfacedesc *interfaces; +} __attribute__((packed)) usb_configurationdesc; + +typedef struct _usbdevdesc +{ + 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 +{ + u32 sector_size; + u32 sector_count; + + u32 lun; + u32 vid; + u32 pid; + u32 tag; + u32 interface; + s32 usb_fd; + + u8 ep_in; + u8 ep_out; +} important_storage_data; + +extern u32 usb_s_size, usb_s_cnt; 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 important_storage_data __mounted_device; static u8 *cbw_buffer = NULL; static u8 *transferbuffer = NULL; -static s32 __usbstorage_reset(); +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 __send_cbw(u8 lun, u32 len, u8 flags, const u8 *cb, u8 cbLen) +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(++__tag)); + write32(((u32)cbw_buffer)+4,bswap32(++dev->tag)); write32(((u32)cbw_buffer)+8,bswap32(len)); cbw_buffer[12] = flags; cbw_buffer[13] = lun; @@ -116,7 +200,7 @@ static s32 __send_cbw(u8 lun, u32 len, u8 flags, const u8 *cb, u8 cbLen) memcpy(cbw_buffer + 15, cb, cbLen); - retval = USB_WriteBlkMsg(__usb_fd, __ep_out, CBW_SIZE, (void *)cbw_buffer); + 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; @@ -124,12 +208,12 @@ static s32 __send_cbw(u8 lun, u32 len, u8 flags, const u8 *cb, u8 cbLen) return retval; } -static s32 __read_csw(u8 *status, u32 *dataResidue) +static s32 __read_csw(important_storage_data *dev, u8 *status, u32 *dataResidue) { s32 retval = USBSTORAGE_OK; u32 signature, tag, _dataResidue, _status; - retval = USB_WriteBlkMsg(__usb_fd, __ep_in, CSW_SIZE, cbw_buffer); + 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; @@ -145,19 +229,19 @@ static s32 __read_csw(u8 *status, u32 *dataResidue) if(status != NULL) *status = _status; - if(tag != __tag) return USBSTORAGE_ETAG; + if(tag != dev->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) +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 ? __ep_out : __ep_in; + u8 ep = write ? dev->ep_out : dev->ep_in; s8 retries = USBSTORAGE_CYCLE_RETRIES + 1; do @@ -169,7 +253,7 @@ static s32 __cycle(u8 lun, u8 *buffer, u32 len, u8 *cb, u8 cbLen, u8 write, u8 * if(retval == USBSTORAGE_ETIMEDOUT) break; - retval = __send_cbw(lun, len, (write ? CBW_OUT:CBW_IN), cb, cbLen); + retval = __send_cbw(dev, lun, len, (write ? CBW_OUT:CBW_IN), cb, cbLen); while(_len > 0 && retval >= 0) { @@ -178,11 +262,11 @@ static s32 __cycle(u8 lun, u8 *buffer, u32 len, u8 *cb, u8 cbLen, u8 write, u8 * if ((u32)_buffer&0x1F || !((u32)_buffer&0x10000000)) { if(write) memcpy(transferbuffer, _buffer, thisLen); - retval = USB_WriteBlkMsg(__usb_fd, ep, thisLen, transferbuffer); + retval = USB_WriteBlkMsg(dev->usb_fd, ep, thisLen, transferbuffer); if (!write && retval > 0) memcpy(_buffer, transferbuffer, retval); } else - retval = USB_WriteBlkMsg(__usb_fd, ep, thisLen, _buffer); + retval = USB_WriteBlkMsg(dev->usb_fd, ep, thisLen, _buffer); if (retval == thisLen) { _len -= retval; @@ -193,10 +277,10 @@ static s32 __cycle(u8 lun, u8 *buffer, u32 len, u8 *cb, u8 cbLen, u8 write, u8 * } if (retval >= 0) - retval = __read_csw(&status, &dataResidue); + retval = __read_csw(dev, &status, &dataResidue); if (retval < 0) { - if (__usbstorage_reset() == USBSTORAGE_ETIMEDOUT) + if (__usbstorage_reset(dev) == USBSTORAGE_ETIMEDOUT) retval = USBSTORAGE_ETIMEDOUT; } } while (retval < 0 && retries > 0); @@ -209,61 +293,65 @@ static s32 __cycle(u8 lun, u8 *buffer, u32 len, u8 *cb, u8 cbLen, u8 write, u8 * return retval; } -static s32 __usbstorage_reset() +static s32 __usbstorage_reset(important_storage_data *dev) { - s32 retval = USB_WriteCtrlMsg(__usb_fd, (USB_CTRLTYPE_DIR_HOST2DEVICE | USB_CTRLTYPE_TYPE_CLASS | USB_CTRLTYPE_REC_INTERFACE), USBSTORAGE_RESET, 0, __interface, 0, NULL); + 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(__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); + 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; } -typedef struct -{ - u32 sector_size; - u32 sector_count; - - u32 lun; - u32 vid; - u32 pid; - u32 tag; - u32 interface; - s32 usb_fd; - - u8 ep_in; - u8 ep_out; -} important_storage_data; - 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; + + usb_s_size = __mounted_device.sector_size; + usb_s_cnt = __mounted_device.sector_count; - __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; + __mounted = true; if(transferbuffer == NULL) transferbuffer = (u8*)malloca(MAX_TRANSFER_SIZE_V5, 32); +} - __mounted = true; +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(void) +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; +} diff --git a/kernel/usbstorage.h b/kernel/usbstorage.h index 880f9654..b95f9723 100644 --- a/kernel/usbstorage.h +++ b/kernel/usbstorage.h @@ -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__ */ diff --git a/loader/source/global.c b/loader/source/global.c index 57223479..a3efc573 100644 --- a/loader/source/global.c +++ b/loader/source/global.c @@ -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) diff --git a/loader/source/main.c b/loader/source/main.c index 1ab3805a..4a85ae86 100644 --- a/loader/source/main.c +++ b/loader/source/main.c @@ -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"); diff --git a/loader/source/menu.c b/loader/source/menu.c index 5f3bbf64..91694625 100644 --- a/loader/source/menu.c +++ b/loader/source/menu.c @@ -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 ");