Merge pull request #5045 from JosJuice/unify-setting-game-metadata

Unify the way of setting game ID, title ID, revision
This commit is contained in:
Anthony 2017-03-09 18:13:02 +00:00 committed by GitHub
commit cf848b7c42
22 changed files with 135 additions and 123 deletions

View File

@ -415,8 +415,7 @@ void Stop()
void RestoreConfig()
{
SConfig::GetInstance().LoadSettingsFromSysconf();
SConfig::GetInstance().m_strGameID = "00000000";
SConfig::GetInstance().m_title_id = 0;
SConfig::GetInstance().ResetRunningGameMetadata();
config_cache.RestoreConfig(&SConfig::GetInstance());
}

View File

@ -21,11 +21,18 @@
#include "Core/Boot/Boot.h"
#include "Core/Boot/Boot_DOL.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h" // for bWii
#include "Core/Core.h"
#include "Core/FifoPlayer/FifoDataFile.h"
#include "Core/HLE/HLE.h"
#include "Core/HW/DVDInterface.h"
#include "Core/HW/DVDThread.h"
#include "Core/HW/SI/SI.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/IOS/USB/Bluetooth/BTBase.h"
#include "Core/PatchEngine.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/PowerPC/PowerPC.h"
#include "VideoCommon/HiresTextures.h"
#include "DiscIO/Enums.h"
#include "DiscIO/NANDContentLoader.h"
@ -726,6 +733,68 @@ void SConfig::LoadSettingsFromSysconf()
bPAL60 = sysconf.GetData<u8>("IPL.E60") != 0;
}
void SConfig::ResetRunningGameMetadata()
{
SetRunningGameMetadata("00000000", 0, 0);
}
void SConfig::SetRunningGameMetadata(const DiscIO::IVolume& volume)
{
u64 title_id = 0;
volume.GetTitleID(&title_id);
SetRunningGameMetadata(volume.GetGameID(), title_id, volume.GetRevision());
}
void SConfig::SetRunningGameMetadata(const IOS::ES::TMDReader& tmd)
{
const u64 tmd_title_id = tmd.GetTitleId();
// If we're launching a disc game, we want to read the revision from
// the disc header instead of the TMD. They can differ.
// (IOS HLE ES calls us with a TMDReader rather than a volume when launching
// a disc game, because ES has no reason to be accessing the disc directly.)
if (DVDInterface::VolumeIsValid())
{
DVDThread::WaitUntilIdle();
const DiscIO::IVolume& volume = DVDInterface::GetVolume();
u64 volume_title_id;
if (volume.GetTitleID(&volume_title_id) && volume_title_id == tmd_title_id)
{
SetRunningGameMetadata(volume.GetGameID(), volume_title_id, volume.GetRevision());
return;
}
}
// If not launching a disc game, just read everything from the TMD.
SetRunningGameMetadata(StringFromFormat("%016" PRIX64, tmd_title_id), tmd_title_id,
tmd.GetTitleVersion());
}
void SConfig::SetRunningGameMetadata(const std::string& game_id, u64 title_id, u16 revision)
{
const bool was_changed = m_game_id != game_id || m_title_id != title_id || m_revision != revision;
m_game_id = game_id;
m_title_id = title_id;
m_revision = revision;
if (was_changed)
{
NOTICE_LOG(BOOT, "Game ID set to %s", game_id.c_str());
if (Core::IsRunning())
{
// TODO: have a callback mechanism for title changes?
g_symbolDB.Clear();
CBoot::LoadMapFromFilename();
HLE::Clear();
HLE::PatchFunctions();
PatchEngine::Shutdown();
PatchEngine::LoadPatches();
HiresTexture::Update();
}
}
}
void SConfig::LoadDefaults()
{
bEnableDebugging = false;
@ -783,9 +852,7 @@ void SConfig::LoadDefaults()
bJITSystemRegistersOff = false;
bJITBranchOff = false;
m_strGameID = "00000000";
m_title_id = 0;
m_revision = 0;
ResetRunningGameMetadata();
}
bool SConfig::IsUSBDeviceWhitelisted(const std::pair<u16, u16> vid_pid) const
@ -870,9 +937,7 @@ bool SConfig::AutoSetup(EBootBS2 _BootBS2)
m_strFilename.c_str());
return false;
}
m_strGameID = pVolume->GetGameID();
pVolume->GetTitleID(&m_title_id);
m_revision = pVolume->GetRevision();
SetRunningGameMetadata(*pVolume);
// Check if we have a Wii disc
bWii = pVolume->GetVolumeType() == DiscIO::Platform::WII_DISC;
@ -916,11 +981,11 @@ bool SConfig::AutoSetup(EBootBS2 _BootBS2)
}
else if (DiscIO::CNANDContentManager::Access().GetNANDLoader(m_strFilename).IsValid())
{
std::unique_ptr<DiscIO::IVolume> pVolume(DiscIO::CreateVolumeFromFilename(m_strFilename));
const DiscIO::CNANDContentLoader& ContentLoader =
const DiscIO::CNANDContentLoader& content_loader =
DiscIO::CNANDContentManager::Access().GetNANDLoader(m_strFilename);
const IOS::ES::TMDReader& tmd = content_loader.GetTMD();
if (ContentLoader.GetContentByIndex(ContentLoader.GetTMD().GetBootIndex()) == nullptr)
if (content_loader.GetContentByIndex(tmd.GetBootIndex()) == nullptr)
{
// WAD is valid yet cannot be booted. Install instead.
u64 installed = DiscIO::CNANDContentManager::Access().Install_WiiWAD(m_strFilename);
@ -929,33 +994,11 @@ bool SConfig::AutoSetup(EBootBS2 _BootBS2)
return false; // do not boot
}
SetRegion(ContentLoader.GetTMD().GetRegion(), &set_region_dir);
SetRegion(tmd.GetRegion(), &set_region_dir);
SetRunningGameMetadata(tmd);
bWii = true;
m_BootType = BOOT_WII_NAND;
if (pVolume)
{
m_strGameID = pVolume->GetGameID();
pVolume->GetTitleID(&m_title_id);
}
else
{
// null pVolume means that we are loading from nand folder (Most Likely Wii Menu)
// if this is the second boot we would be using the Name and id of the last title
m_strGameID.clear();
m_title_id = 0;
}
// Use the TitleIDhex for name and/or game ID if launching
// from nand folder or if it is not ascii characters
// (specifically sysmenu could potentially apply to other things)
std::string titleidstr = StringFromFormat("%016" PRIx64, m_title_id);
if (m_strGameID.empty())
{
m_strGameID = titleidstr;
}
}
else
{

View File

@ -19,6 +19,14 @@ namespace DiscIO
{
enum class Language;
enum class Region;
class IVolume;
}
namespace IOS
{
namespace ES
{
class TMDReader;
}
}
// DSP Backend Types
@ -209,17 +217,20 @@ struct SConfig : NonCopyable
std::string m_strDefaultISO;
std::string m_strDVDRoot;
std::string m_strApploader;
std::string m_strGameID;
u64 m_title_id;
std::string m_strWiiSDCardPath;
u16 m_revision;
std::string m_perfDir;
const std::string& GetGameID() const { return m_game_id; }
u64 GetTitleID() const { return m_title_id; }
u16 GetRevision() const { return m_revision; }
void ResetRunningGameMetadata();
void SetRunningGameMetadata(const DiscIO::IVolume& volume);
void SetRunningGameMetadata(const IOS::ES::TMDReader& tmd);
void LoadDefaults();
static const char* GetDirectoryForRegion(DiscIO::Region region);
bool AutoSetup(EBootBS2 _BootBS2);
const std::string& GetGameID() const { return m_strGameID; }
void CheckMemcardPath(std::string& memcardPath, const std::string& gameRegion, bool isSlotA);
DiscIO::Language GetCurrentLanguage(bool wii) const;
@ -373,7 +384,12 @@ private:
void LoadUSBPassthroughSettings(IniFile& ini);
void LoadSysconfSettings(IniFile& ini);
void SetRunningGameMetadata(const std::string& game_id, u64 title_id, u16 revision);
bool SetRegion(DiscIO::Region region, std::string* directory_name);
static SConfig* m_Instance;
std::string m_game_id;
u64 m_title_id;
u16 m_revision;
};

