IOS/ES: Keep track of the active title properly

This changes ES to keep track of the active title properly,
just like IOS:

* It is NOT changed on resource manager open/close.
* It is reset on IOS reload.
* It is changed by ES_DIVerify and ES_Launch.

IOS stores the active title in a structure like this:

    struct ESTitleContext
    {
      Ticket* ticket;
      TMD* tmd;
      u32 active;
    };

With this commit, we also do keep the Ticket and TMD around. This
makes some of the DI ioctlvs (which return data about the current
active title) trivial to implement in the future.

This fixes the System Menu not being able to see update partitions
and also allows us to change Dolphin's active game info in the future.
This commit is contained in:
Léo Lam 2017-02-26 17:26:08 +01:00
parent 78e7f4aae3
commit 4d776ffa8f
13 changed files with 128 additions and 75 deletions

View File

@ -278,8 +278,6 @@ bool CBoot::BootUp()
PanicAlertT("Warning - starting ISO in wrong console mode!"); PanicAlertT("Warning - starting ISO in wrong console mode!");
} }
IOS::HLE::ES_DIVerify(pVolume.GetTMD());
_StartupPara.bWii = pVolume.GetVolumeType() == DiscIO::Platform::WII_DISC; _StartupPara.bWii = pVolume.GetVolumeType() == DiscIO::Platform::WII_DISC;
// HLE BS2 or not // HLE BS2 or not

View File

@ -311,6 +311,11 @@ bool CBoot::EmulatedBS2_Wii()
if (DVDInterface::GetVolume().GetVolumeType() != DiscIO::Platform::WII_DISC) if (DVDInterface::GetVolume().GetVolumeType() != DiscIO::Platform::WII_DISC)
return false; return false;
const IOS::ES::TMDReader tmd = DVDInterface::GetVolume().GetTMD();
if (!SetupWiiMemory(tmd.GetIOSId()))
return false;
// This is some kind of consistency check that is compared to the 0x00 // This is some kind of consistency check that is compared to the 0x00
// values as the game boots. This location keeps the 4 byte ID for as long // values as the game boots. This location keeps the 4 byte ID for as long
// as the game is running. The 6 byte ID at 0x00 is overwritten sometime // as the game is running. The 6 byte ID at 0x00 is overwritten sometime
@ -346,11 +351,6 @@ bool CBoot::EmulatedBS2_Wii()
PowerPC::ppcState.gpr[1] = 0x816ffff0; // StackPointer PowerPC::ppcState.gpr[1] = 0x816ffff0; // StackPointer
IOS::ES::TMDReader tmd = DVDInterface::GetVolume().GetTMD();
if (!SetupWiiMemory(tmd.GetIOSId()))
return false;
// Execute the apploader // Execute the apploader
const u32 apploader_offset = 0x2440; // 0x1c40; const u32 apploader_offset = 0x2440; // 0x1c40;
@ -383,11 +383,7 @@ bool CBoot::EmulatedBS2_Wii()
PowerPC::ppcState.gpr[3] = 0x81300000; PowerPC::ppcState.gpr[3] = 0x81300000;
RunFunction(iAppLoaderInit); RunFunction(iAppLoaderInit);
// Let the apploader load the exe to memory. At this point I get an unknown IPC command // Let the apploader load the exe to memory
// (command zero) when I load Wii Sports or other games a second time. I don't notice
// any side effects however. It's a little disconcerting however that Start after Stop
// behaves differently than the first Start after starting Dolphin. It means something
// was not reset correctly.
DEBUG_LOG(BOOT, "Run iAppLoaderMain"); DEBUG_LOG(BOOT, "Run iAppLoaderMain");
do do
{ {
@ -413,6 +409,8 @@ bool CBoot::EmulatedBS2_Wii()
// Load patches and run startup patches // Load patches and run startup patches
PatchEngine::LoadPatches(); PatchEngine::LoadPatches();
IOS::HLE::ES_DIVerify(tmd, DVDInterface::GetVolume().GetTicket());
// return // return
PC = PowerPC::ppcState.gpr[3]; PC = PowerPC::ppcState.gpr[3];
return true; return true;

View File

@ -109,7 +109,7 @@ IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request)
const ES::TMDReader tmd = DVDInterface::GetVolume().GetTMD(); const ES::TMDReader tmd = DVDInterface::GetVolume().GetTMD();
const std::vector<u8> raw_tmd = tmd.GetRawTMD(); const std::vector<u8> raw_tmd = tmd.GetRawTMD();
Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size()); Memory::CopyToEmu(request.io_vectors[0].address, raw_tmd.data(), raw_tmd.size());
ES_DIVerify(tmd); ES_DIVerify(tmd, DVDInterface::GetVolume().GetTicket());
return_value = 1; return_value = 1;
break; break;

