[Cleanup] Remove static state in ES

This commit is contained in:
Léo Lam 2017-10-01 16:28:46 +02:00
parent 9000a042e4
commit 0476c0e60e
9 changed files with 43 additions and 84 deletions

View File

@ -389,7 +389,7 @@ bool CBoot::EmulatedBS2_Wii(const DiscIO::Volume& volume)
// Warning: This call will set incorrect running game metadata if our volume parameter // Warning: This call will set incorrect running game metadata if our volume parameter
// doesn't point to the same disc as the one that's inserted in the emulated disc drive! // doesn't point to the same disc as the one that's inserted in the emulated disc drive!
IOS::HLE::Device::ES::DIVerify(tmd, volume.GetTicket(partition)); IOS::HLE::GetIOS()->GetES()->DIVerify(tmd, volume.GetTicket(partition));
return true; return true;
} }

View File

@ -113,7 +113,7 @@ IPCCommandResult DI::IOCtlV(const IOCtlVRequest& request)
const IOS::ES::TMDReader tmd = DVDThread::GetTMD(partition); const IOS::ES::TMDReader tmd = DVDThread::GetTMD(partition);
const std::vector<u8>& raw_tmd = tmd.GetBytes(); const std::vector<u8>& raw_tmd = tmd.GetBytes();
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, DVDThread::GetTicket(partition)); m_ios.GetES()->DIVerify(tmd, DVDThread::GetTicket(partition));
return_value = 1; return_value = 1;
break; break;

View File