View File

@ -156,7 +156,7 @@ void CEXIMemoryCard::SetupGciFolder(u16 sizeMb)
{
DiscIO::Region region = SConfig::GetInstance().m_region;
std::string game_id = SConfig::GetInstance().m_strGameID;
const std::string& game_id = SConfig::GetInstance().GetGameID();
u32 CurrentGameId = 0;
if (game_id.length() >= 4 && game_id != "00000000" && game_id != TITLEID_SYSMENU_STRING)
CurrentGameId = BE32((u8*)game_id.c_str());

View File

@ -24,17 +24,12 @@
#include "Common/MsgHandler.h"
#include "Common/NandPaths.h"
#include "Common/Swap.h"
#include "Core/Boot/Boot.h"
#include "Core/ConfigManager.h"
#include "Core/HLE/HLE.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/ES/Formats.h"
#include "Core/PatchEngine.h"
#include "Core/PowerPC/PPCSymbolDB.h"
#include "Core/ec_wii.h"
#include "DiscIO/NANDContentLoader.h"
#include "DiscIO/Volume.h"
#include "VideoCommon/HiresTextures.h"
namespace IOS
{
@ -48,7 +43,6 @@ struct TitleContext
void DoState(PointerWrap& p);
void Update(const DiscIO::CNANDContentLoader& content_loader);
void Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketReader& ticket_);
void UpdateRunningGame() const;
IOS::ES::TicketReader ticket;
IOS::ES::TMDReader tmd;
@ -86,12 +80,6 @@ constexpr const u8* s_key_table[11] = {
s_key_empty, // Unknown
};
static bool IsDiscTitle(u64 title_id)
{
return IsTitleType(title_id, IOS::ES::TitleType::Game) ||
IsTitleType(title_id, IOS::ES::TitleType::GameWithChannel);
}
ES::ES(u32 device_id, const std::string& device_name) : Device(device_id, device_name)
{
}
@ -145,43 +133,11 @@ void TitleContext::Update(const IOS::ES::TMDReader& tmd_, const IOS::ES::TicketR
// Interesting title changes (channel or disc game launch) always happen after an IOS reload.
if (first_change)
{
UpdateRunningGame();
SConfig::GetInstance().SetRunningGameMetadata(tmd);
first_change = false;
}
}
void TitleContext::UpdateRunningGame() const
{
if (IsDiscTitle(tmd.GetTitleId()))
{
const u32 title_identifier = Common::swap32(static_cast<u32>(tmd.GetTitleId()));
const u16 group_id = Common::swap16(tmd.GetGroupId());
char ascii_game_id[6];
std::memcpy(ascii_game_id, &title_identifier, sizeof(title_identifier));
std::memcpy(ascii_game_id + sizeof(title_identifier), &group_id, sizeof(group_id));
SConfig::GetInstance().m_strGameID = ascii_game_id;
}
else
{
SConfig::GetInstance().m_strGameID = StringFromFormat("%016" PRIX64, tmd.GetTitleId());
}
SConfig::GetInstance().m_title_id = tmd.GetTitleId();
// TODO: have a callback mechanism for title changes?
g_symbolDB.Clear();
CBoot::LoadMapFromFilename();
::HLE::Clear();
::HLE::PatchFunctions();
PatchEngine::Shutdown();
PatchEngine::LoadPatches();
HiresTexture::Update();
NOTICE_LOG(IOS_ES, "Active title: %016" PRIx64, tmd.GetTitleId());
}
void ES::LoadWAD(const std::string& _rContentFile)
{
s_content_file = _rContentFile;
@ -1040,7 +996,8 @@ IPCCommandResult ES::GetTitles(const IOCtlVRequest& request)
static bool ShouldReturnFakeViewsForIOSes(u64 title_id)
{
const bool ios = IsTitleType(title_id, IOS::ES::TitleType::System) && title_id != TITLEID_SYSMENU;
const bool disc_title = s_title_context.active && IsDiscTitle(s_title_context.tmd.GetTitleId());
const bool disc_title =
s_title_context.active && IOS::ES::IsDiscTitle(s_title_context.tmd.GetTitleId());
return ios && SConfig::GetInstance().m_BootType == SConfig::BOOT_ISO && disc_title;
}

View File

@ -26,6 +26,12 @@ bool IsTitleType(u64 title_id, TitleType title_type)
return static_cast<u32>(title_id >> 32) == static_cast<u32>(title_type);
}
bool IsDiscTitle(u64 title_id)
{
return IsTitleType(title_id, TitleType::Game) ||
IsTitleType(title_id, TitleType::GameWithChannel);
}
bool Content::IsShared() const
{
return (type & 0x8000) != 0;

View File

@ -30,6 +30,7 @@ enum class TitleType : u32
};
bool IsTitleType(u64 title_id, TitleType title_type);
bool IsDiscTitle(u64 title_id);
#pragma pack(push, 4)
struct TMDHeader

View File

@ -12,7 +12,6 @@
#include "Common/MsgHandler.h"
#include "Common/NandPaths.h"
#include "Common/Swap.h"
#include "Core/Boot/Boot.h"
#include "Core/Boot/ElfReader.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
@ -124,17 +123,8 @@ static void ReinitHardware()
static void UpdateRunningGame()
{
DVDThread::WaitUntilIdle();
const DiscIO::IVolume& volume = DVDInterface::GetVolume();
SConfig::GetInstance().m_BootType = SConfig::BOOT_MIOS;
SConfig::GetInstance().m_strGameID = volume.GetGameID();
SConfig::GetInstance().m_revision = volume.GetRevision();
g_symbolDB.Clear();
CBoot::LoadMapFromFilename();
::HLE::Clear();
::HLE::PatchFunctions();
NOTICE_LOG(IOS, "Running game: %s", SConfig::GetInstance().m_strGameID.c_str());
SConfig::GetInstance().SetRunningGameMetadata(DVDInterface::GetVolume());
}
constexpr u32 ADDRESS_INIT_SEMAPHORE = 0x30f8;

View File

@ -1477,7 +1477,7 @@ void GetSettings()
s_bNetPlay = NetPlay::IsNetPlayRunning();
if (SConfig::GetInstance().bWii)
{
u64 title_id = SConfig::GetInstance().m_title_id;
u64 title_id = SConfig::GetInstance().GetTitleID();
s_bClearSave =
!File::Exists(Common::GetTitleDataPath(title_id, Common::FROM_SESSION_ROOT) + "banner.bin");
s_language = SConfig::GetInstance().m_wii_language;

View File

@ -27,9 +27,9 @@ static std::string s_temp_wii_root;
static void InitializeDeterministicWiiSaves()
{
std::string save_path =
Common::GetTitleDataPath(SConfig::GetInstance().m_title_id, Common::FROM_SESSION_ROOT);
Common::GetTitleDataPath(SConfig::GetInstance().GetTitleID(), Common::FROM_SESSION_ROOT);
std::string user_save_path =
Common::GetTitleDataPath(SConfig::GetInstance().m_title_id, Common::FROM_CONFIGURED_ROOT);
Common::GetTitleDataPath(SConfig::GetInstance().GetTitleID(), Common::FROM_CONFIGURED_ROOT);
if (Movie::IsRecordingInput())
{
if (NetPlay::IsNetPlayRunning() && !SConfig::GetInstance().bCopyWiiSaveNetplay)
@ -93,13 +93,13 @@ void ShutdownWiiRoot()
if (!s_temp_wii_root.empty())
{
std::string save_path =
Common::GetTitleDataPath(SConfig::GetInstance().m_title_id, Common::FROM_SESSION_ROOT);
Common::GetTitleDataPath(SConfig::GetInstance().GetTitleID(), Common::FROM_SESSION_ROOT);
std::string user_save_path =
Common::GetTitleDataPath(SConfig::GetInstance().m_title_id, Common::FROM_CONFIGURED_ROOT);
Common::GetTitleDataPath(SConfig::GetInstance().GetTitleID(), Common::FROM_CONFIGURED_ROOT);
std::string user_backup_path =
File::GetUserPath(D_BACKUP_IDX) +
StringFromFormat("%08x/%08x/", static_cast<u32>(SConfig::GetInstance().m_title_id >> 32),
static_cast<u32>(SConfig::GetInstance().m_title_id));
StringFromFormat("%08x/%08x/", static_cast<u32>(SConfig::GetInstance().GetTitleID() >> 32),
static_cast<u32>(SConfig::GetInstance().GetTitleID()));
if (File::Exists(save_path + "banner.bin") && SConfig::GetInstance().bEnableMemcardSdWriting)
{
// Backup the existing save just in case it's still needed.

View File

@ -161,7 +161,7 @@ std::string CVolumeWiiCrypted::GetGameID() const
char ID[6];
if (!Read(0, 6, (u8*)ID, false))
if (!Read(0, 6, (u8*)ID, true))
return std::string();
return DecodeString(ID);
@ -179,7 +179,7 @@ Region CVolumeWiiCrypted::GetRegion() const
Country CVolumeWiiCrypted::GetCountry() const
{
u8 country_byte;
if (!m_pReader->Read(3, 1, &country_byte))
if (!ReadSwapped(3, &country_byte, true))
return Country::COUNTRY_UNKNOWN;
const Region region = GetRegion();
@ -209,7 +209,7 @@ std::string CVolumeWiiCrypted::GetMakerID() const
char makerID[2];
if (!Read(0x4, 0x2, (u8*)&makerID, false))
if (!Read(0x4, 0x2, (u8*)&makerID, true))
return std::string();
return DecodeString(makerID);
@ -221,7 +221,7 @@ u16 CVolumeWiiCrypted::GetRevision() const
return 0;
u8 revision;
if (!m_pReader->Read(7, 1, &revision))
if (!ReadSwapped(7, &revision, true))
return 0;
return revision;
@ -230,7 +230,7 @@ u16 CVolumeWiiCrypted::GetRevision() const
std::string CVolumeWiiCrypted::GetInternalName() const
{
char name_buffer[0x60];
if (m_pReader != nullptr && Read(0x20, 0x60, (u8*)&name_buffer, false))
if (m_pReader != nullptr && Read(0x20, 0x60, (u8*)&name_buffer, true))
return DecodeString(name_buffer);
return "";
@ -291,7 +291,7 @@ Platform CVolumeWiiCrypted::GetVolumeType() const
u8 CVolumeWiiCrypted::GetDiscNumber() const
{
u8 disc_number;
m_pReader->Read(6, 1, &disc_number);
ReadSwapped(6, &disc_number, true);
return disc_number;
}

View File

@ -177,7 +177,7 @@ void wxCheatsWindow::UpdateGUI()
m_gameini_default = parameters.LoadDefaultGameIni();
m_gameini_local = parameters.LoadLocalGameIni();
m_game_id = parameters.GetGameID();
m_game_revision = parameters.m_revision;
m_game_revision = parameters.GetRevision();
m_gameini_local_path = File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini";
Load_ARCodes();
Load_GeckoCodes();

View File

@ -232,7 +232,7 @@ void GFXDebuggerPanel::OnPauseAtNextFrameButton(wxCommandEvent& event)
void GFXDebuggerPanel::OnDumpButton(wxCommandEvent& event)
{
std::string dump_path =
File::GetUserPath(D_DUMP_IDX) + "Debug/" + SConfig::GetInstance().m_strGameID + "/";
File::GetUserPath(D_DUMP_IDX) + "Debug/" + SConfig::GetInstance().GetGameID() + "/";
if (!File::CreateFullPath(dump_path))
return;

View File

@ -164,7 +164,7 @@ void GeometryShaderCache::Init()
std::string cache_filename =
StringFromFormat("%sdx11-%s-gs.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(),
SConfig::GetInstance().m_strGameID.c_str());
SConfig::GetInstance().GetGameID().c_str());
GeometryShaderCacheInserter inserter;
g_gs_disk_cache.OpenAndRead(cache_filename, inserter);
}

View File

@ -504,7 +504,7 @@ void PixelShaderCache::Init()
std::string cache_filename =
StringFromFormat("%sdx11-%s-ps.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(),
SConfig::GetInstance().m_strGameID.c_str());
SConfig::GetInstance().GetGameID().c_str());
PixelShaderCacheInserter inserter;
g_ps_disk_cache.OpenAndRead(cache_filename, inserter);
}

View File

@ -165,7 +165,7 @@ void VertexShaderCache::Init()
std::string cache_filename =
StringFromFormat("%sdx11-%s-vs.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(),
SConfig::GetInstance().m_strGameID.c_str());
SConfig::GetInstance().GetGameID().c_str());
VertexShaderCacheInserter inserter;
g_vs_disk_cache.OpenAndRead(cache_filename, inserter);
}

View File

@ -143,7 +143,7 @@ void StateCache::Init()
std::string cache_filename =
StringFromFormat("%sdx12-%s-pso.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(),
SConfig::GetInstance().m_strGameID.c_str());
SConfig::GetInstance().GetGameID().c_str());
PipelineStateCacheInserter inserter;
s_pso_disk_cache.OpenAndRead(cache_filename, inserter);

View File

@ -77,7 +77,7 @@ void ShaderCache::Init()
if (!File::Exists(shader_cache_path))
File::CreateDir(File::GetUserPath(D_SHADERCACHE_IDX));
std::string title_game_id = SConfig::GetInstance().m_strGameID.c_str();
const std::string& title_game_id = SConfig::GetInstance().GetGameID();
std::string gs_cache_filename =
StringFromFormat("%sdx11-%s-gs.cache", shader_cache_path.c_str(), title_game_id.c_str());

View File

@ -437,7 +437,7 @@ void ProgramShaderCache::Init()
std::string cache_filename =
StringFromFormat("%sogl-%s-shaders.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(),
SConfig::GetInstance().m_strGameID.c_str());
SConfig::GetInstance().GetGameID().c_str());
ProgramShaderCacheInserter inserter;
g_program_disk_cache.OpenAndRead(cache_filename, inserter);

View File

@ -327,7 +327,7 @@ std::pair<VkPipeline, bool> ObjectCache::GetPipelineWithCacheResult(const Pipeli
std::string ObjectCache::GetDiskCacheFileName(const char* type)
{
return StringFromFormat("%svulkan-%s-%s.cache", File::GetUserPath(D_SHADERCACHE_IDX).c_str(),
SConfig::GetInstance().m_strGameID.c_str(), type);
SConfig::GetInstance().GetGameID().c_str(), type);
}
class PipelineCacheReadCallback : public LinearDiskCacheReader<u32, u8>

View File

@ -87,7 +87,7 @@ void HiresTexture::Update()
s_textureCache.clear();
}
const std::string& game_id = SConfig::GetInstance().m_strGameID;
const std::string& game_id = SConfig::GetInstance().GetGameID();
const std::string texture_directory = GetTextureDirectory(game_id);
std::vector<std::string> extensions{
".png", ".bmp", ".tga", ".dds",
@ -226,7 +226,7 @@ std::string HiresTexture::GenBaseName(const u8* texture, size_t texture_size, co
u64 tlut_hash = tlut_size ? GetHashHiresTexture(tlut, (int)tlut_size,
g_ActiveConfig.iSafeTextureCache_ColorSamples) :
0;
name = StringFromFormat("%s_%08x_%i", SConfig::GetInstance().m_strGameID.c_str(),
name = StringFromFormat("%s_%08x_%i", SConfig::GetInstance().GetGameID().c_str(),
(u32)(tex_hash ^ tlut_hash), (u16)format);
if (s_textureMap.find(name) != s_textureMap.end())
{

View File

@ -437,7 +437,7 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntryBase* entry_to_update, u8*
void TextureCacheBase::DumpTexture(TCacheEntryBase* entry, std::string basename, unsigned int level)
{
std::string szDir = File::GetUserPath(D_DUMPTEXTURES_IDX) + SConfig::GetInstance().m_strGameID;
std::string szDir = File::GetUserPath(D_DUMPTEXTURES_IDX) + SConfig::GetInstance().GetGameID();
// make sure that the directory exists
if (!File::Exists(szDir) || !File::IsDirectory(szDir))