mirror of
https://github.com/project-slippi/Nintendont.git
synced 2025-10-06 00:22:40 +02:00
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:
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -118,4 +118,9 @@ static inline u32 ConfigGetMeleeCodeValue(int identifier)
|
||||
return ncfg->MeleeCodeOptions[identifier];
|
||||
}
|
||||
|
||||
static inline u32 ConfigGetReplaysLED(void)
|
||||
{
|
||||
return ncfg->ReplaysLED;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,6 +4,7 @@
|
||||
#include "global.h"
|
||||
|
||||
void SlippiFileWriterInit();
|
||||
void SlippiFileWriterUpdateRegisters();
|
||||
void SlippiFileWriterShutdown();
|
||||
|
||||
#endif
|
||||
|
@@ -12,7 +12,6 @@
|
||||
#include "string.h"
|
||||
#include "debug.h"
|
||||
#include "net.h"
|
||||
#include "ff_utf8.h"
|
||||
#include "Config.h"
|
||||
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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()
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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__ */
|
||||
|
@@ -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)
|
||||
|
@@ -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");
|
||||
|
@@ -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 ");
|
||||
|
Reference in New Issue
Block a user