View File

@ -39,6 +39,7 @@ namespace HLE
namespace Device namespace Device
{ {
std::string ES::m_ContentFile; std::string ES::m_ContentFile;
ES::TitleContext ES::m_title_context;
constexpr u8 s_key_sd[0x10] = {0xab, 0x01, 0xb9, 0xd8, 0xe1, 0x62, 0x2b, 0x08, constexpr u8 s_key_sd[0x10] = {0xab, 0x01, 0xb9, 0xd8, 0xe1, 0x62, 0x2b, 0x08,
0xaf, 0xba, 0xd8, 0x4d, 0xbf, 0xc2, 0xa5, 0x5d}; 0xaf, 0xba, 0xd8, 0x4d, 0xbf, 0xc2, 0xa5, 0x5d};
@ -65,11 +66,58 @@ constexpr const u8* s_key_table[11] = {
ES::ES(u32 device_id, const std::string& device_name) : Device(device_id, device_name) ES::ES(u32 device_id, const std::string& device_name) : Device(device_id, device_name)
{ {
m_title_context.Clear();
m_TitleIDs.clear();
DiscIO::cUIDsys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT};
uid_sys.GetTitleIDs(m_TitleIDs);
// uncomment if ES_GetOwnedTitlesCount / ES_GetOwnedTitles is implemented
// m_TitleIDsOwned.clear();
// DiscIO::cUIDsys::AccessInstance().GetTitleIDs(m_TitleIDsOwned, true);
}
void ES::TitleContext::Clear()
{
ticket.SetBytes({});
tmd.SetBytes({});
active = false;
}
void ES::TitleContext::DoState(PointerWrap& p)
{
ticket.DoState(p);
tmd.DoState(p);
p.Do(active);
}
void ES::TitleContext::Update(const DiscIO::CNANDContentLoader& content_loader)
{
if (!content_loader.IsValid())
return;
Update(content_loader.GetTMD(), content_loader.GetTicket());
}
void ES::TitleContext::Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketReader& ticket_)
{
if (!tmd_.IsValid() || !ticket_.IsValid())
{
ERROR_LOG(IOS_ES, "TMD or ticket is not valid -- refusing to update title context");
return;
}
ticket = ticket_;
tmd = tmd_;
active = true;
} }
void ES::LoadWAD(const std::string& _rContentFile) void ES::LoadWAD(const std::string& _rContentFile)
{ {
m_ContentFile = _rContentFile; m_ContentFile = _rContentFile;
// XXX: Ideally, this should be done during a launch, but because we support launching WADs
// without installing them (which is a bit of a hack), we have to do this manually here.
const auto& content_loader = DiscIO::CNANDContentManager::Access().GetNANDLoader(m_ContentFile);
m_title_context.Update(content_loader);
} }
void ES::DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output) void ES::DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output)
@ -82,6 +130,8 @@ void ES::DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv,
bool ES::LaunchTitle(u64 title_id, bool skip_reload) const bool ES::LaunchTitle(u64 title_id, bool skip_reload) const
{ {
m_title_context.Clear();
NOTICE_LOG(IOS_ES, "Launching title %016" PRIx64 "...", title_id); NOTICE_LOG(IOS_ES, "Launching title %016" PRIx64 "...", title_id);
// ES_Launch should probably reset the whole state, which at least means closing all open files. // ES_Launch should probably reset the whole state, which at least means closing all open files.
@ -120,47 +170,18 @@ bool ES::LaunchPPCTitle(u64 title_id, bool skip_reload) const
return LaunchTitle(required_ios); return LaunchTitle(required_ios);
} }
m_title_context.Update(content_loader);
SetDefaultContentFile(Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT)); SetDefaultContentFile(Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT));
return BootstrapPPC(content_loader); return BootstrapPPC(content_loader);
} }
void ES::OpenInternal()
{
auto& contentLoader = DiscIO::CNANDContentManager::Access().GetNANDLoader(m_ContentFile);
// check for cd ...
if (contentLoader.IsValid())
{
m_TitleID = contentLoader.GetTMD().GetTitleId();
m_TitleIDs.clear();
DiscIO::cUIDsys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT};
uid_sys.GetTitleIDs(m_TitleIDs);
// uncomment if ES_GetOwnedTitlesCount / ES_GetOwnedTitles is implemented
// m_TitleIDsOwned.clear();
// DiscIO::cUIDsys::AccessInstance().GetTitleIDs(m_TitleIDsOwned, true);
}
else if (DVDInterface::VolumeIsValid())
{
// blindly grab the titleID from the disc - it's unencrypted at:
// offset 0x0F8001DC and 0x0F80044C
DVDInterface::GetVolume().GetTitleID(&m_TitleID);
}
else
{
m_TitleID = ((u64)0x00010000 << 32) | 0xF00DBEEF;
}
INFO_LOG(IOS_ES, "Set default title to %08x/%08x", (u32)(m_TitleID >> 32), (u32)m_TitleID);
}
void ES::DoState(PointerWrap& p) void ES::DoState(PointerWrap& p)
{ {
Device::DoState(p); Device::DoState(p);
p.Do(m_ContentFile); p.Do(m_ContentFile);
OpenInternal();
p.Do(m_AccessIdentID); p.Do(m_AccessIdentID);
p.Do(m_TitleIDs); p.Do(m_TitleIDs);
m_title_context.DoState(p);
m_addtitle_tmd.DoState(p); m_addtitle_tmd.DoState(p);
p.Do(m_addtitle_content_id); p.Do(m_addtitle_content_id);
@ -197,8 +218,6 @@ void ES::DoState(PointerWrap& p)
ReturnCode ES::Open(const OpenRequest& request) ReturnCode ES::Open(const OpenRequest& request)
{ {
OpenInternal();
if (m_is_active) if (m_is_active)
INFO_LOG(IOS_ES, "Device was re-opened."); INFO_LOG(IOS_ES, "Device was re-opened.");
return Device::Open(request); return Device::Open(request);
@ -206,9 +225,8 @@ ReturnCode ES::Open(const OpenRequest& request)
void ES::Close() void ES::Close()
{ {
// XXX: does IOS really clear the content access map here?
m_ContentAccessMap.clear(); m_ContentAccessMap.clear();
m_TitleIDs.clear();
m_TitleID = -1;
m_AccessIdentID = 0; m_AccessIdentID = 0;
INFO_LOG(IOS_ES, "ES: Close"); INFO_LOG(IOS_ES, "ES: Close");
@ -628,7 +646,10 @@ IPCCommandResult ES::OpenContent(const IOCtlVRequest& request)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
u32 Index = Memory::Read_U32(request.in_vectors[0].address); u32 Index = Memory::Read_U32(request.in_vectors[0].address);
s32 CFD = OpenTitleContent(m_AccessIdentID++, m_TitleID, Index); if (!m_title_context.active)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
s32 CFD = OpenTitleContent(m_AccessIdentID++, m_title_context.tmd.GetTitleId(), Index);
INFO_LOG(IOS_ES, "IOCTL_ES_OPENCONTENT: Index %i -> got CFD %x", Index, CFD); INFO_LOG(IOS_ES, "IOCTL_ES_OPENCONTENT: Index %i -> got CFD %x", Index, CFD);
return GetDefaultReply(CFD); return GetDefaultReply(CFD);
@ -771,8 +792,13 @@ IPCCommandResult ES::GetTitleID(const IOCtlVRequest& request)
if (!request.HasNumberOfValidVectors(0, 1)) if (!request.HasNumberOfValidVectors(0, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT); return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
Memory::Write_U64(m_TitleID, request.io_vectors[0].address); if (!m_title_context.active)
INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLEID: %08x/%08x", (u32)(m_TitleID >> 32), (u32)m_TitleID); return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
const u64 title_id = m_title_context.tmd.GetTitleId();
Memory::Write_U64(title_id, request.io_vectors[0].address);
INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLEID: %08x/%08x", static_cast<u32>(title_id >> 32),
static_cast<u32>(title_id));
return GetDefaultReply(IPC_SUCCESS); return GetDefaultReply(IPC_SUCCESS);
} }
@ -1327,8 +1353,12 @@ IPCCommandResult ES::Sign(const IOCtlVRequest& request)
u32 data_size = request.in_vectors[0].size; u32 data_size = request.in_vectors[0].size;
u8* sig_out = Memory::GetPointer(request.io_vectors[0].address); u8* sig_out = Memory::GetPointer(request.io_vectors[0].address);
if (!m_title_context.active)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
const EcWii& ec = EcWii::GetInstance(); const EcWii& ec = EcWii::GetInstance();
MakeAPSigAndCert(sig_out, ap_cert_out, m_TitleID, data, data_size, ec.GetNGPriv(), ec.GetNGID()); MakeAPSigAndCert(sig_out, ap_cert_out, m_title_context.tmd.GetTitleId(), data, data_size,
ec.GetNGPriv(), ec.GetNGID());
return GetDefaultReply(IPC_SUCCESS); return GetDefaultReply(IPC_SUCCESS);
} }
@ -1373,28 +1403,27 @@ const DiscIO::CNANDContentLoader& ES::AccessContentDevice(u64 title_id) const
// the WAD // the WAD
// need not be installed in the NAND, but it could be opened directly from a WAD file anywhere on // need not be installed in the NAND, but it could be opened directly from a WAD file anywhere on
// disk. // disk.
if (m_TitleID == title_id && !m_ContentFile.empty()) if (m_title_context.active && m_title_context.tmd.GetTitleId() == title_id &&
!m_ContentFile.empty())
return DiscIO::CNANDContentManager::Access().GetNANDLoader(m_ContentFile); return DiscIO::CNANDContentManager::Access().GetNANDLoader(m_ContentFile);
return DiscIO::CNANDContentManager::Access().GetNANDLoader(title_id, Common::FROM_SESSION_ROOT); return DiscIO::CNANDContentManager::Access().GetNANDLoader(title_id, Common::FROM_SESSION_ROOT);
} }
u32 ES::ES_DIVerify(const IOS::ES::TMDReader& tmd) s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& ticket)
{ {
if (!tmd.IsValid()) m_title_context.Clear();
return -1;
u64 title_id = 0xDEADBEEFDEADBEEFull; if (!tmd.IsValid() || !ticket.IsValid())
u64 tmd_title_id = tmd.GetTitleId(); return ES_PARAMETER_SIZE_OR_ALIGNMENT;
DVDInterface::GetVolume().GetTitleID(&title_id); if (tmd.GetTitleId() != ticket.GetTitleId())
if (title_id != tmd_title_id) return ES_PARAMETER_SIZE_OR_ALIGNMENT;
return -1;
std::string tmd_path = Common::GetTMDFileName(tmd_title_id, Common::FROM_SESSION_ROOT); std::string tmd_path = Common::GetTMDFileName(tmd.GetTitleId(), Common::FROM_SESSION_ROOT);
File::CreateFullPath(tmd_path); File::CreateFullPath(tmd_path);
File::CreateFullPath(Common::GetTitleDataPath(tmd_title_id, Common::FROM_SESSION_ROOT)); File::CreateFullPath(Common::GetTitleDataPath(tmd.GetTitleId(), Common::FROM_SESSION_ROOT));
if (!File::Exists(tmd_path)) if (!File::Exists(tmd_path))
{ {
@ -1404,11 +1433,13 @@ u32 ES::ES_DIVerify(const IOS::ES::TMDReader& tmd)
ERROR_LOG(IOS_ES, "DIVerify failed to write disc TMD to NAND."); ERROR_LOG(IOS_ES, "DIVerify failed to write disc TMD to NAND.");
} }
DiscIO::cUIDsys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT}; DiscIO::cUIDsys uid_sys{Common::FromWhichRoot::FROM_SESSION_ROOT};
uid_sys.AddTitle(tmd_title_id); uid_sys.AddTitle(tmd.GetTitleId());
// DI_VERIFY writes to title.tmd, which is read and cached inside the NAND Content Manager. // DI_VERIFY writes to title.tmd, which is read and cached inside the NAND Content Manager.
// clear the cache to avoid content access mismatches. // clear the cache to avoid content access mismatches.
DiscIO::CNANDContentManager::Access().ClearCache(); DiscIO::CNANDContentManager::Access().ClearCache();
return 0;
m_title_context.Update(tmd, ticket);
return IPC_SUCCESS;
} }
} // namespace Device } // namespace Device
} // namespace HLE } // namespace HLE

