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!");
}
IOS::HLE::ES_DIVerify(pVolume.GetTMD());
_StartupPara.bWii = pVolume.GetVolumeType() == DiscIO::Platform::WII_DISC;
// HLE BS2 or not

View File

@ -311,6 +311,11 @@ bool CBoot::EmulatedBS2_Wii()
if (DVDInterface::GetVolume().GetVolumeType() != DiscIO::Platform::WII_DISC)
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
// 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
@ -346,11 +351,6 @@ bool CBoot::EmulatedBS2_Wii()
PowerPC::ppcState.gpr[1] = 0x816ffff0; // StackPointer
IOS::ES::TMDReader tmd = DVDInterface::GetVolume().GetTMD();
if (!SetupWiiMemory(tmd.GetIOSId()))
return false;
// Execute the apploader
const u32 apploader_offset = 0x2440; // 0x1c40;
@ -383,11 +383,7 @@ bool CBoot::EmulatedBS2_Wii()
PowerPC::ppcState.gpr[3] = 0x81300000;
RunFunction(iAppLoaderInit);
// Let the apploader load the exe to memory. At this point I get an unknown IPC command
// (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.
// Let the apploader load the exe to memory
DEBUG_LOG(BOOT, "Run iAppLoaderMain");
do
{
@ -413,6 +409,8 @@ bool CBoot::EmulatedBS2_Wii()
// Load patches and run startup patches
PatchEngine::LoadPatches();
IOS::HLE::ES_DIVerify(tmd, DVDInterface::GetVolume().GetTicket());
// return
PC = PowerPC::ppcState.gpr[3];
return true;

View File

@ -109,7 +109,7 @@ IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request)
const ES::TMDReader tmd = DVDInterface::GetVolume().GetTMD();
const std::vector<u8> raw_tmd = tmd.GetRawTMD();
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;
break;

View File

@ -39,6 +39,7 @@ namespace HLE
namespace Device
{
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,
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)
{
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)
{
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)
@ -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
{
m_title_context.Clear();
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.
@ -120,47 +170,18 @@ bool ES::LaunchPPCTitle(u64 title_id, bool skip_reload) const
return LaunchTitle(required_ios);
}
m_title_context.Update(content_loader);
SetDefaultContentFile(Common::GetTitleContentPath(title_id, Common::FROM_SESSION_ROOT));
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)
{
Device::DoState(p);
p.Do(m_ContentFile);
OpenInternal();
p.Do(m_AccessIdentID);
p.Do(m_TitleIDs);
m_title_context.DoState(p);
m_addtitle_tmd.DoState(p);
p.Do(m_addtitle_content_id);
@ -197,8 +218,6 @@ void ES::DoState(PointerWrap& p)
ReturnCode ES::Open(const OpenRequest& request)
{
OpenInternal();
if (m_is_active)
INFO_LOG(IOS_ES, "Device was re-opened.");
return Device::Open(request);
@ -206,9 +225,8 @@ ReturnCode ES::Open(const OpenRequest& request)
void ES::Close()
{
// XXX: does IOS really clear the content access map here?
m_ContentAccessMap.clear();
m_TitleIDs.clear();
m_TitleID = -1;
m_AccessIdentID = 0;
INFO_LOG(IOS_ES, "ES: Close");
@ -628,7 +646,10 @@ IPCCommandResult ES::OpenContent(const IOCtlVRequest& request)
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
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);
return GetDefaultReply(CFD);
@ -771,8 +792,13 @@ IPCCommandResult ES::GetTitleID(const IOCtlVRequest& request)
if (!request.HasNumberOfValidVectors(0, 1))
return GetDefaultReply(ES_PARAMETER_SIZE_OR_ALIGNMENT);
Memory::Write_U64(m_TitleID, request.io_vectors[0].address);
INFO_LOG(IOS_ES, "IOCTL_ES_GETTITLEID: %08x/%08x", (u32)(m_TitleID >> 32), (u32)m_TitleID);
if (!m_title_context.active)
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);
}
@ -1327,8 +1353,12 @@ IPCCommandResult ES::Sign(const IOCtlVRequest& request)
u32 data_size = request.in_vectors[0].size;
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();
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);
}
@ -1373,28 +1403,27 @@ const DiscIO::CNANDContentLoader& ES::AccessContentDevice(u64 title_id) const
// the WAD
// need not be installed in the NAND, but it could be opened directly from a WAD file anywhere on
// 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(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())
return -1;
m_title_context.Clear();
u64 title_id = 0xDEADBEEFDEADBEEFull;
u64 tmd_title_id = tmd.GetTitleId();
if (!tmd.IsValid() || !ticket.IsValid())
return ES_PARAMETER_SIZE_OR_ALIGNMENT;
DVDInterface::GetVolume().GetTitleID(&title_id);
if (title_id != tmd_title_id)
return -1;
if (tmd.GetTitleId() != ticket.GetTitleId())
return ES_PARAMETER_SIZE_OR_ALIGNMENT;
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(Common::GetTitleDataPath(tmd_title_id, Common::FROM_SESSION_ROOT));
File::CreateFullPath(Common::GetTitleDataPath(tmd.GetTitleId(), Common::FROM_SESSION_ROOT));
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.");
}
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.
// clear the cache to avoid content access mismatches.
DiscIO::CNANDContentManager::Access().ClearCache();
return 0;
m_title_context.Update(tmd, ticket);
return IPC_SUCCESS;
}
} // namespace Device
} // namespace HLE

View File

@ -40,15 +40,13 @@ public:
// Internal implementation of the ES_DECRYPT ioctlv.
void DecryptContent(u32 key_index, u8* iv, u8* input, u32 size, u8* new_iv, u8* output);
void OpenInternal();
void DoState(PointerWrap& p) override;
ReturnCode Open(const OpenRequest& request) override;
void Close() 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
static std::string m_ContentFile;
@ -211,9 +209,21 @@ private:
ContentAccessMap m_ContentAccessMap;
std::vector<u64> m_TitleIDs;
u64 m_TitleID = -1;
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*).
IOS::ES::TMDReader m_addtitle_tmd;
u32 m_addtitle_content_id = 0xFFFFFFFF;

View File

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

View File

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

View File

@ -752,13 +752,13 @@ bool BootstrapPPC(const DiscIO::CNANDContentLoader& content_loader)
void SetDefaultContentFile(const std::string& file_name)
{
std::lock_guard<std::mutex> lock(s_device_map_mutex);
for (const auto& es : s_es_handles)
es->LoadWAD(file_name);
s_es_handles[0]->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()

View File

@ -24,6 +24,7 @@ namespace IOS
namespace ES
{
class TMDReader;
class TicketReader;
}
namespace HLE
@ -75,7 +76,7 @@ void DoState(PointerWrap& p);
// Set default content file
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();

View File

@ -71,7 +71,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// 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.
// 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 IOS::ES::TicketReader GetTicket() const { return {}; }
virtual IOS::ES::TMDReader GetTMD() const { return {}; }
virtual u64 PartitionOffsetToRawOffset(u64 offset) const { return offset; }
virtual std::string GetGameID() const = 0;

View File

@ -114,6 +114,13 @@ bool CVolumeWiiCrypted::GetTitleID(u64* buffer) const
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
{
u32 tmd_size;

View File

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