mirror of
https://github.com/wiidev/usbloadergx.git
synced 2024-11-05 02:55:07 +01:00
8ab03c4bf9
Thanks to Oddx. I just tested his changes, fixed some issues and then refactored code.
787 lines
20 KiB
C++
Executable File
787 lines
20 KiB
C++
Executable File
/***************************************************************************
|
|
* Copyright (C) 2010 by dude
|
|
* Copyright (C) 2011 by Miigotu
|
|
* Copyright (C) 2011 by Dimok
|
|
*
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any
|
|
* damages arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any
|
|
* purpose, including commercial applications, and to alter it and
|
|
* redistribute it freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you
|
|
* must not claim that you wrote the original software. If you use
|
|
* this software in a product, an acknowledgment in the product
|
|
* documentation would be appreciated but is not required.
|
|
*
|
|
* 2. Altered source versions must be plainly marked as such, and
|
|
* must not be misrepresented as being the original software.
|
|
*
|
|
* 3. This notice may not be removed or altered from any source
|
|
* distribution.
|
|
***************************************************************************/
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <sys/dirent.h>
|
|
|
|
#include "FileOperations/fileops.h"
|
|
#include "Controls/DeviceHandler.hpp"
|
|
#include "settings/CSettings.h"
|
|
#include "settings/GameTitles.h"
|
|
#include "patches/gamepatches.h"
|
|
#include "wad/nandtitle.h"
|
|
#include "memory/memory.h"
|
|
#include "utils/lz77.h"
|
|
#include "gecko.h"
|
|
#include "cache/cache.hpp"
|
|
#include "channels.h"
|
|
|
|
/* GCC 11 false positives */
|
|
#if __GNUC__ > 10
|
|
#pragma GCC diagnostic ignored "-Warray-bounds"
|
|
#pragma GCC diagnostic ignored "-Wstringop-overflow"
|
|
#endif
|
|
|
|
typedef struct _dolheader
|
|
{
|
|
u32 section_pos[18];
|
|
u32 section_start[18];
|
|
u32 section_size[18];
|
|
u32 bss_start;
|
|
u32 bss_size;
|
|
u32 entry_point;
|
|
u32 padding[7];
|
|
} __attribute__((packed)) dolheader;
|
|
|
|
Channels *Channels::instance = NULL;
|
|
|
|
void Channels::clear()
|
|
{
|
|
EmuChannels.clear();
|
|
//! Clear memory of the vector completely
|
|
std::vector<struct discHdr>().swap(EmuChannels);
|
|
}
|
|
|
|
void Channels::GetEmuChannelList(bool use_cache)
|
|
{
|
|
if (use_cache && Settings.CacheTitles && isCacheFile(EMUNAND_HEADER_CACHE_FILE))
|
|
{
|
|
if (EmuChannels.empty())
|
|
LoadGameHeaderCache(EmuChannels);
|
|
if (!EmuChannels.empty())
|
|
return;
|
|
}
|
|
|
|
EmuChannels.clear();
|
|
|
|
char filepath[1024];
|
|
int language = CONF_GetLanguage();
|
|
|
|
snprintf(filepath, sizeof(filepath), "%s/title/00010001", Settings.NandEmuChanPath);
|
|
ParseTitleDir(filepath, language);
|
|
|
|
snprintf(filepath, sizeof(filepath), "%s/title/00010004", Settings.NandEmuChanPath);
|
|
ParseTitleDir(filepath, language);
|
|
|
|
snprintf(filepath, sizeof(filepath), "%s/title/00010002", Settings.NandEmuChanPath);
|
|
ParseTitleDir(filepath, language);
|
|
|
|
if (Settings.CacheTitles)
|
|
SaveGameHeaderCache(EmuChannels);
|
|
}
|
|
|
|
void Channels::GetChannelList()
|
|
{
|
|
NandChannels.clear();
|
|
|
|
// Get count of titles of the good titles
|
|
InternalGetNandChannelList(0x00010001);
|
|
InternalGetNandChannelList(0x00010004);
|
|
InternalGetNandChannelList(0x00010002);
|
|
}
|
|
|
|
void Channels::InternalGetNandChannelList(u32 type)
|
|
{
|
|
// Get count of system titles
|
|
u32 num_titles = NandTitles.SetType(type);
|
|
|
|
for (u32 i = 0; i < num_titles; i++)
|
|
{
|
|
u64 tid = NandTitles.Next();
|
|
if (!tid)
|
|
break;
|
|
|
|
// These can't be booted anyways
|
|
if (TITLE_LOWER(tid) == 0x48414741 || TITLE_LOWER(tid) == 0x48414141 || TITLE_LOWER(tid) == 0x48414641)
|
|
continue;
|
|
|
|
// These aren't installed on the nand
|
|
if (!NandTitles.Exists(tid))
|
|
continue;
|
|
|
|
char id[5];
|
|
NandTitles.AsciiTID(tid, id);
|
|
|
|
// Force old and new format to be "JODI" which is known by GameTDB
|
|
if (tid == 0x000100014c554c5aLL || tid == 0x00010001AF1BF516LL || tid == 0x0001000148415858LL)
|
|
strcpy(id, "JODI");
|
|
|
|
std::string TitleName(NandTitles.NameOf(tid));
|
|
TitleName.erase(0, TitleName.find_first_not_of(' '));
|
|
|
|
int s = NandChannels.size();
|
|
NandChannels.resize(s + 1);
|
|
memset(&NandChannels[s], 0, sizeof(struct discHdr));
|
|
memcpy(NandChannels[s].id, id, 4);
|
|
NandChannels[s].tid = tid;
|
|
NandChannels[s].type = TYPE_GAME_NANDCHAN;
|
|
strncpy(NandChannels[s].title, TitleName.c_str(), sizeof(NandChannels[s].title) - 1);
|
|
}
|
|
}
|
|
|
|
std::vector<struct discHdr> &Channels::GetNandHeaders(void)
|
|
{
|
|
if (NandChannels.empty())
|
|
this->GetChannelList();
|
|
|
|
return NandChannels;
|
|
}
|
|
|
|
std::vector<struct discHdr> &Channels::GetEmuHeaders(void)
|
|
{
|
|
if (EmuChannels.empty())
|
|
this->GetEmuChannelList(true);
|
|
|
|
return EmuChannels;
|
|
}
|
|
|
|
u8 *Channels::GetDol(const u64 &title, u8 *tmdBuffer, bool &isForwarder)
|
|
{
|
|
static const u8 dolsign[6] = {0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
|
|
static u8 dolhead[32] ATTRIBUTE_ALIGN(32);
|
|
u8 *buffer = NULL;
|
|
u32 filesize = 0;
|
|
u32 bootcontent = 0xDEADBEAF;
|
|
u32 high = TITLE_UPPER(title);
|
|
u32 low = TITLE_LOWER(title);
|
|
|
|
char *filepath = (char *)memalign(32, ISFS_MAXPATH);
|
|
if (!filepath)
|
|
return NULL;
|
|
|
|
_tmd *tmd_file = (_tmd *)SIGNATURE_PAYLOAD((u32 *)tmdBuffer);
|
|
|
|
if (!Settings.UseChanLauncher)
|
|
{
|
|
// Channels and VC
|
|
for (u32 i = 0; i < tmd_file->num_contents; ++i)
|
|
{
|
|
if (tmd_file->contents[i].index == tmd_file->boot_index)
|
|
continue; // Skip loader
|
|
|
|
snprintf(filepath, ISFS_MAXPATH, "/title/%08x/%08x/content/%08x.app", (unsigned int)high, (unsigned int)low, (unsigned int)tmd_file->contents[i].cid);
|
|
|
|
s32 fd = ISFS_Open(filepath, ISFS_OPEN_READ);
|
|
if (fd < 0)
|
|
continue;
|
|
|
|
s32 ret = ISFS_Read(fd, dolhead, 32);
|
|
ISFS_Close(fd);
|
|
|
|
if (ret != 32)
|
|
continue;
|
|
|
|
if (memcmp(dolhead, dolsign, sizeof(dolsign)) == 0)
|
|
{
|
|
// Normal channels and VC use 1
|
|
if (tmd_file->contents[i].index != 1)
|
|
isForwarder = true;
|
|
bootcontent = tmd_file->contents[i].cid;
|
|
break;
|
|
}
|
|
}
|
|
// WiiWare not matching a dol signature
|
|
if (bootcontent == 0xDEADBEAF)
|
|
{
|
|
for (u32 i = 0; i < tmd_file->num_contents; ++i)
|
|
{
|
|
if (tmd_file->contents[i].index == 1)
|
|
{
|
|
bootcontent = tmd_file->contents[i].cid;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//! Fall back to boot content if dol is not found
|
|
if (bootcontent == 0xDEADBEAF)
|
|
{
|
|
bootcontent = tmd_file->contents[tmd_file->boot_index].cid;
|
|
if (!Settings.UseChanLauncher)
|
|
gprintf("Main dol not found -> ");
|
|
gprintf("Loading boot content index\n");
|
|
}
|
|
|
|
snprintf(filepath, ISFS_MAXPATH, "/title/%08x/%08x/content/%08x.app", (unsigned int)high, (unsigned int)low, (unsigned int)bootcontent);
|
|
gprintf("Loading Channel DOL: %s\n", filepath);
|
|
|
|
if (NandTitle::LoadFileFromNand(filepath, &buffer, &filesize) < 0)
|
|
{
|
|
gprintf("Failed loading DOL file\n");
|
|
free(filepath);
|
|
return NULL;
|
|
}
|
|
|
|
free(filepath);
|
|
|
|
if (isLZ77compressed(buffer))
|
|
{
|
|
u8 *decompressed = NULL;
|
|
u32 size = 0;
|
|
if (decompressLZ77content(buffer, filesize, &decompressed, &size) < 0)
|
|
{
|
|
gprintf("Decompression failed\n");
|
|
free(buffer);
|
|
return NULL;
|
|
}
|
|
free(buffer);
|
|
buffer = decompressed;
|
|
filesize = size;
|
|
}
|
|
|
|
// move dol to mem2
|
|
u8 *outBuf = (u8 *)MEM2_alloc(filesize);
|
|
if (!outBuf)
|
|
return buffer;
|
|
|
|
memcpy(outBuf, buffer, filesize);
|
|
free(buffer);
|
|
|
|
return outBuf;
|
|
}
|
|
|
|
u8 Channels::GetRequestedIOS(const u64 &title)
|
|
{
|
|
u8 IOS = 0;
|
|
|
|
u32 tmdSize = 0;
|
|
u8 *titleTMD = GetTMD(title, &tmdSize, "");
|
|
if (!titleTMD)
|
|
return 0;
|
|
|
|
if (tmdSize > 0x18B)
|
|
IOS = titleTMD[0x18B];
|
|
|
|
free(titleTMD);
|
|
|
|
return IOS;
|
|
}
|
|
|
|
u8 *Channels::GetTMD(const u64 &tid, u32 *size, const char *prefix)
|
|
{
|
|
char *filepath = (char *)memalign(32, ISFS_MAXPATH);
|
|
if (!filepath)
|
|
return NULL;
|
|
|
|
if (!prefix)
|
|
prefix = "";
|
|
|
|
snprintf(filepath, ISFS_MAXPATH, "%s/title/%08x/%08x/content/title.tmd", prefix, (unsigned int)TITLE_UPPER(tid), (unsigned int)TITLE_LOWER(tid));
|
|
|
|
u8 *tmdBuffer = NULL;
|
|
u32 tmdSize = 0;
|
|
|
|
int ret;
|
|
|
|
if (*prefix != '\0')
|
|
ret = LoadFileToMem(filepath, &tmdBuffer, &tmdSize);
|
|
else
|
|
ret = NandTitle::LoadFileFromNand(filepath, &tmdBuffer, &tmdSize);
|
|
|
|
free(filepath);
|
|
|
|
if (ret < 0)
|
|
{
|
|
gprintf("Reading TMD...Failed!\n");
|
|
if (tmdBuffer)
|
|
free(tmdBuffer);
|
|
return NULL;
|
|
}
|
|
|
|
if (size)
|
|
*size = tmdSize;
|
|
|
|
return tmdBuffer;
|
|
}
|
|
|
|
u32 Channels::LoadChannel(const u64 &chantitle)
|
|
{
|
|
ISFS_Initialize();
|
|
|
|
u32 ios = 0;
|
|
u32 tmdSize = 0;
|
|
u8 *tmdBuffer = GetTMD(chantitle, &tmdSize, "");
|
|
if (!tmdBuffer)
|
|
{
|
|
ISFS_Deinitialize();
|
|
return 0;
|
|
}
|
|
|
|
bool isForwarder = false;
|
|
u8 *chanDOL = GetDol(chantitle, tmdBuffer, isForwarder);
|
|
if (!chanDOL)
|
|
{
|
|
ISFS_Deinitialize();
|
|
free(tmdBuffer);
|
|
return 0;
|
|
}
|
|
|
|
if (tmdSize > 0x18B)
|
|
ios = tmdBuffer[0x18B];
|
|
|
|
Identify(chantitle, tmdBuffer, tmdSize);
|
|
|
|
free(tmdBuffer);
|
|
|
|
dolheader *dolfile = (dolheader *)chanDOL;
|
|
|
|
if (dolfile->bss_start)
|
|
{
|
|
dolfile->bss_start |= 0x80000000;
|
|
|
|
if (dolfile->bss_start < 0x81800000)
|
|
{
|
|
// For homebrews...not all have it clean.
|
|
if (dolfile->bss_start + dolfile->bss_size >= 0x81800000)
|
|
dolfile->bss_size = 0x81800000 - dolfile->bss_start;
|
|
|
|
memset((void *)dolfile->bss_start, 0, dolfile->bss_size);
|
|
DCFlushRange((void *)dolfile->bss_start, dolfile->bss_size);
|
|
ICInvalidateRange((void *)dolfile->bss_start, dolfile->bss_size);
|
|
}
|
|
}
|
|
|
|
int i;
|
|
for (i = 0; i < 18; i++)
|
|
{
|
|
if (!dolfile->section_size[i])
|
|
continue;
|
|
if (dolfile->section_pos[i] < sizeof(dolheader))
|
|
continue;
|
|
if (!(dolfile->section_start[i] & 0x80000000))
|
|
dolfile->section_start[i] |= 0x80000000;
|
|
|
|
u8 *dolChunkOffset = (u8 *)dolfile->section_start[i];
|
|
u32 dolChunkSize = dolfile->section_size[i];
|
|
|
|
memmove(dolChunkOffset, chanDOL + dolfile->section_pos[i], dolChunkSize);
|
|
DCFlushRange(dolChunkOffset, dolChunkSize);
|
|
ICInvalidateRange(dolChunkOffset, dolChunkSize);
|
|
|
|
RegisterDOL(dolChunkOffset, dolChunkSize);
|
|
}
|
|
|
|
u32 chanEntryPoint = dolfile->entry_point;
|
|
|
|
free(dolfile);
|
|
|
|
// Preparations
|
|
memset((void *)Disc_ID, 0, 6);
|
|
*Disc_ID = TITLE_LOWER(chantitle); // Game ID
|
|
*Arena_H = 0; // Arena High, the apploader does this too
|
|
*BI2 = 0x817FE000; // BI2, the apploader does this too
|
|
*Bus_Speed = 0x0E7BE2C0; // bus speed
|
|
*CPU_Speed = 0x2B73A840; // cpu speed
|
|
*GameID_Address = 0x81000000; // Game id address, while there's all 0s at 0x81000000 when using the apploader...
|
|
memcpy((void *)Online_Check, (void *)Disc_ID, 4); // online check
|
|
|
|
memset((void *)0x817FE000, 0, 0x2000); // Clearing BI2
|
|
DCFlushRange((void *)0x817FE000, 0x2000);
|
|
|
|
// IOS Version Check
|
|
*(vu32 *)0x80003140 = ((ios << 16)) | 0xFFFF;
|
|
if (!isForwarder)
|
|
*(vu32 *)0x80003188 = ((ios << 16)) | 0xFFFF;
|
|
|
|
ISFS_Deinitialize();
|
|
|
|
return chanEntryPoint;
|
|
}
|
|
|
|
static bool Identify_GenerateTik(signed_blob **outbuf, u32 *outlen)
|
|
{
|
|
signed_blob *buffer = (signed_blob *)memalign(32, STD_SIGNED_TIK_SIZE);
|
|
if (!buffer)
|
|
return false;
|
|
memset(buffer, 0, STD_SIGNED_TIK_SIZE);
|
|
|
|
sig_rsa2048 *signature = (sig_rsa2048 *)buffer;
|
|
signature->type = ES_SIG_RSA2048;
|
|
|
|
tik *tik_data = (tik *)SIGNATURE_PAYLOAD(buffer);
|
|
strcpy(tik_data->issuer, "Root-CA00000001-XS00000003");
|
|
memset(tik_data->cidx_mask, 0xFF, 32);
|
|
|
|
*outbuf = buffer;
|
|
*outlen = STD_SIGNED_TIK_SIZE;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Channels::Identify(const u64 &titleid, u8 *tmdBuffer, u32 tmdSize)
|
|
{
|
|
char *filepath = (char *)memalign(32, ISFS_MAXPATH);
|
|
if (!filepath)
|
|
return false;
|
|
|
|
u32 tikSize;
|
|
signed_blob *tikBuffer = NULL;
|
|
|
|
if (!Identify_GenerateTik(&tikBuffer, &tikSize))
|
|
{
|
|
free(filepath);
|
|
gprintf("Generating fake ticket...Failed!");
|
|
return false;
|
|
}
|
|
|
|
strlcpy(filepath, "/sys/cert.sys", ISFS_MAXPATH);
|
|
u8 *certBuffer = NULL;
|
|
u32 certSize = 0;
|
|
if (NandTitle::LoadFileFromNand(filepath, &certBuffer, &certSize) < 0)
|
|
{
|
|
gprintf("Reading certs...Failed!\n");
|
|
free(tikBuffer);
|
|
free(filepath);
|
|
return false;
|
|
}
|
|
s32 ret = ES_Identify((signed_blob *)certBuffer, certSize, (signed_blob *)tmdBuffer, tmdSize, tikBuffer, tikSize, NULL);
|
|
if (ret < 0)
|
|
{
|
|
switch (ret)
|
|
{
|
|
case ES_EINVAL:
|
|
gprintf("Error! ES_Identify (ret = %d;) Data invalid!\n", ret);
|
|
break;
|
|
case ES_EALIGN:
|
|
gprintf("Error! ES_Identify (ret = %d;) Data not aligned!\n", ret);
|
|
break;
|
|
case ES_ENOTINIT:
|
|
gprintf("Error! ES_Identify (ret = %d;) ES not initialized!\n", ret);
|
|
break;
|
|
case ES_ENOMEM:
|
|
gprintf("Error! ES_Identify (ret = %d;) No memory!\n", ret);
|
|
break;
|
|
default:
|
|
gprintf("Error! ES_Identify (ret = %d)\n", ret);
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(tikBuffer);
|
|
free(filepath);
|
|
free(certBuffer);
|
|
|
|
return ret < 0 ? false : true;
|
|
}
|
|
|
|
bool Channels::emuExists(char *tmdpath)
|
|
{
|
|
u8 *buffer = NULL;
|
|
u32 size = 0;
|
|
|
|
if (LoadFileToMem(tmdpath, &buffer, &size) < 0)
|
|
return false;
|
|
|
|
signed_blob *s_tmd = (signed_blob *)buffer;
|
|
|
|
u32 i;
|
|
tmd *titleTmd = (tmd *)SIGNATURE_PAYLOAD(s_tmd);
|
|
|
|
for (i = 0; i < titleTmd->num_contents; i++)
|
|
if (!titleTmd->contents[i].index)
|
|
break;
|
|
|
|
if (i == titleTmd->num_contents)
|
|
{
|
|
free(buffer);
|
|
return false;
|
|
}
|
|
|
|
u32 cid = titleTmd->contents[i].cid;
|
|
|
|
free(buffer);
|
|
|
|
char *ptr = strrchr(tmdpath, '/');
|
|
if (!ptr)
|
|
return false;
|
|
|
|
//! tmdpath has length of 1024
|
|
snprintf(ptr + 1, 1024 - (ptr + 1 - tmdpath), "%08x.app", (unsigned int)cid);
|
|
|
|
FILE *f = fopen(tmdpath, "rb");
|
|
if (!f)
|
|
return false;
|
|
|
|
fclose(f);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Channels::ParseTitleDir(char *path, int language)
|
|
{
|
|
if (!path)
|
|
return false;
|
|
|
|
const char *tidPtr = strrchr(path, '/');
|
|
if (!tidPtr)
|
|
return false;
|
|
else
|
|
tidPtr++;
|
|
|
|
struct dirent *dirent = NULL;
|
|
DIR *dir = opendir(path);
|
|
if (!dir)
|
|
return false;
|
|
|
|
char *pathEndPtr = path + strlen(path);
|
|
|
|
u32 tidHigh = strtoul(tidPtr, NULL, 16);
|
|
struct stat st;
|
|
|
|
while ((dirent = readdir(dir)) != 0)
|
|
{
|
|
if (!dirent->d_name)
|
|
continue;
|
|
|
|
// These can't be booted anyways
|
|
if (*dirent->d_name == '.' || strcmp(dirent->d_name, "48414141") == 0 || strcmp(dirent->d_name, "48414641") == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
snprintf(pathEndPtr, 1024 - (pathEndPtr - path), "/%s/content/title.tmd", dirent->d_name);
|
|
|
|
if (stat(path, &st) != 0)
|
|
continue;
|
|
|
|
// Check if content in tmd exists
|
|
if (!emuExists(path))
|
|
continue;
|
|
|
|
// Reset tmd location on path
|
|
snprintf(pathEndPtr, 1024 - (pathEndPtr - path), "/%s/content/title.tmd", dirent->d_name);
|
|
|
|
u32 tidLow = strtoul(dirent->d_name, NULL, 16);
|
|
char id[5];
|
|
memset(id, 0, sizeof(id));
|
|
memcpy(id, &tidLow, 4);
|
|
|
|
u64 tid = ((u64)tidHigh << 32) | ((u64)tidLow);
|
|
|
|
// Force old and new format to be "JODI" which is known by GameTDB
|
|
if (tid == 0x000100014c554c5aLL || tid == 0x00010001AF1BF516LL || tid == 0x0001000148415858LL)
|
|
strcpy(id, "JODI");
|
|
|
|
std::string TitleName;
|
|
if(!GetEmuChanTitle(path, language, TitleName))
|
|
TitleName = id;
|
|
|
|
TitleName.erase(0, TitleName.find_first_not_of(' '));
|
|
|
|
int s = EmuChannels.size();
|
|
EmuChannels.resize(s + 1);
|
|
memset(&EmuChannels[s], 0, sizeof(struct discHdr));
|
|
memcpy(EmuChannels[s].id, id, 4);
|
|
EmuChannels[s].tid = tid;
|
|
EmuChannels[s].type = TYPE_GAME_EMUNANDCHAN;
|
|
strncpy(EmuChannels[s].title, TitleName.c_str(), sizeof(EmuChannels[s].title) - 1);
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Channels::GetEmuChanTitle(char *tmdpath, int language, std::string &Title)
|
|
{
|
|
u8 *buffer = NULL;
|
|
u32 size = 0;
|
|
|
|
if (LoadFileToMem(tmdpath, &buffer, &size) < 0)
|
|
return false;
|
|
|
|
signed_blob *s_tmd = (signed_blob *)buffer;
|
|
|
|
u32 i;
|
|
tmd *titleTmd = (tmd *)SIGNATURE_PAYLOAD(s_tmd);
|
|
|
|
for (i = 0; i < titleTmd->num_contents; i++)
|
|
if (!titleTmd->contents[i].index)
|
|
break;
|
|
|
|
if (i == titleTmd->num_contents)
|
|
{
|
|
free(buffer);
|
|
return false;
|
|
}
|
|
|
|
u32 cid = titleTmd->contents[i].cid;
|
|
|
|
free(buffer);
|
|
|
|
char *ptr = strrchr(tmdpath, '/');
|
|
if (!ptr)
|
|
return false;
|
|
|
|
//! tmdpath has length of 1024
|
|
snprintf(ptr + 1, 1024 - (ptr + 1 - tmdpath), "%08x.app", (unsigned int)cid);
|
|
|
|
FILE *f = fopen(tmdpath, "rb");
|
|
if (!f)
|
|
return false;
|
|
|
|
if (fseek(f, IMET_OFFSET, SEEK_SET) != 0)
|
|
{
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
|
|
IMET *imet = (IMET *)malloc(sizeof(IMET));
|
|
if (!imet)
|
|
{
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
|
|
if (fread(imet, 1, sizeof(IMET), f) != sizeof(IMET))
|
|
{
|
|
free(imet);
|
|
fclose(f);
|
|
return false;
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
if (imet->sig != IMET_SIGNATURE)
|
|
{
|
|
free(imet);
|
|
return false;
|
|
}
|
|
|
|
// names not available
|
|
if (imet->name_japanese[language * IMET_MAX_NAME_LEN] == 0)
|
|
{
|
|
if (imet->name_english[0] != 0)
|
|
language = CONF_LANG_ENGLISH;
|
|
else
|
|
{
|
|
free(imet);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
wchar_t wName[IMET_MAX_NAME_LEN];
|
|
|
|
// retrieve channel name in system language or on english
|
|
for (int i = 0; i < IMET_MAX_NAME_LEN; i++)
|
|
wName[i] = imet->name_japanese[i + (language * IMET_MAX_NAME_LEN)];
|
|
|
|
wString wsname(wName);
|
|
Title = wsname.toUTF8();
|
|
|
|
free(imet);
|
|
|
|
return true;
|
|
}
|
|
|
|
u8 *Channels::GetOpeningBnr(const u64 &title, u32 *outsize, const char *pathPrefix)
|
|
{
|
|
u8 *banner = NULL;
|
|
u32 high = TITLE_UPPER(title);
|
|
u32 low = TITLE_LOWER(title);
|
|
|
|
char *filepath = (char *)memalign(32, ISFS_MAXPATH + strlen(pathPrefix));
|
|
if (!filepath)
|
|
return NULL;
|
|
|
|
do
|
|
{
|
|
snprintf(filepath, ISFS_MAXPATH, "%s/title/%08x/%08x/content/title.tmd", pathPrefix, (unsigned int)high, (unsigned int)low);
|
|
|
|
u8 *buffer = NULL;
|
|
u32 filesize = 0;
|
|
|
|
int ret = 0;
|
|
|
|
if (pathPrefix && *pathPrefix != 0)
|
|
ret = LoadFileToMem(filepath, &buffer, &filesize);
|
|
else
|
|
ret = NandTitle::LoadFileFromNand(filepath, &buffer, &filesize);
|
|
|
|
if (ret < 0)
|
|
break;
|
|
|
|
_tmd *tmd_file = (_tmd *)SIGNATURE_PAYLOAD((u32 *)buffer);
|
|
bool found = false;
|
|
u32 bootcontent = 0;
|
|
for (u32 i = 0; i < tmd_file->num_contents; ++i)
|
|
{
|
|
if (tmd_file->contents[i].index == 0)
|
|
{
|
|
bootcontent = tmd_file->contents[i].cid;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(buffer);
|
|
buffer = NULL;
|
|
filesize = 0;
|
|
|
|
if (!found)
|
|
break;
|
|
|
|
snprintf(filepath, ISFS_MAXPATH, "%s/title/%08x/%08x/content/%08x.app", pathPrefix, (unsigned int)high, (unsigned int)low, (unsigned int)bootcontent);
|
|
|
|
if (pathPrefix && *pathPrefix != 0)
|
|
ret = LoadFileToMem(filepath, &buffer, &filesize);
|
|
else
|
|
ret = NandTitle::LoadFileFromNand(filepath, &buffer, &filesize);
|
|
|
|
if (ret < 0)
|
|
break;
|
|
|
|
IMET *imet = (IMET *)(buffer + IMET_OFFSET);
|
|
if (imet->sig != IMET_SIGNATURE)
|
|
{
|
|
free(buffer);
|
|
break;
|
|
}
|
|
|
|
// move IMET_OFFSET bytes back
|
|
filesize -= IMET_OFFSET;
|
|
|
|
banner = (u8 *)memalign(32, filesize);
|
|
if (!banner)
|
|
{
|
|
free(buffer);
|
|
break;
|
|
}
|
|
|
|
memcpy(banner, buffer + IMET_OFFSET, filesize);
|
|
|
|
free(buffer);
|
|
|
|
if (outsize)
|
|
*outsize = filesize;
|
|
} while (0);
|
|
|
|
free(filepath);
|
|
|
|
return banner;
|
|
}
|