View File

@ -40,15 +40,13 @@ public:
// Internal implementation of the ES_DECRYPT ioctlv. // Internal implementation of the ES_DECRYPT ioctlv.
void DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output); void DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output);
void OpenInternal();
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;
ReturnCode Open(const OpenRequest& request) override; ReturnCode Open(const OpenRequest& request) override;
void Close() override; void Close() override;
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
static u32 ES_DIVerify(const IOS::ES::TMDReader& tmd); static s32 DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& ticket);
// This should only be cleared on power reset // This should only be cleared on power reset
static std::string m_ContentFile; static std::string m_ContentFile;
@ -211,9 +209,21 @@ private:
ContentAccessMap m_ContentAccessMap; ContentAccessMap m_ContentAccessMap;
std::vector<u64> m_TitleIDs; std::vector<u64> m_TitleIDs;
u64 m_TitleID = -1;
u32 m_AccessIdentID = 0; u32 m_AccessIdentID = 0;
// Shared across all ES instances.
static struct TitleContext
{
void Clear();
void DoState(PointerWrap& p);
void Update(const DiscIO::CNANDContentLoader& content_loader);
void Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketReader& ticket_);
IOS::ES::TicketReader ticket;
IOS::ES::TMDReader tmd;
bool active = false;
} m_title_context;
// For title installation (ioctls IOCTL_ES_ADDTITLE*). // For title installation (ioctls IOCTL_ES_ADDTITLE*).
IOS::ES::TMDReader m_addtitle_tmd; IOS::ES::TMDReader m_addtitle_tmd;
u32 m_addtitle_content_id = 0xFFFFFFFF; u32 m_addtitle_content_id = 0xFFFFFFFF;

