RetroArch/ctr/exec-3dsx/exec_cia.c
MrHuu 422d2fcf12
(3DS) Fix CIA installation issues (#14399)
- Fixes CIA installation failing
- Add CIA installation progress stats
2022-09-11 02:57:31 +02:00

258 lines
6.3 KiB
C

#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>
#include <sys/stat.h>
#include <3ds.h>
#include "ctr/ctr_debug.h"
#define FILE_CHUNK_SIZE 4096
PrintConsole debugConsoleTop;
typedef struct
{
u32 argc;
char args[0x300 - 0x4];
}ciaParam;
char argvHmac[0x20] = {0x1d, 0x78, 0xff, 0xb9, 0xc5, 0xbc, 0x78, 0xb7, 0xac, 0x29, 0x1d, 0x3e, 0x16, 0xd0, 0xcf, 0x53, 0xef, 0x12, 0x58, 0x83, 0xb6, 0x9e, 0x2f, 0x79, 0x47, 0xf9, 0x35, 0x61, 0xeb, 0x50, 0xd7, 0x67};
static int isCiaInstalled(u64 titleId, u16 version)
{
u32 titlesToRetrieve;
u32 titlesRetrieved;
u64* titleIds;
u32 titlesToCheck;
AM_TitleEntry titleInfo;
bool titleExists = false;
Result failed = AM_GetTitleCount(MEDIATYPE_SD, &titlesToRetrieve);
if (R_FAILED(failed))
return -1;
titleIds = malloc(titlesToRetrieve * sizeof(uint64_t));
if (!titleIds)
return -1;
failed = AM_GetTitleList(&titlesRetrieved, MEDIATYPE_SD, titlesToRetrieve, titleIds);
if (R_FAILED(failed))
return -1;
for(titlesToCheck = 0; titlesToCheck < titlesRetrieved; titlesToCheck++)
{
if (titleIds[titlesToCheck] == titleId)
{
titleExists = true;
break;
}
}
free(titleIds);
if (titleExists)
{
failed = AM_GetTitleInfo(MEDIATYPE_SD,
1 /*titleCount*/, &titleId, &titleInfo);
if (R_FAILED(failed))
return -1;
if (titleInfo.version == version)
return 1;
}
return 0;
}
static int deleteCia(u64 TitleId)
{
u64 currTitleId = 0;
/* Do not delete if the titleid is currently running. */
if (R_FAILED(APT_GetAppletInfo((NS_APPID) envGetAptAppId(), &currTitleId, NULL, NULL, NULL, NULL)) || TitleId != currTitleId)
{
AM_DeleteTitle(MEDIATYPE_SD, TitleId);
AM_DeleteTicket(TitleId);
}
}
static int installCia(Handle ciaFile)
{
Handle outputHandle;
u64 fileSize;
u32 bytesRead;
u32 bytesWritten;
u8 transferBuffer[FILE_CHUNK_SIZE];
u64 fileOffset = 0;
Result failed = AM_StartCiaInstall(MEDIATYPE_SD, &outputHandle);
if (R_FAILED(failed))
return -1;
failed = FSFILE_GetSize(ciaFile, &fileSize);
if (R_FAILED(failed))
return -1;
while(fileOffset < fileSize)
{
u64 bytesRemaining = fileSize - fileOffset;
failed = FSFILE_Read(ciaFile, &bytesRead,
fileOffset, transferBuffer,
bytesRemaining < FILE_CHUNK_SIZE
? bytesRemaining
: FILE_CHUNK_SIZE);
if (R_FAILED(failed))
{
AM_CancelCIAInstall(outputHandle);
return -1;
}
failed = FSFILE_Write(outputHandle, &bytesWritten,
fileOffset, transferBuffer, bytesRead, FS_WRITE_FLUSH);
if (R_FAILED(failed))
{
AM_CancelCIAInstall(outputHandle);
if (R_DESCRIPTION(failed) == RD_ALREADY_EXISTS)
return 1;
return -1;
}
printf(PRINTFPOS(27,0)" %llu / %llu\n\n", fileOffset, fileSize);
int progress = roundf(((float)fileOffset/(float)fileSize)*50);
for (int i = 0; i < progress; i++)
{
printf("#");
}
if (bytesWritten != bytesRead)
{
AM_CancelCIAInstall(outputHandle);
return -1;
}
fileOffset += bytesWritten;
}
failed = AM_FinishCiaInstall(outputHandle);
if (R_FAILED(failed))
return -1;
return 1;
}
int exec_cia(const char* path, const char** args)
{
struct stat sBuff;
bool fileExists;
bool inited;
#ifdef USE_CTRULIB_2
if (!gspHasGpuRight())
#endif
gfxInitDefault();
consoleInit(GFX_TOP, &debugConsoleTop);
if (!path || path[0] == '\0')
{
errno = EINVAL;
return -1;
}
fileExists = stat(path, &sBuff) == 0;
if (!fileExists)
{
errno = ENOENT;
return -1;
}
else if (S_ISDIR(sBuff.st_mode))
{
errno = EINVAL;
return -1;
}
inited = R_SUCCEEDED(amInit()) && R_SUCCEEDED(fsInit());
if (inited)
{
AM_TitleEntry ciaInfo;
FS_Archive ciaArchive;
Handle ciaFile;
int ciaInstalled;
ciaParam param;
int argsLength;
/* open CIA file */
Result res = FSUSER_OpenArchive(&ciaArchive,
ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""));
if (R_FAILED(res))
error_and_quit("Cant open SD FS archive.");
res = FSUSER_OpenFile(&ciaFile,
ciaArchive, fsMakePath(PATH_ASCII, path + 5/*skip "sdmc:"*/),
FS_OPEN_READ, 0);
if (R_FAILED(res))
error_and_quit("Cant open CIA file.");
res = AM_GetCiaFileInfo(MEDIATYPE_SD, &ciaInfo, ciaFile);
if (R_FAILED(res))
error_and_quit("Cant get CIA file info.");
ciaInstalled = isCiaInstalled(ciaInfo.titleID, ciaInfo.version);
if (ciaInstalled == -1)
{
/* error */
error_and_quit("Could not read title ID list.");
}
else if (ciaInstalled == 0)
{
/* titleid with version not installed. */
consoleSelect(&debugConsoleTop);
printf(PRINTFPOS(2,0)"Do not close RetroArch or turn off your console.");
printf(PRINTFPOS(5,0)"Installing core:\n%s\n", path);
/* Delete existing content of the pending titleid. */
deleteCia(ciaInfo.titleID);
int error = installCia(ciaFile);
if (error == -1)
{
printf(PRINTFPOS(25,0)"CIA install failed.\n");
wait_for_input();
}
}
FSFILE_Close(ciaFile);
FSUSER_CloseArchive(ciaArchive);
param.argc = 0;
argsLength = 0;
char *argLocation = param.args;
while(args[param.argc])
{
strcpy(argLocation, args[param.argc]);
argLocation += strlen(args[param.argc]) + 1;
argsLength += strlen(args[param.argc]) + 1;
param.argc++;
}
res = APT_PrepareToDoApplicationJump(0, ciaInfo.titleID, 0x1);
if (R_FAILED(res))
error_and_quit("CIA cant run, cant prepare.");
res = APT_DoApplicationJump(&param, sizeof(param.argc)
+ argsLength, argvHmac);
if (R_FAILED(res))
error_and_quit("CIA cant run, cant jump.");
/* wait for application jump, for some reason its not instant */
while(1);
}
/* should never be reached */
amExit();
fsExit();
errno = ENOTSUP;
return -1;
}