mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-04-25 06:44:59 +00:00
It's used by both the GUI to do things like install WADs and check up on the system menu, in which case the global root should be used, and by /dev/es, in which case the local one should. The latter isn't *terribly* useful today, since no contents will ever be installed in temporary roots (although it's still relevant for data directories), but converting the whole thing makes sense because then it will Just Work once the entire NAND is synced. Because it would have been a bit of work to split it up (but I can if desired), this commit also contains some basic cleanup of NANDContentLoader: (1) The useless interface class INANDContentLoader is removed and the methods are changed to just return CNANDContentLoader (the only implementation); (2) CNANDContentManager is changed to use unique_ptr and cleaned up a bit.
466 lines
12 KiB
C++
466 lines
12 KiB
C++
// Copyright 2009 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <mbedtls/aes.h>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/FileUtil.h"
|
|
#include "Common/MathUtil.h"
|
|
#include "Common/MsgHandler.h"
|
|
#include "Common/NandPaths.h"
|
|
#include "Common/StringUtil.h"
|
|
#include "Common/Logging/Log.h"
|
|
|
|
#include "DiscIO/NANDContentLoader.h"
|
|
#include "DiscIO/Volume.h"
|
|
#include "DiscIO/WiiWad.h"
|
|
|
|
namespace DiscIO
|
|
{
|
|
|
|
CSharedContent::CSharedContent()
|
|
{
|
|
UpdateLocation();
|
|
}
|
|
|
|
void CSharedContent::UpdateLocation()
|
|
{
|
|
m_Elements.clear();
|
|
m_lastID = 0;
|
|
m_contentMap = StringFromFormat("%s/shared1/content.map", File::GetUserPath(D_WIIROOT_IDX).c_str());
|
|
|
|
File::IOFile pFile(m_contentMap, "rb");
|
|
SElement Element;
|
|
while (pFile.ReadArray(&Element, 1))
|
|
{
|
|
m_Elements.push_back(Element);
|
|
m_lastID++;
|
|
}
|
|
}
|
|
|
|
CSharedContent::~CSharedContent()
|
|
{}
|
|
|
|
std::string CSharedContent::GetFilenameFromSHA1(const u8* _pHash)
|
|
{
|
|
for (auto& Element : m_Elements)
|
|
{
|
|
if (memcmp(_pHash, Element.SHA1Hash, 20) == 0)
|
|
{
|
|
return StringFromFormat("%s/shared1/%c%c%c%c%c%c%c%c.app", File::GetUserPath(D_WIIROOT_IDX).c_str(),
|
|
Element.FileName[0], Element.FileName[1], Element.FileName[2], Element.FileName[3],
|
|
Element.FileName[4], Element.FileName[5], Element.FileName[6], Element.FileName[7]);
|
|
}
|
|
}
|
|
return "unk";
|
|
}
|
|
|
|
std::string CSharedContent::AddSharedContent(const u8* _pHash)
|
|
{
|
|
std::string filename = GetFilenameFromSHA1(_pHash);
|
|
|
|
if (strcasecmp(filename.c_str(), "unk") == 0)
|
|
{
|
|
std::string id = StringFromFormat("%08x", m_lastID);
|
|
SElement Element;
|
|
memcpy(Element.FileName, id.c_str(), 8);
|
|
memcpy(Element.SHA1Hash, _pHash, 20);
|
|
m_Elements.push_back(Element);
|
|
|
|
File::CreateFullPath(m_contentMap);
|
|
|
|
File::IOFile pFile(m_contentMap, "ab");
|
|
pFile.WriteArray(&Element, 1);
|
|
|
|
filename = StringFromFormat("%s/shared1/%s.app", File::GetUserPath(D_WIIROOT_IDX).c_str(), id.c_str());
|
|
m_lastID++;
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
|
|
CNANDContentLoader::CNANDContentLoader(const std::string& _rName)
|
|
: m_Valid(false)
|
|
, m_isWAD(false)
|
|
, m_TitleID(-1)
|
|
, m_IosVersion(0x09)
|
|
, m_BootIndex(-1)
|
|
, m_TIKSize(0)
|
|
, m_TIK(nullptr)
|
|
{
|
|
m_Valid = Initialize(_rName);
|
|
}
|
|
|
|
CNANDContentLoader::~CNANDContentLoader()
|
|
{
|
|
for (auto& content : m_Content)
|
|
{
|
|
delete [] content.m_pData;
|
|
}
|
|
m_Content.clear();
|
|
if (m_TIK)
|
|
{
|
|
delete []m_TIK;
|
|
m_TIK = nullptr;
|
|
}
|
|
}
|
|
|
|
const SNANDContent* CNANDContentLoader::GetContentByIndex(int _Index) const
|
|
{
|
|
for (auto& Content : m_Content)
|
|
{
|
|
if (Content.m_Index == _Index)
|
|
{
|
|
return &Content;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool CNANDContentLoader::Initialize(const std::string& _rName)
|
|
{
|
|
if (_rName.empty())
|
|
return false;
|
|
m_Path = _rName;
|
|
WiiWAD Wad(_rName);
|
|
u8* pDataApp = nullptr;
|
|
u8* pTMD = nullptr;
|
|
u8 DecryptTitleKey[16];
|
|
u8 IV[16];
|
|
if (Wad.IsValid())
|
|
{
|
|
m_isWAD = true;
|
|
m_TIKSize = Wad.GetTicketSize();
|
|
m_TIK = new u8[m_TIKSize];
|
|
memcpy(m_TIK, Wad.GetTicket(), m_TIKSize);
|
|
GetKeyFromTicket(m_TIK, DecryptTitleKey);
|
|
u32 pTMDSize = Wad.GetTMDSize();
|
|
pTMD = new u8[pTMDSize];
|
|
memcpy(pTMD, Wad.GetTMD(), pTMDSize);
|
|
pDataApp = Wad.GetDataApp();
|
|
}
|
|
else
|
|
{
|
|
std::string TMDFileName(m_Path);
|
|
|
|
if ('/' == *TMDFileName.rbegin())
|
|
TMDFileName += "title.tmd";
|
|
else
|
|
m_Path = TMDFileName.substr(0, TMDFileName.find("title.tmd"));
|
|
|
|
File::IOFile pTMDFile(TMDFileName, "rb");
|
|
if (!pTMDFile)
|
|
{
|
|
WARN_LOG(DISCIO, "CreateFromDirectory: error opening %s", TMDFileName.c_str());
|
|
return false;
|
|
}
|
|
u32 pTMDSize = (u32)File::GetSize(TMDFileName);
|
|
pTMD = new u8[pTMDSize];
|
|
pTMDFile.ReadBytes(pTMD, (size_t)pTMDSize);
|
|
pTMDFile.Close();
|
|
}
|
|
|
|
memcpy(m_TMDView, pTMD + 0x180, TMD_VIEW_SIZE);
|
|
memcpy(m_TMDHeader, pTMD, TMD_HEADER_SIZE);
|
|
|
|
|
|
m_TitleVersion = Common::swap16(pTMD + 0x01dc);
|
|
m_numEntries = Common::swap16(pTMD + 0x01de);
|
|
m_BootIndex = Common::swap16(pTMD + 0x01e0);
|
|
m_TitleID = Common::swap64(pTMD + 0x018c);
|
|
m_IosVersion = Common::swap16(pTMD + 0x018a);
|
|
m_Country = *(u8*)&m_TitleID;
|
|
if (m_Country == 2) // SYSMENU
|
|
m_Country = GetSysMenuRegion(m_TitleVersion);
|
|
|
|
m_Content.resize(m_numEntries);
|
|
|
|
|
|
for (u32 i=0; i < m_numEntries; i++)
|
|
{
|
|
SNANDContent& rContent = m_Content[i];
|
|
|
|
rContent.m_ContentID = Common::swap32(pTMD + 0x01e4 + 0x24*i);
|
|
rContent.m_Index = Common::swap16(pTMD + 0x01e8 + 0x24*i);
|
|
rContent.m_Type = Common::swap16(pTMD + 0x01ea + 0x24*i);
|
|
rContent.m_Size = (u32)Common::swap64(pTMD + 0x01ec + 0x24*i);
|
|
memcpy(rContent.m_SHA1Hash, pTMD + 0x01f4 + 0x24*i, 20);
|
|
memcpy(rContent.m_Header, pTMD + 0x01e4 + 0x24*i, 36);
|
|
|
|
if (m_isWAD)
|
|
{
|
|
u32 RoundedSize = ROUND_UP(rContent.m_Size, 0x40);
|
|
rContent.m_pData = new u8[RoundedSize];
|
|
|
|
memset(IV, 0, sizeof IV);
|
|
memcpy(IV, pTMD + 0x01e8 + 0x24*i, 2);
|
|
AESDecode(DecryptTitleKey, IV, pDataApp, RoundedSize, rContent.m_pData);
|
|
|
|
pDataApp += RoundedSize;
|
|
continue;
|
|
}
|
|
|
|
rContent.m_pData = nullptr;
|
|
|
|
if (rContent.m_Type & 0x8000) // shared app
|
|
rContent.m_Filename = CSharedContent::AccessInstance().GetFilenameFromSHA1(rContent.m_SHA1Hash);
|
|
else
|
|
rContent.m_Filename = StringFromFormat("%s/%08x.app", m_Path.c_str(), rContent.m_ContentID);
|
|
|
|
// Be graceful about incorrect TMDs.
|
|
if (File::Exists(rContent.m_Filename))
|
|
rContent.m_Size = (u32) File::GetSize(rContent.m_Filename);
|
|
}
|
|
|
|
delete [] pTMD;
|
|
return true;
|
|
}
|
|
void CNANDContentLoader::AESDecode(u8* _pKey, u8* _IV, u8* _pSrc, u32 _Size, u8* _pDest)
|
|
{
|
|
mbedtls_aes_context AES_ctx;
|
|
|
|
mbedtls_aes_setkey_dec(&AES_ctx, _pKey, 128);
|
|
mbedtls_aes_crypt_cbc(&AES_ctx, MBEDTLS_AES_DECRYPT, _Size, _IV, _pSrc, _pDest);
|
|
}
|
|
|
|
void CNANDContentLoader::GetKeyFromTicket(u8* pTicket, u8* pTicketKey)
|
|
{
|
|
u8 CommonKey[16] = {0xeb,0xe4,0x2a,0x22,0x5e,0x85,0x93,0xe4,0x48,0xd9,0xc5,0x45,0x73,0x81,0xaa,0xf7};
|
|
u8 IV[16];
|
|
memset(IV, 0, sizeof IV);
|
|
memcpy(IV, pTicket + 0x01dc, 8);
|
|
AESDecode(CommonKey, IV, pTicket + 0x01bf, 16, pTicketKey);
|
|
}
|
|
|
|
|
|
DiscIO::IVolume::ECountry CNANDContentLoader::GetCountry() const
|
|
{
|
|
if (!IsValid())
|
|
return DiscIO::IVolume::COUNTRY_UNKNOWN;
|
|
|
|
return CountrySwitch(m_Country);
|
|
}
|
|
|
|
|
|
CNANDContentManager::~CNANDContentManager()
|
|
{
|
|
}
|
|
|
|
const CNANDContentLoader& CNANDContentManager::GetNANDLoader(const std::string& content_path)
|
|
{
|
|
auto it = m_map.find(content_path);
|
|
if (it != m_map.end())
|
|
return *it->second;
|
|
return *m_map.emplace_hint(it, std::make_pair(content_path, std::make_unique<CNANDContentLoader>(content_path)))->second;
|
|
}
|
|
|
|
const CNANDContentLoader& CNANDContentManager::GetNANDLoader(u64 title_id, Common::FromWhichRoot from)
|
|
{
|
|
std::string path = Common::GetTitleContentPath(title_id, from);
|
|
return GetNANDLoader(path);
|
|
}
|
|
|
|
bool CNANDContentManager::RemoveTitle(u64 title_id, Common::FromWhichRoot from)
|
|
{
|
|
auto& loader = GetNANDLoader(title_id, from);
|
|
if (!loader.IsValid())
|
|
return false;
|
|
loader.RemoveTitle();
|
|
return GetNANDLoader(title_id, from).IsValid();
|
|
}
|
|
|
|
void CNANDContentManager::ClearCache()
|
|
{
|
|
m_map.clear();
|
|
}
|
|
|
|
void CNANDContentLoader::RemoveTitle() const
|
|
{
|
|
INFO_LOG(DISCIO, "RemoveTitle %08x/%08x", (u32)(m_TitleID >> 32), (u32)m_TitleID);
|
|
if (IsValid())
|
|
{
|
|
// remove TMD?
|
|
for (u32 i = 0; i < m_numEntries; i++)
|
|
{
|
|
if (!(m_Content[i].m_Type & 0x8000)) // skip shared apps
|
|
{
|
|
std::string filename = StringFromFormat("%s/%08x.app", m_Path.c_str(), m_Content[i].m_ContentID);
|
|
INFO_LOG(DISCIO, "Delete %s", filename.c_str());
|
|
File::Delete(filename);
|
|
}
|
|
}
|
|
CNANDContentManager::Access().ClearCache(); // deletes 'this'
|
|
}
|
|
}
|
|
|
|
cUIDsys::cUIDsys()
|
|
{
|
|
UpdateLocation();
|
|
}
|
|
|
|
void cUIDsys::UpdateLocation()
|
|
{
|
|
m_Elements.clear();
|
|
m_lastUID = 0x00001000;
|
|
m_uidSys = File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/sys/uid.sys";
|
|
|
|
File::IOFile pFile(m_uidSys, "rb");
|
|
SElement Element;
|
|
while (pFile.ReadArray(&Element, 1))
|
|
{
|
|
*(u32*)&(Element.UID) = Common::swap32(m_lastUID++);
|
|
m_Elements.push_back(Element);
|
|
}
|
|
pFile.Close();
|
|
|
|
if (m_Elements.empty())
|
|
{
|
|
*(u64*)&(Element.titleID) = Common::swap64(TITLEID_SYSMENU);
|
|
*(u32*)&(Element.UID) = Common::swap32(m_lastUID++);
|
|
|
|
File::CreateFullPath(m_uidSys);
|
|
pFile.Open(m_uidSys, "wb");
|
|
if (!pFile.WriteArray(&Element, 1))
|
|
ERROR_LOG(DISCIO, "Failed to write to %s", m_uidSys.c_str());
|
|
}
|
|
}
|
|
|
|
cUIDsys::~cUIDsys()
|
|
{}
|
|
|
|
u32 cUIDsys::GetUIDFromTitle(u64 _Title)
|
|
{
|
|
for (auto& Element : m_Elements)
|
|
{
|
|
if (Common::swap64(_Title) == *(u64*)&(Element.titleID))
|
|
{
|
|
return Common::swap32(Element.UID);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void cUIDsys::AddTitle(u64 _TitleID)
|
|
{
|
|
if (GetUIDFromTitle(_TitleID))
|
|
{
|
|
INFO_LOG(DISCIO, "Title %08x%08x, already exists in uid.sys", (u32)(_TitleID >> 32), (u32)_TitleID);
|
|
return;
|
|
}
|
|
|
|
SElement Element;
|
|
*(u64*)&(Element.titleID) = Common::swap64(_TitleID);
|
|
*(u32*)&(Element.UID) = Common::swap32(m_lastUID++);
|
|
m_Elements.push_back(Element);
|
|
|
|
File::CreateFullPath(m_uidSys);
|
|
File::IOFile pFile(m_uidSys, "ab");
|
|
|
|
if (!pFile.WriteArray(&Element, 1))
|
|
ERROR_LOG(DISCIO, "fwrite failed");
|
|
}
|
|
|
|
void cUIDsys::GetTitleIDs(std::vector<u64>& _TitleIDs, bool _owned)
|
|
{
|
|
for (auto& Element : m_Elements)
|
|
{
|
|
if ((_owned && Common::CheckTitleTIK(Common::swap64(Element.titleID), Common::FROM_SESSION_ROOT)) ||
|
|
(!_owned && Common::CheckTitleTMD(Common::swap64(Element.titleID), Common::FROM_SESSION_ROOT)))
|
|
_TitleIDs.push_back(Common::swap64(Element.titleID));
|
|
}
|
|
}
|
|
|
|
u64 CNANDContentManager::Install_WiiWAD(const std::string& fileName)
|
|
{
|
|
if (fileName.find(".wad") == std::string::npos)
|
|
return 0;
|
|
const CNANDContentLoader& ContentLoader = GetNANDLoader(fileName);
|
|
if (ContentLoader.IsValid() == false)
|
|
return 0;
|
|
|
|
u64 TitleID = ContentLoader.GetTitleID();
|
|
|
|
//copy WAD's TMD header and contents to content directory
|
|
|
|
std::string ContentPath(Common::GetTitleContentPath(TitleID, Common::FROM_CONFIGURED_ROOT));
|
|
std::string TMDFileName(Common::GetTMDFileName(TitleID, Common::FROM_CONFIGURED_ROOT));
|
|
File::CreateFullPath(TMDFileName);
|
|
|
|
File::IOFile pTMDFile(TMDFileName, "wb");
|
|
if (!pTMDFile)
|
|
{
|
|
PanicAlertT("WAD installation failed: error creating %s", TMDFileName.c_str());
|
|
return 0;
|
|
}
|
|
|
|
pTMDFile.WriteBytes(ContentLoader.GetTMDHeader(), CNANDContentLoader::TMD_HEADER_SIZE);
|
|
|
|
for (u32 i = 0; i < ContentLoader.GetContentSize(); i++)
|
|
{
|
|
const SNANDContent& Content = ContentLoader.GetContent()[i];
|
|
|
|
pTMDFile.WriteBytes(Content.m_Header, CNANDContentLoader::CONTENT_HEADER_SIZE);
|
|
|
|
std::string APPFileName;
|
|
if (Content.m_Type & 0x8000) //shared
|
|
APPFileName = CSharedContent::AccessInstance().AddSharedContent(Content.m_SHA1Hash);
|
|
else
|
|
APPFileName = StringFromFormat("%s%08x.app", ContentPath.c_str(), Content.m_ContentID);
|
|
|
|
if (!File::Exists(APPFileName))
|
|
{
|
|
File::CreateFullPath(APPFileName);
|
|
File::IOFile pAPPFile(APPFileName, "wb");
|
|
if (!pAPPFile)
|
|
{
|
|
PanicAlertT("WAD installation failed: error creating %s", APPFileName.c_str());
|
|
return 0;
|
|
}
|
|
|
|
pAPPFile.WriteBytes(Content.m_pData, Content.m_Size);
|
|
}
|
|
else
|
|
{
|
|
INFO_LOG(DISCIO, "Content %s already exists.", APPFileName.c_str());
|
|
}
|
|
}
|
|
|
|
//Extract and copy WAD's ticket to ticket directory
|
|
if (!Add_Ticket(TitleID, ContentLoader.GetTIK(), ContentLoader.GetTIKSize()))
|
|
{
|
|
PanicAlertT("WAD installation failed: error creating ticket");
|
|
return 0;
|
|
}
|
|
|
|
cUIDsys::AccessInstance().AddTitle(TitleID);
|
|
|
|
ClearCache();
|
|
|
|
return TitleID;
|
|
}
|
|
|
|
bool Add_Ticket(u64 TitleID, const u8* p_tik, u32 tikSize)
|
|
{
|
|
std::string TicketFileName = Common::GetTicketFileName(TitleID, Common::FROM_CONFIGURED_ROOT);
|
|
File::CreateFullPath(TicketFileName);
|
|
File::IOFile pTicketFile(TicketFileName, "wb");
|
|
if (!pTicketFile || !p_tik)
|
|
{
|
|
//PanicAlertT("WAD installation failed: error creating %s", TicketFileName.c_str());
|
|
return false;
|
|
}
|
|
return pTicketFile.WriteBytes(p_tik, tikSize);
|
|
}
|
|
|
|
} // namespace end
|
|
|