View File

@ -212,6 +212,11 @@ bool TicketReader::IsValid() const
return true; return true;
} }
void TicketReader::DoState(PointerWrap& p)
{
p.Do(m_bytes);
}
u32 TicketReader::GetNumberOfTickets() const u32 TicketReader::GetNumberOfTickets() const
{ {
return static_cast<u32>(m_bytes.size() / (GetOffset() + sizeof(Ticket))); return static_cast<u32>(m_bytes.size() / (GetOffset() + sizeof(Ticket)));

View File

@ -160,6 +160,7 @@ public:
void SetBytes(std::vector<u8>&& bytes); void SetBytes(std::vector<u8>&& bytes);
bool IsValid() const; bool IsValid() const;
void DoState(PointerWrap& p);
const std::vector<u8>& GetRawTicket() const; const std::vector<u8>& GetRawTicket() const;
u32 GetNumberOfTickets() const; u32 GetNumberOfTickets() const;

View File

@ -752,13 +752,13 @@ bool BootstrapPPC(const DiscIO::CNANDContentLoader& content_loader)
void SetDefaultContentFile(const std::string& file_name) void SetDefaultContentFile(const std::string& file_name)
{ {
std::lock_guard<std::mutex> lock(s_device_map_mutex); std::lock_guard<std::mutex> lock(s_device_map_mutex);
for (const auto& es : s_es_handles) s_es_handles[0]->LoadWAD(file_name);
es->LoadWAD(file_name);
} }
void ES_DIVerify(const ES::TMDReader& tmd) // XXX: also pass certificate chains?
void ES_DIVerify(const ES::TMDReader& tmd, const ES::TicketReader& ticket)
{ {
Device::ES::ES_DIVerify(tmd); Device::ES::DIVerify(tmd, ticket);
} }
void SDIO_EventNotify() void SDIO_EventNotify()

View File

@ -24,6 +24,7 @@ namespace IOS
namespace ES namespace ES
{ {
class TMDReader; class TMDReader;
class TicketReader;
} }
namespace HLE namespace HLE
@ -75,7 +76,7 @@ void DoState(PointerWrap& p);
// Set default content file // Set default content file
void SetDefaultContentFile(const std::string& file_name); void SetDefaultContentFile(const std::string& file_name);
void ES_DIVerify(const ES::TMDReader& tmd); void ES_DIVerify(const ES::TMDReader& tmd, const ES::TicketReader& ticket);
void SDIO_EventNotify(); void SDIO_EventNotify();

View File

@ -71,7 +71,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread; static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system // Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 78; // Last changed in PR 49XX static const u32 STATE_VERSION = 79; // Last changed in PR 4981
// Maps savestate versions to Dolphin versions. // Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list, // Versions after 42 don't need to be added to this list,

View File

@ -37,6 +37,7 @@ public:
} }
virtual bool GetTitleID(u64*) const { return false; } virtual bool GetTitleID(u64*) const { return false; }
virtual IOS::ES::TicketReader GetTicket() const { return {}; }
virtual IOS::ES::TMDReader GetTMD() const { return {}; } virtual IOS::ES::TMDReader GetTMD() const { return {}; }
virtual u64 PartitionOffsetToRawOffset(u64 offset) const { return offset; } virtual u64 PartitionOffsetToRawOffset(u64 offset) const { return offset; }
virtual std::string GetGameID() const = 0; virtual std::string GetGameID() const = 0;