@ -35,10 +35,6 @@ namespace HLE
{ {
namespace Device namespace Device
{ {
// TODO: drop this and convert the title context into a member once the WAD launch hack is gone.
static std::string s_content_file;
static TitleContext s_title_context;
// Title to launch after IOS has been reset and reloaded (similar to /sys/launch.sys). // Title to launch after IOS has been reset and reloaded (similar to /sys/launch.sys).
static u64 s_title_to_launch; static u64 s_title_to_launch;
@ -84,9 +80,6 @@ ES::ES(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
FinishAllStaleImports(); FinishAllStaleImports();
s_content_file = "";
s_title_context = TitleContext{};
if (s_title_to_launch != 0) if (s_title_to_launch != 0)
{ {
NOTICE_LOG(IOS, "Re-launching title after IOS reload."); NOTICE_LOG(IOS, "Re-launching title after IOS reload.");
@ -95,11 +88,6 @@ ES::ES(Kernel& ios, const std::string& device_name) : Device(ios, device_name)
} }
} }
TitleContext& ES::GetTitleContext()
{
return s_title_context;
}
void TitleContext::Clear() void TitleContext::Clear()
{ {
ticket.SetBytes({}); ticket.SetBytes({});
@ -114,13 +102,6 @@ void TitleContext::DoState(PointerWrap& p)
p.Do(active); p.Do(active);
} }
void TitleContext::Update(const DiscIO::NANDContentLoader& content_loader)
{
if (!content_loader.IsValid())
return;
Update(content_loader.GetTMD(), content_loader.GetTicket());
}
void TitleContext::Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketReader& ticket_) void TitleContext::Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketReader& ticket_)
{ {
if (!tmd_.IsValid() || !ticket_.IsValid()) if (!tmd_.IsValid() || !ticket_.IsValid())
@ -141,16 +122,6 @@ void TitleContext::Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketR
} }
} }
void ES::LoadWAD(const std::string& _rContentFile)
{
s_content_file = _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::NANDContentManager::Access().GetNANDLoader(s_content_file);
s_title_context.Update(content_loader);
INFO_LOG(IOS_ES, "LoadWAD: Title context changed: %016" PRIx64, s_title_context.tmd.GetTitleId());
}
IPCCommandResult ES::GetTitleDirectory(const IOCtlVRequest& request) IPCCommandResult ES::GetTitleDirectory(const IOCtlVRequest& request)
{ {
if (!request.HasNumberOfValidVectors(1, 1)) if (!request.HasNumberOfValidVectors(1, 1))
@ -167,9 +138,9 @@ IPCCommandResult ES::GetTitleDirectory(const IOCtlVRequest& request)
ReturnCode ES::GetTitleId(u64* title_id) const ReturnCode ES::GetTitleId(u64* title_id) const
{ {
if (!s_title_context.active) if (!m_title_context.active)
return ES_EINVAL; return ES_EINVAL;
*title_id = s_title_context.tmd.GetTitleId(); *title_id = m_title_context.tmd.GetTitleId();
return IPC_SUCCESS; return IPC_SUCCESS;
} }
@ -242,7 +213,7 @@ IPCCommandResult ES::SetUID(u32 uid, const IOCtlVRequest& request)
bool ES::LaunchTitle(u64 title_id, bool skip_reload) bool ES::LaunchTitle(u64 title_id, bool skip_reload)
{ {
s_title_context.Clear(); m_title_context.Clear();
INFO_LOG(IOS_ES, "ES_Launch: Title context changed: (none)"); INFO_LOG(IOS_ES, "ES_Launch: Title context changed: (none)");
NOTICE_LOG(IOS_ES, "Launching title %016" PRIx64 "...", title_id); NOTICE_LOG(IOS_ES, "Launching title %016" PRIx64 "...", title_id);
@ -310,15 +281,15 @@ bool ES::LaunchPPCTitle(u64 title_id, bool skip_reload)
return LaunchTitle(required_ios); return LaunchTitle(required_ios);
} }
s_title_context.Update(content_loader); m_title_context.Update(content_loader.GetTMD(), content_loader.GetTicket());
INFO_LOG(IOS_ES, "LaunchPPCTitle: Title context changed: %016" PRIx64, INFO_LOG(IOS_ES, "LaunchPPCTitle: Title context changed: %016" PRIx64,
s_title_context.tmd.GetTitleId()); m_title_context.tmd.GetTitleId());
// Note: the UID/GID is also updated for IOS titles, but since we have no guarantee IOS titles // Note: the UID/GID is also updated for IOS titles, but since we have no guarantee IOS titles
// are installed, we can only do this for PPC titles. // are installed, we can only do this for PPC titles.
if (!UpdateUIDAndGID(m_ios, s_title_context.tmd)) if (!UpdateUIDAndGID(m_ios, m_title_context.tmd))
{ {
s_title_context.Clear(); m_title_context.Clear();
INFO_LOG(IOS_ES, "LaunchPPCTitle: Title context changed: (none)"); INFO_LOG(IOS_ES, "LaunchPPCTitle: Title context changed: (none)");
return false; return false;
} }
@ -339,9 +310,8 @@ void ES::Context::DoState(PointerWrap& p)
void ES::DoState(PointerWrap& p) void ES::DoState(PointerWrap& p)
{ {
Device::DoState(p); Device::DoState(p);
p.Do(s_content_file);
p.Do(m_content_table); p.Do(m_content_table);
s_title_context.DoState(p); m_title_context.DoState(p);
for (auto& context : m_contexts) for (auto& context : m_contexts)
context.DoState(p); context.DoState(p);
@ -605,16 +575,6 @@ IPCCommandResult ES::LaunchBC(const IOCtlVRequest& request)
const DiscIO::NANDContentLoader& ES::AccessContentDevice(u64 title_id) const DiscIO::NANDContentLoader& ES::AccessContentDevice(u64 title_id)
{ {
// for WADs, the passed title id and the stored title id match; along with s_content_file
// being set to the actual WAD file name. We cannot simply get a NAND Loader for the title id
// in those cases, since the WAD need not be installed in the NAND, but it could be opened
// directly from a WAD file anywhere on disk.
if (s_title_context.active && s_title_context.tmd.GetTitleId() == title_id &&
!s_content_file.empty())
{
return DiscIO::NANDContentManager::Access().GetNANDLoader(s_content_file);
}
return DiscIO::NANDContentManager::Access().GetNANDLoader(title_id, Common::FROM_SESSION_ROOT); return DiscIO::NANDContentManager::Access().GetNANDLoader(title_id, Common::FROM_SESSION_ROOT);
} }
@ -628,7 +588,7 @@ IPCCommandResult ES::DIVerify(const IOCtlVRequest& request)
s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& ticket) s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& ticket)
{ {
s_title_context.Clear(); m_title_context.Clear();
INFO_LOG(IOS_ES, "ES_DIVerify: Title context changed: (none)"); INFO_LOG(IOS_ES, "ES_DIVerify: Title context changed: (none)");
if (!tmd.IsValid() || !ticket.IsValid()) if (!tmd.IsValid() || !ticket.IsValid())
@ -637,7 +597,7 @@ s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& tic
if (tmd.GetTitleId() != ticket.GetTitleId()) if (tmd.GetTitleId() != ticket.GetTitleId())
return ES_EINVAL; return ES_EINVAL;
s_title_context.Update(tmd, ticket); m_title_context.Update(tmd, ticket);
INFO_LOG(IOS_ES, "ES_DIVerify: Title context changed: %016" PRIx64, tmd.GetTitleId()); INFO_LOG(IOS_ES, "ES_DIVerify: Title context changed: %016" PRIx64, tmd.GetTitleId());
std::string tmd_path = Common::GetTMDFileName(tmd.GetTitleId(), Common::FROM_SESSION_ROOT); std::string tmd_path = Common::GetTMDFileName(tmd.GetTitleId(), Common::FROM_SESSION_ROOT);
@ -659,7 +619,7 @@ s32 ES::DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& tic
// clear the cache to avoid content access mismatches. // clear the cache to avoid content access mismatches.
DiscIO::NANDContentManager::Access().ClearCache(); DiscIO::NANDContentManager::Access().ClearCache();
if (!UpdateUIDAndGID(*GetIOS(), s_title_context.tmd)) if (!UpdateUIDAndGID(*GetIOS(), m_title_context.tmd))
{ {
return ES_SHORT_READ; return ES_SHORT_READ;
} }
@ -803,10 +763,10 @@ IPCCommandResult ES::DeleteStreamKey(const IOCtlVRequest& request)
bool ES::IsActiveTitlePermittedByTicket(const u8* ticket_view) const bool ES::IsActiveTitlePermittedByTicket(const u8* ticket_view) const
{ {
if (!GetTitleContext().active) if (!m_title_context.active)
return false; return false;
const u32 title_identifier = static_cast<u32>(GetTitleContext().tmd.GetTitleId()); const u32 title_identifier = static_cast<u32>(m_title_context.tmd.GetTitleId());
const u32 permitted_title_mask = const u32 permitted_title_mask =
Common::swap32(ticket_view + offsetof(IOS::ES::TicketView, permitted_title_mask)); Common::swap32(ticket_view + offsetof(IOS::ES::TicketView, permitted_title_mask));
const u32 permitted_title_id = const u32 permitted_title_id =

View File

@ -32,7 +32,6 @@ struct TitleContext
{ {
void Clear(); void Clear();
void DoState(PointerWrap& p); void DoState(PointerWrap& p);
void Update(const DiscIO::NANDContentLoader& content_loader);
void Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketReader& ticket_); void Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketReader& ticket_);
IOS::ES::TicketReader ticket; IOS::ES::TicketReader ticket;
@ -46,8 +45,7 @@ class ES final : public Device
public: public:
ES(Kernel& ios, const std::string& device_name); ES(Kernel& ios, const std::string& device_name);
static s32 DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& ticket); s32 DIVerify(const IOS::ES::TMDReader& tmd, const IOS::ES::TicketReader& ticket);
static void LoadWAD(const std::string& _rContentFile);
bool LaunchTitle(u64 title_id, bool skip_reload = false); bool LaunchTitle(u64 title_id, bool skip_reload = false);
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;
@ -306,7 +304,6 @@ private:
bool LaunchIOS(u64 ios_title_id); bool LaunchIOS(u64 ios_title_id);
bool LaunchPPCTitle(u64 title_id, bool skip_reload); bool LaunchPPCTitle(u64 title_id, bool skip_reload);
static TitleContext& GetTitleContext();
bool IsActiveTitlePermittedByTicket(const u8* ticket_view) const; bool IsActiveTitlePermittedByTicket(const u8* ticket_view) const;
ReturnCode CheckStreamKeyPermissions(u32 uid, const u8* ticket_view, ReturnCode CheckStreamKeyPermissions(u32 uid, const u8* ticket_view,
@ -341,7 +338,8 @@ private:
void FinishStaleImport(u64 title_id); void FinishStaleImport(u64 title_id);
void FinishAllStaleImports(); void FinishAllStaleImports();
static const DiscIO::NANDContentLoader& AccessContentDevice(u64 title_id); // TODO: remove these
const DiscIO::NANDContentLoader& AccessContentDevice(u64 title_id);
// TODO: reuse the FS code. // TODO: reuse the FS code.
struct OpenedContent struct OpenedContent
@ -357,6 +355,7 @@ private:
ContentTable m_content_table; ContentTable m_content_table;
ContextArray m_contexts; ContextArray m_contexts;
TitleContext m_title_context{};
}; };
} // namespace Device } // namespace Device
} // namespace HLE } // namespace HLE

View File

@ -110,11 +110,11 @@ 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 (!GetTitleContext().active) if (!m_title_context.active)
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
const EcWii& ec = EcWii::GetInstance(); const EcWii& ec = EcWii::GetInstance();
MakeAPSigAndCert(sig_out, ap_cert_out, GetTitleContext().tmd.GetTitleId(), data, data_size, MakeAPSigAndCert(sig_out, ap_cert_out, m_title_context.tmd.GetTitleId(), data, data_size,
ec.GetNGPriv(), ec.GetNGID()); ec.GetNGPriv(), ec.GetNGID());
return GetDefaultReply(IPC_SUCCESS); return GetDefaultReply(IPC_SUCCESS);

View File

@ -77,15 +77,15 @@ IPCCommandResult ES::OpenActiveTitleContent(u32 caller_uid, const IOCtlVRequest&
const u32 content_index = Memory::Read_U32(request.in_vectors[0].address); const u32 content_index = Memory::Read_U32(request.in_vectors[0].address);
if (!GetTitleContext().active) if (!m_title_context.active)
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
IOS::ES::UIDSys uid_map{Common::FROM_SESSION_ROOT}; IOS::ES::UIDSys uid_map{Common::FROM_SESSION_ROOT};
const u32 uid = uid_map.GetOrInsertUIDForTitle(GetTitleContext().tmd.GetTitleId()); const u32 uid = uid_map.GetOrInsertUIDForTitle(m_title_context.tmd.GetTitleId());
if (caller_uid != 0 && caller_uid != uid) if (caller_uid != 0 && caller_uid != uid)
return GetDefaultReply(ES_EACCES); return GetDefaultReply(ES_EACCES);
return GetDefaultReply(OpenContent(GetTitleContext().tmd, content_index, caller_uid)); return GetDefaultReply(OpenContent(m_title_context.tmd, content_index, caller_uid));
} }
s32 ES::ReadContent(u32 cfd, u8* buffer, u32 size, u32 uid) s32 ES::ReadContent(u32 cfd, u8* buffer, u32 size, u32 uid)

View File

@ -160,8 +160,8 @@ ReturnCode ES::ImportTmd(Context& context, const std::vector<u8>& tmd_bytes)
if (!InitImport(context.title_import_export.tmd.GetTitleId())) if (!InitImport(context.title_import_export.tmd.GetTitleId()))
return ES_EIO; return ES_EIO;
ret = InitBackupKey(GetTitleContext().tmd, m_ios.GetIOSC(), ret =
&context.title_import_export.key_handle); InitBackupKey(m_title_context.tmd, m_ios.GetIOSC(), &context.title_import_export.key_handle);
if (ret != IPC_SUCCESS) if (ret != IPC_SUCCESS)
return ret; return ret;
@ -639,8 +639,8 @@ ReturnCode ES::ExportTitleInit(Context& context, u64 title_id, u8* tmd_bytes, u3
ResetTitleImportContext(&context, m_ios.GetIOSC()); ResetTitleImportContext(&context, m_ios.GetIOSC());
context.title_import_export.tmd = tmd; context.title_import_export.tmd = tmd;
const ReturnCode ret = InitBackupKey(GetTitleContext().tmd, m_ios.GetIOSC(), const ReturnCode ret =
&context.title_import_export.key_handle); InitBackupKey(m_title_context.tmd, m_ios.GetIOSC(), &context.title_import_export.key_handle);
if (ret != IPC_SUCCESS) if (ret != IPC_SUCCESS)
return ret; return ret;

View File

@ -52,7 +52,7 @@ IPCCommandResult ES::GetTicketViewCount(const IOCtlVRequest& request)
const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(TitleID); const IOS::ES::TicketReader ticket = DiscIO::FindSignedTicket(TitleID);
u32 view_count = ticket.IsValid() ? static_cast<u32>(ticket.GetNumberOfTickets()) : 0; u32 view_count = ticket.IsValid() ? static_cast<u32>(ticket.GetNumberOfTickets()) : 0;
if (ShouldReturnFakeViewsForIOSes(TitleID, GetTitleContext())) if (ShouldReturnFakeViewsForIOSes(TitleID, m_title_context))
{ {
view_count = 1; view_count = 1;
WARN_LOG(IOS_ES, "GetViewCount: Faking IOS title %016" PRIx64 " being present", TitleID); WARN_LOG(IOS_ES, "GetViewCount: Faking IOS title %016" PRIx64 " being present", TitleID);
@ -85,7 +85,7 @@ IPCCommandResult ES::GetTicketViews(const IOCtlVRequest& request)
ticket_view.data(), ticket_view.size()); ticket_view.data(), ticket_view.size());
} }
} }
else if (ShouldReturnFakeViewsForIOSes(TitleID, GetTitleContext())) else if (ShouldReturnFakeViewsForIOSes(TitleID, m_title_context))
{ {
Memory::Memset(request.io_vectors[0].address, 0, sizeof(IOS::ES::TicketView)); Memory::Memset(request.io_vectors[0].address, 0, sizeof(IOS::ES::TicketView));
WARN_LOG(IOS_ES, "GetViews: Faking IOS title %016" PRIx64 " being present", TitleID); WARN_LOG(IOS_ES, "GetViews: Faking IOS title %016" PRIx64 " being present", TitleID);
@ -112,11 +112,11 @@ ReturnCode ES::GetV0TicketFromView(const u8* ticket_view, u8* ticket) const
if (ticket_bytes.empty()) if (ticket_bytes.empty())
return ES_NO_TICKET; return ES_NO_TICKET;
if (!GetTitleContext().active) if (!m_title_context.active)
return ES_EINVAL; return ES_EINVAL;
// Check for permission to export the ticket. // Check for permission to export the ticket.
const u32 title_identifier = static_cast<u32>(GetTitleContext().tmd.GetTitleId()); const u32 title_identifier = static_cast<u32>(m_title_context.tmd.GetTitleId());
const u32 permitted_title_mask = const u32 permitted_title_mask =
Common::swap32(ticket_bytes.data() + offsetof(IOS::ES::Ticket, permitted_title_mask)); Common::swap32(ticket_bytes.data() + offsetof(IOS::ES::Ticket, permitted_title_mask));
const u32 permitted_title_id = const u32 permitted_title_id =
@ -276,10 +276,10 @@ IPCCommandResult ES::DIGetTMDViewSize(const IOCtlVRequest& request)
else else
{ {
// If no TMD was passed in and no title is active, IOS returns -1017. // If no TMD was passed in and no title is active, IOS returns -1017.
if (!GetTitleContext().active) if (!m_title_context.active)
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
tmd_view_size = GetTitleContext().tmd.GetRawView().size(); tmd_view_size = m_title_context.tmd.GetRawView().size();
} }
Memory::Write_U32(static_cast<u32>(tmd_view_size), request.io_vectors[0].address); Memory::Write_U32(static_cast<u32>(tmd_view_size), request.io_vectors[0].address);
@ -319,10 +319,10 @@ IPCCommandResult ES::DIGetTMDView(const IOCtlVRequest& request)
else else
{ {
// If no TMD was passed in and no title is active, IOS returns -1017. // If no TMD was passed in and no title is active, IOS returns -1017.
if (!GetTitleContext().active) if (!m_title_context.active)
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
tmd_view = GetTitleContext().tmd.GetRawView(); tmd_view = m_title_context.tmd.GetRawView();
} }
if (tmd_view.size() > request.io_vectors[0].size) if (tmd_view.size() > request.io_vectors[0].size)
@ -352,10 +352,10 @@ IPCCommandResult ES::DIGetTicketView(const IOCtlVRequest& request)
// Of course, this returns -1017 if no title is active and no ticket is passed. // Of course, this returns -1017 if no title is active and no ticket is passed.
if (!has_ticket_vector) if (!has_ticket_vector)
{ {
if (!GetTitleContext().active) if (!m_title_context.active)
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
view = GetTitleContext().ticket.GetRawTicketView(0); view = m_title_context.ticket.GetRawTicketView(0);
} }
else else
{ {
@ -375,10 +375,10 @@ IPCCommandResult ES::DIGetTMDSize(const IOCtlVRequest& request)
if (!request.HasNumberOfValidVectors(0, 1) || request.io_vectors[0].size != sizeof(u32)) if (!request.HasNumberOfValidVectors(0, 1) || request.io_vectors[0].size != sizeof(u32))
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
if (!GetTitleContext().active) if (!m_title_context.active)
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
Memory::Write_U32(static_cast<u32>(GetTitleContext().tmd.GetBytes().size()), Memory::Write_U32(static_cast<u32>(m_title_context.tmd.GetBytes().size()),
request.io_vectors[0].address); request.io_vectors[0].address);
return GetDefaultReply(IPC_SUCCESS); return GetDefaultReply(IPC_SUCCESS);
} }
@ -392,10 +392,10 @@ IPCCommandResult ES::DIGetTMD(const IOCtlVRequest& request)
if (tmd_size != request.io_vectors[0].size) if (tmd_size != request.io_vectors[0].size)
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
if (!GetTitleContext().active) if (!m_title_context.active)
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
const std::vector<u8>& tmd_bytes = GetTitleContext().tmd.GetBytes(); const std::vector<u8>& tmd_bytes = m_title_context.tmd.GetBytes();
if (static_cast<u32>(tmd_bytes.size()) > tmd_size) if (static_cast<u32>(tmd_bytes.size()) > tmd_size)
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);

View File

@ -74,7 +74,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 = 90; // Last changed in PR 6077 static const u32 STATE_VERSION = 91; // Last changed in PR 6094
// 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,