NANDContentLoader.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. // Copyright 2009 Dolphin Emulator Project
  2. // Licensed under GPLv2+
  3. // Refer to the license.txt file included.
  4. #include <cstddef>
  5. #include <cstdio>
  6. #include <cstring>
  7. #include <functional>
  8. #include <map>
  9. #include <string>
  10. #include <utility>
  11. #include <vector>
  12. #include <polarssl/aes.h>
  13. #include "Common/CommonTypes.h"
  14. #include "Common/FileUtil.h"
  15. #include "Common/MathUtil.h"
  16. #include "Common/NandPaths.h"
  17. #include "Common/StringUtil.h"
  18. #include "Common/Logging/Log.h"
  19. #include "DiscIO/NANDContentLoader.h"
  20. #include "DiscIO/Volume.h"
  21. #include "DiscIO/WiiWad.h"
  22. namespace DiscIO
  23. {
  24. CSharedContent::CSharedContent()
  25. {
  26. UpdateLocation();
  27. }
  28. void CSharedContent::UpdateLocation()
  29. {
  30. m_Elements.clear();
  31. m_lastID = 0;
  32. m_contentMap = StringFromFormat("%s/shared1/content.map", File::GetUserPath(D_WIIROOT_IDX).c_str());
  33. File::IOFile pFile(m_contentMap, "rb");
  34. SElement Element;
  35. while (pFile.ReadArray(&Element, 1))
  36. {
  37. m_Elements.push_back(Element);
  38. m_lastID++;
  39. }
  40. }
  41. CSharedContent::~CSharedContent()
  42. {}
  43. std::string CSharedContent::GetFilenameFromSHA1(const u8* _pHash)
  44. {
  45. for (auto& Element : m_Elements)
  46. {
  47. if (memcmp(_pHash, Element.SHA1Hash, 20) == 0)
  48. {
  49. return StringFromFormat("%s/shared1/%c%c%c%c%c%c%c%c.app", File::GetUserPath(D_WIIROOT_IDX).c_str(),
  50. Element.FileName[0], Element.FileName[1], Element.FileName[2], Element.FileName[3],
  51. Element.FileName[4], Element.FileName[5], Element.FileName[6], Element.FileName[7]);
  52. }
  53. }
  54. return "unk";
  55. }
  56. std::string CSharedContent::AddSharedContent(const u8* _pHash)
  57. {
  58. std::string filename = GetFilenameFromSHA1(_pHash);
  59. if (strcasecmp(filename.c_str(), "unk") == 0)
  60. {
  61. std::string id = StringFromFormat("%08x", m_lastID);
  62. SElement Element;
  63. memcpy(Element.FileName, id.c_str(), 8);
  64. memcpy(Element.SHA1Hash, _pHash, 20);
  65. m_Elements.push_back(Element);
  66. File::CreateFullPath(m_contentMap);
  67. File::IOFile pFile(m_contentMap, "ab");
  68. pFile.WriteArray(&Element, 1);
  69. filename = StringFromFormat("%s/shared1/%s.app", File::GetUserPath(D_WIIROOT_IDX).c_str(), id.c_str());
  70. m_lastID++;
  71. }
  72. return filename;
  73. }
  74. // this classes must be created by the CNANDContentManager
  75. class CNANDContentLoader : public INANDContentLoader
  76. {
  77. public:
  78. CNANDContentLoader(const std::string& _rName);
  79. virtual ~CNANDContentLoader();
  80. bool IsValid() const override { return m_Valid; }
  81. void RemoveTitle() const override;
  82. u64 GetTitleID() const override { return m_TitleID; }
  83. u16 GetIosVersion() const override { return m_IosVersion; }
  84. u32 GetBootIndex() const override { return m_BootIndex; }
  85. size_t GetContentSize() const override { return m_Content.size(); }
  86. const SNANDContent* GetContentByIndex(int _Index) const override;
  87. const u8* GetTMDView() const override { return m_TMDView; }
  88. const u8* GetTMDHeader() const override { return m_TMDHeader; }
  89. u32 GetTIKSize() const override { return m_TIKSize; }
  90. const u8* GetTIK() const override { return m_TIK; }
  91. const std::vector<SNANDContent>& GetContent() const override { return m_Content; }
  92. u16 GetTitleVersion() const override {return m_TitleVersion;}
  93. u16 GetNumEntries() const override {return m_numEntries;}
  94. DiscIO::IVolume::ECountry GetCountry() const override;
  95. u8 GetCountryChar() const override {return m_Country; }
  96. private:
  97. bool m_Valid;
  98. bool m_isWAD;
  99. std::string m_Path;
  100. u64 m_TitleID;
  101. u16 m_IosVersion;
  102. u32 m_BootIndex;
  103. u16 m_numEntries;
  104. u16 m_TitleVersion;
  105. u8 m_TMDView[TMD_VIEW_SIZE];
  106. u8 m_TMDHeader[TMD_HEADER_SIZE];
  107. u32 m_TIKSize;
  108. u8* m_TIK;
  109. u8 m_Country;
  110. std::vector<SNANDContent> m_Content;
  111. bool Initialize(const std::string& _rName);
  112. void AESDecode(u8* _pKey, u8* _IV, u8* _pSrc, u32 _Size, u8* _pDest);
  113. void GetKeyFromTicket(u8* pTicket, u8* pTicketKey);
  114. };
  115. CNANDContentLoader::CNANDContentLoader(const std::string& _rName)
  116. : m_Valid(false)
  117. , m_isWAD(false)
  118. , m_TitleID(-1)
  119. , m_IosVersion(0x09)
  120. , m_BootIndex(-1)
  121. , m_TIKSize(0)
  122. , m_TIK(nullptr)
  123. {
  124. m_Valid = Initialize(_rName);
  125. }
  126. CNANDContentLoader::~CNANDContentLoader()
  127. {
  128. for (auto& content : m_Content)
  129. {
  130. delete [] content.m_pData;
  131. }
  132. m_Content.clear();
  133. if (m_TIK)
  134. {
  135. delete []m_TIK;
  136. m_TIK = nullptr;
  137. }
  138. }
  139. const SNANDContent* CNANDContentLoader::GetContentByIndex(int _Index) const
  140. {
  141. for (auto& Content : m_Content)
  142. {
  143. if (Content.m_Index == _Index)
  144. {
  145. return &Content;
  146. }
  147. }
  148. return nullptr;
  149. }
  150. bool CNANDContentLoader::Initialize(const std::string& _rName)
  151. {
  152. if (_rName.empty())
  153. return false;
  154. m_Path = _rName;
  155. WiiWAD Wad(_rName);
  156. u8* pDataApp = nullptr;
  157. u8* pTMD = nullptr;
  158. u8 DecryptTitleKey[16];
  159. u8 IV[16];
  160. if (Wad.IsValid())
  161. {
  162. m_isWAD = true;
  163. m_TIKSize = Wad.GetTicketSize();
  164. m_TIK = new u8[m_TIKSize];
  165. memcpy(m_TIK, Wad.GetTicket(), m_TIKSize);
  166. GetKeyFromTicket(m_TIK, DecryptTitleKey);
  167. u32 pTMDSize = Wad.GetTMDSize();
  168. pTMD = new u8[pTMDSize];
  169. memcpy(pTMD, Wad.GetTMD(), pTMDSize);
  170. pDataApp = Wad.GetDataApp();
  171. }
  172. else
  173. {
  174. std::string TMDFileName(m_Path);
  175. if ('/' == *TMDFileName.rbegin())
  176. TMDFileName += "title.tmd";
  177. else
  178. m_Path = TMDFileName.substr(0, TMDFileName.find("title.tmd"));
  179. File::IOFile pTMDFile(TMDFileName, "rb");
  180. if (!pTMDFile)
  181. {
  182. WARN_LOG(DISCIO, "CreateFromDirectory: error opening %s", TMDFileName.c_str());
  183. return false;
  184. }
  185. u32 pTMDSize = (u32)File::GetSize(TMDFileName);
  186. pTMD = new u8[pTMDSize];
  187. pTMDFile.ReadBytes(pTMD, (size_t)pTMDSize);
  188. pTMDFile.Close();
  189. }
  190. memcpy(m_TMDView, pTMD + 0x180, TMD_VIEW_SIZE);
  191. memcpy(m_TMDHeader, pTMD, TMD_HEADER_SIZE);
  192. m_TitleVersion = Common::swap16(pTMD + 0x01dc);
  193. m_numEntries = Common::swap16(pTMD + 0x01de);
  194. m_BootIndex = Common::swap16(pTMD + 0x01e0);
  195. m_TitleID = Common::swap64(pTMD + 0x018c);
  196. m_IosVersion = Common::swap16(pTMD + 0x018a);
  197. m_Country = *(u8*)&m_TitleID;
  198. if (m_Country == 2) // SYSMENU
  199. m_Country = GetSysMenuRegion(m_TitleVersion);
  200. m_Content.resize(m_numEntries);
  201. for (u32 i=0; i < m_numEntries; i++)
  202. {
  203. SNANDContent& rContent = m_Content[i];
  204. rContent.m_ContentID = Common::swap32(pTMD + 0x01e4 + 0x24*i);
  205. rContent.m_Index = Common::swap16(pTMD + 0x01e8 + 0x24*i);
  206. rContent.m_Type = Common::swap16(pTMD + 0x01ea + 0x24*i);
  207. rContent.m_Size = (u32)Common::swap64(pTMD + 0x01ec + 0x24*i);
  208. memcpy(rContent.m_SHA1Hash, pTMD + 0x01f4 + 0x24*i, 20);
  209. memcpy(rContent.m_Header, pTMD + 0x01e4 + 0x24*i, 36);
  210. if (m_isWAD)
  211. {
  212. u32 RoundedSize = ROUND_UP(rContent.m_Size, 0x40);
  213. rContent.m_pData = new u8[RoundedSize];
  214. memset(IV, 0, sizeof IV);
  215. memcpy(IV, pTMD + 0x01e8 + 0x24*i, 2);
  216. AESDecode(DecryptTitleKey, IV, pDataApp, RoundedSize, rContent.m_pData);
  217. pDataApp += RoundedSize;
  218. continue;
  219. }
  220. rContent.m_pData = nullptr;
  221. if (rContent.m_Type & 0x8000) // shared app
  222. rContent.m_Filename = CSharedContent::AccessInstance().GetFilenameFromSHA1(rContent.m_SHA1Hash);
  223. else
  224. rContent.m_Filename = StringFromFormat("%s/%08x.app", m_Path.c_str(), rContent.m_ContentID);
  225. // Be graceful about incorrect TMDs.
  226. if (File::Exists(rContent.m_Filename))
  227. rContent.m_Size = (u32) File::GetSize(rContent.m_Filename);
  228. }
  229. delete [] pTMD;
  230. return true;
  231. }
  232. void CNANDContentLoader::AESDecode(u8* _pKey, u8* _IV, u8* _pSrc, u32 _Size, u8* _pDest)
  233. {
  234. aes_context AES_ctx;
  235. aes_setkey_dec(&AES_ctx, _pKey, 128);
  236. aes_crypt_cbc(&AES_ctx, AES_DECRYPT, _Size, _IV, _pSrc, _pDest);
  237. }
  238. void CNANDContentLoader::GetKeyFromTicket(u8* pTicket, u8* pTicketKey)
  239. {
  240. u8 CommonKey[16] = {0xeb,0xe4,0x2a,0x22,0x5e,0x85,0x93,0xe4,0x48,0xd9,0xc5,0x45,0x73,0x81,0xaa,0xf7};
  241. u8 IV[16];
  242. memset(IV, 0, sizeof IV);
  243. memcpy(IV, pTicket + 0x01dc, 8);
  244. AESDecode(CommonKey, IV, pTicket + 0x01bf, 16, pTicketKey);
  245. }
  246. DiscIO::IVolume::ECountry CNANDContentLoader::GetCountry() const
  247. {
  248. if (!IsValid())
  249. return DiscIO::IVolume::COUNTRY_UNKNOWN;
  250. return CountrySwitch(m_Country);
  251. }
  252. CNANDContentManager::~CNANDContentManager()
  253. {
  254. for (auto& entry : m_Map)
  255. {
  256. delete entry.second;
  257. }
  258. m_Map.clear();
  259. }
  260. const INANDContentLoader& CNANDContentManager::GetNANDLoader(const std::string& _rName, bool forceReload)
  261. {
  262. CNANDContentMap::iterator lb = m_Map.lower_bound(_rName);
  263. if (lb == m_Map.end() || (m_Map.key_comp()(_rName, lb->first)))
  264. {
  265. m_Map.insert(lb, CNANDContentMap::value_type(_rName, new CNANDContentLoader(_rName)));
  266. }
  267. else
  268. {
  269. if (!lb->second->IsValid() || forceReload)
  270. {
  271. delete lb->second;
  272. lb->second = new CNANDContentLoader(_rName);
  273. }
  274. }
  275. return *m_Map[_rName];
  276. }
  277. const INANDContentLoader& CNANDContentManager::GetNANDLoader(u64 _titleId, bool forceReload)
  278. {
  279. std::string _rName = Common::GetTitleContentPath(_titleId);
  280. return GetNANDLoader(_rName, forceReload);
  281. }
  282. bool CNANDContentManager::RemoveTitle(u64 _titleID)
  283. {
  284. if (!GetNANDLoader(_titleID).IsValid())
  285. return false;
  286. GetNANDLoader(_titleID).RemoveTitle();
  287. return GetNANDLoader(_titleID, true).IsValid();
  288. }
  289. void CNANDContentLoader::RemoveTitle() const
  290. {
  291. INFO_LOG(DISCIO, "RemoveTitle %08x/%08x", (u32)(m_TitleID >> 32), (u32)m_TitleID);
  292. if (IsValid())
  293. {
  294. // remove TMD?
  295. for (u32 i = 0; i < m_numEntries; i++)
  296. {
  297. if (!(m_Content[i].m_Type & 0x8000)) // skip shared apps
  298. {
  299. std::string filename = StringFromFormat("%s%08x.app", Common::GetTitleContentPath(m_TitleID).c_str(), m_Content[i].m_ContentID);
  300. INFO_LOG(DISCIO, "Delete %s", filename.c_str());
  301. File::Delete(filename);
  302. }
  303. }
  304. }
  305. }
  306. cUIDsys::cUIDsys()
  307. {
  308. UpdateLocation();
  309. }
  310. void cUIDsys::UpdateLocation()
  311. {
  312. m_Elements.clear();
  313. m_lastUID = 0x00001000;
  314. m_uidSys = File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/sys/uid.sys";
  315. File::IOFile pFile(m_uidSys, "rb");
  316. SElement Element;
  317. while (pFile.ReadArray(&Element, 1))
  318. {
  319. *(u32*)&(Element.UID) = Common::swap32(m_lastUID++);
  320. m_Elements.push_back(Element);
  321. }
  322. pFile.Close();
  323. if (m_Elements.empty())
  324. {
  325. *(u64*)&(Element.titleID) = Common::swap64(TITLEID_SYSMENU);
  326. *(u32*)&(Element.UID) = Common::swap32(m_lastUID++);
  327. File::CreateFullPath(m_uidSys);
  328. pFile.Open(m_uidSys, "wb");
  329. if (!pFile.WriteArray(&Element, 1))
  330. ERROR_LOG(DISCIO, "Failed to write to %s", m_uidSys.c_str());
  331. }
  332. }
  333. cUIDsys::~cUIDsys()
  334. {}
  335. u32 cUIDsys::GetUIDFromTitle(u64 _Title)
  336. {
  337. for (auto& Element : m_Elements)
  338. {
  339. if (Common::swap64(_Title) == *(u64*)&(Element.titleID))
  340. {
  341. return Common::swap32(Element.UID);
  342. }
  343. }
  344. return 0;
  345. }
  346. void cUIDsys::AddTitle(u64 _TitleID)
  347. {
  348. if (GetUIDFromTitle(_TitleID))
  349. {
  350. INFO_LOG(DISCIO, "Title %08x%08x, already exists in uid.sys", (u32)(_TitleID >> 32), (u32)_TitleID);
  351. return;
  352. }
  353. SElement Element;
  354. *(u64*)&(Element.titleID) = Common::swap64(_TitleID);
  355. *(u32*)&(Element.UID) = Common::swap32(m_lastUID++);
  356. m_Elements.push_back(Element);
  357. File::CreateFullPath(m_uidSys);
  358. File::IOFile pFile(m_uidSys, "ab");
  359. if (!pFile.WriteArray(&Element, 1))
  360. ERROR_LOG(DISCIO, "fwrite failed");
  361. }
  362. void cUIDsys::GetTitleIDs(std::vector<u64>& _TitleIDs, bool _owned)
  363. {
  364. for (auto& Element : m_Elements)
  365. {
  366. if ((_owned && Common::CheckTitleTIK(Common::swap64(Element.titleID))) ||
  367. (!_owned && Common::CheckTitleTMD(Common::swap64(Element.titleID))))
  368. _TitleIDs.push_back(Common::swap64(Element.titleID));
  369. }
  370. }
  371. u64 CNANDContentManager::Install_WiiWAD(const std::string& fileName)
  372. {
  373. if (fileName.find(".wad") == std::string::npos)
  374. return 0;
  375. const INANDContentLoader& ContentLoader = GetNANDLoader(fileName);
  376. if (ContentLoader.IsValid() == false)
  377. return 0;
  378. u64 TitleID = ContentLoader.GetTitleID();
  379. //copy WAD's TMD header and contents to content directory
  380. std::string ContentPath(Common::GetTitleContentPath(TitleID));
  381. std::string TMDFileName(Common::GetTMDFileName(TitleID));
  382. File::CreateFullPath(TMDFileName);
  383. File::IOFile pTMDFile(TMDFileName, "wb");
  384. if (!pTMDFile)
  385. {
  386. PanicAlertT("WAD installation failed: error creating %s", TMDFileName.c_str());
  387. return 0;
  388. }
  389. pTMDFile.WriteBytes(ContentLoader.GetTMDHeader(), INANDContentLoader::TMD_HEADER_SIZE);
  390. for (u32 i = 0; i < ContentLoader.GetContentSize(); i++)
  391. {
  392. const SNANDContent& Content = ContentLoader.GetContent()[i];
  393. pTMDFile.WriteBytes(Content.m_Header, INANDContentLoader::CONTENT_HEADER_SIZE);
  394. std::string APPFileName;
  395. if (Content.m_Type & 0x8000) //shared
  396. APPFileName = CSharedContent::AccessInstance().AddSharedContent(Content.m_SHA1Hash);
  397. else
  398. APPFileName = StringFromFormat("%s%08x.app", ContentPath.c_str(), Content.m_ContentID);
  399. if (!File::Exists(APPFileName))
  400. {
  401. File::CreateFullPath(APPFileName);
  402. File::IOFile pAPPFile(APPFileName, "wb");
  403. if (!pAPPFile)
  404. {
  405. PanicAlertT("WAD installation failed: error creating %s", APPFileName.c_str());
  406. return 0;
  407. }
  408. pAPPFile.WriteBytes(Content.m_pData, Content.m_Size);
  409. }
  410. else
  411. {
  412. INFO_LOG(DISCIO, "Content %s already exists.", APPFileName.c_str());
  413. }
  414. }
  415. //Extract and copy WAD's ticket to ticket directory
  416. if (!Add_Ticket(TitleID, ContentLoader.GetTIK(), ContentLoader.GetTIKSize()))
  417. {
  418. PanicAlertT("WAD installation failed: error creating ticket");
  419. return 0;
  420. }
  421. cUIDsys::AccessInstance().AddTitle(TitleID);
  422. return TitleID;
  423. }
  424. bool Add_Ticket(u64 TitleID, const u8* p_tik, u32 tikSize)
  425. {
  426. std::string TicketFileName = Common::GetTicketFileName(TitleID);
  427. File::CreateFullPath(TicketFileName);
  428. File::IOFile pTicketFile(TicketFileName, "wb");
  429. if (!pTicketFile || !p_tik)
  430. {
  431. //PanicAlertT("WAD installation failed: error creating %s", TicketFileName.c_str());
  432. return false;
  433. }
  434. return pTicketFile.WriteBytes(p_tik, tikSize);
  435. }
  436. } // namespace end