View File

@ -114,6 +114,13 @@ bool CVolumeWiiCrypted::GetTitleID(u64* buffer) const
return true; return true;
} }
IOS::ES::TicketReader CVolumeWiiCrypted::GetTicket() const
{
std::vector<u8> buffer(0x2a4);
Read(m_VolumeOffset, buffer.size(), buffer.data(), false);
return IOS::ES::TicketReader{std::move(buffer)};
}
IOS::ES::TMDReader CVolumeWiiCrypted::GetTMD() const IOS::ES::TMDReader CVolumeWiiCrypted::GetTMD() const
{ {
u32 tmd_size; u32 tmd_size;

View File

@ -33,6 +33,7 @@ public:
~CVolumeWiiCrypted(); ~CVolumeWiiCrypted();
bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const override; bool Read(u64 _Offset, u64 _Length, u8* _pBuffer, bool decrypt) const override;
bool GetTitleID(u64* buffer) const override; bool GetTitleID(u64* buffer) const override;
IOS::ES::TicketReader GetTicket() const override;
IOS::ES::TMDReader GetTMD() const override; IOS::ES::TMDReader GetTMD() const override;
u64 PartitionOffsetToRawOffset(u64 offset) const override; u64 PartitionOffsetToRawOffset(u64 offset) const override;
std::string GetGameID() const override; std::string GetGameID() const override;