summaryrefslogtreecommitdiff
path: root/Source/Core/DiscIO
diff options
context:
space:
mode:
authorcrediar <crediar@rypp.net>2025-07-16 19:55:59 +0200
committerJordan Woyak <jordan.woyak@gmail.com>2026-02-15 20:14:14 -0600
commit2c62214875e4d409136229421c44aa8b88fbb635 (patch)
tree43d2e45b6e763fcf2f7ab0fbb8a6d474dd2e7e6f /Source/Core/DiscIO
parentd6b5af57d01a04918e9ec849689b6d39b8e33591 (diff)
Added Triforce support
Diffstat (limited to 'Source/Core/DiscIO')
-rw-r--r--Source/Core/DiscIO/Enums.h9
-rw-r--r--Source/Core/DiscIO/Volume.h1
-rw-r--r--Source/Core/DiscIO/VolumeDisc.cpp107
-rw-r--r--Source/Core/DiscIO/VolumeDisc.h24
-rw-r--r--Source/Core/DiscIO/VolumeGC.cpp33
-rw-r--r--Source/Core/DiscIO/VolumeGC.h14
6 files changed, 160 insertions, 28 deletions
diff --git a/Source/Core/DiscIO/Enums.h b/Source/Core/DiscIO/Enums.h
index 9b5e274de9..62ad9e846a 100644
--- a/Source/Core/DiscIO/Enums.h
+++ b/Source/Core/DiscIO/Enums.h
@@ -19,7 +19,7 @@ enum class Platform
WiiDisc,
WiiWAD,
ELFOrDOL,
- NumberOfPlatforms
+ NumberOfPlatforms,
};
enum class Country
@@ -38,7 +38,7 @@ enum class Country
Taiwan,
World,
Unknown,
- NumberOfCountries
+ NumberOfCountries,
};
// This numbering matches Nintendo's GameCube/Wii region numbering.
@@ -48,7 +48,8 @@ enum class Region
NTSC_U = 1, // Mainly North America
PAL = 2, // Mainly Europe and Oceania
Unknown = 3, // Nintendo uses this to mean region free, but we also use it for unknown regions
- NTSC_K = 4 // South Korea (Wii only)
+ NTSC_K = 4, // South Korea (Wii only)
+ DEV = 5, // Triforce has no regions
};
// Languages 0 - 9 match Nintendo's Wii language numbering.
@@ -66,7 +67,7 @@ enum class Language
SimplifiedChinese = 7, // Not selectable on any unmodded retail Wii
TraditionalChinese = 8, // Not selectable on any unmodded retail Wii
Korean = 9,
- Unknown
+ Unknown,
};
std::string GetName(Country country, bool translate);
diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h
index 3f80cd2e3d..3d161811e4 100644
--- a/Source/Core/DiscIO/Volume.h
+++ b/Source/Core/DiscIO/Volume.h
@@ -96,7 +96,6 @@ public:
}
virtual std::string GetGameID(const Partition& partition = PARTITION_NONE) const = 0;
virtual std::string GetGameTDBID(const Partition& partition = PARTITION_NONE) const = 0;
- virtual std::string GetTriforceID() const { return ""; }
virtual std::string GetMakerID(const Partition& partition = PARTITION_NONE) const = 0;
virtual std::optional<u16> GetRevision(const Partition& partition = PARTITION_NONE) const = 0;
virtual std::string GetInternalName(const Partition& partition = PARTITION_NONE) const = 0;
diff --git a/Source/Core/DiscIO/VolumeDisc.cpp b/Source/Core/DiscIO/VolumeDisc.cpp
index 882bd3ac96..a915df1224 100644
--- a/Source/Core/DiscIO/VolumeDisc.cpp
+++ b/Source/Core/DiscIO/VolumeDisc.cpp
@@ -12,6 +12,7 @@
#include "DiscIO/DiscUtils.h"
#include "DiscIO/Enums.h"
#include "DiscIO/Filesystem.h"
+#include "DiscIO/VolumeGC.h"
namespace DiscIO
{
@@ -19,6 +20,55 @@ std::string VolumeDisc::GetGameID(const Partition& partition) const
{
char id[6];
+ if (GetVolumeType() == Platform::Triforce)
+ {
+ // Triforce games have their Game ID stored in the boot.id file
+ const BootID* boot_id = static_cast<const VolumeGC*>(this)->GetTriforceBootID();
+
+ // Construct game ID from the BTID
+ id[0] = 'G';
+
+ memcpy(id + 1, boot_id->gameId + 2, 2);
+
+ switch (boot_id->regionFlags)
+ {
+ default:
+ case 0x02: // JAPAN
+ id[3] = 'J';
+ break;
+ case 0x08: // ASIA
+ id[3] = 'W';
+ break;
+ case 0x0E: // USA
+ id[3] = 'E';
+ break;
+ case 0x0C: // EXPORT
+ id[3] = 'P';
+ break;
+ }
+
+ // There only seem to be three different makers here,
+ // so we can just check the first char to difference between them.
+ //
+ // NAMCO CORPORATION, SEGA CORPORATION and Hitmaker co,ltd.
+
+ switch (boot_id->manufacturer[0])
+ {
+ case 'N': // NAMCO CORPORATION
+ id[4] = '8';
+ id[5] = '2';
+ break;
+ default:
+ case 'H': // Hitmaker co,ltd.
+ case 'S': // SEGA CORPORATION
+ id[4] = '6';
+ id[5] = 'E';
+ break;
+ }
+
+ return DecodeString(id);
+ }
+
if (!Read(0, sizeof(id), reinterpret_cast<u8*>(id), partition))
return std::string();
@@ -27,8 +77,35 @@ std::string VolumeDisc::GetGameID(const Partition& partition) const
Country VolumeDisc::GetCountry(const Partition& partition) const
{
- // The 0 that we use as a default value is mapped to Country::Unknown and Region::Unknown
- const u8 country_byte = ReadSwapped<u8>(3, partition).value_or(0);
+ u8 country_byte = 0;
+
+ if (GetVolumeType() == Platform::Triforce)
+ {
+ const BootID* boot_id = static_cast<const VolumeGC*>(this)->GetTriforceBootID();
+
+ switch (boot_id->regionFlags)
+ {
+ default:
+ case 0x02: // JAPAN
+ country_byte = 'J';
+ break;
+ case 0x08: // ASIA
+ country_byte = 'W';
+ break;
+ case 0x0E: // USA
+ country_byte = 'E';
+ break;
+ case 0x0C: // EXPORT
+ country_byte = 'P';
+ break;
+ }
+ }
+ else
+ {
+ // The 0 that we use as a default value is mapped to Country::Unknown and Region::Unknown
+ country_byte = ReadSwapped<u8>(3, partition).value_or(0);
+ }
+
const Region region = GetRegion();
const std::optional<u16> revision = GetRevision();
@@ -51,6 +128,24 @@ std::string VolumeDisc::GetMakerID(const Partition& partition) const
{
char maker_id[2];
+ // Triforce games have their maker stored in the boot.id file as an actual string
+ if (GetVolumeType() == Platform::Triforce)
+ {
+ const BootID* boot_id = static_cast<const VolumeGC*>(this)->GetTriforceBootID();
+
+ switch (boot_id->manufacturer[0])
+ {
+ case 'S': // SEGA CORPORATION
+ case 'H': // Hitmaker co,ltd
+ return DecodeString("6E");
+ case 'N': // NAMCO CORPORATION
+ return DecodeString("82");
+ default:
+ break;
+ }
+ // Fall back to normal maker from header
+ }
+
if (!Read(0x4, sizeof(maker_id), reinterpret_cast<u8*>(&maker_id), partition))
return std::string();
@@ -66,6 +161,14 @@ std::optional<u16> VolumeDisc::GetRevision(const Partition& partition) const
std::string VolumeDisc::GetInternalName(const Partition& partition) const
{
char name[0x60];
+
+ // Triforce games have their Title stored in the boot.id file
+ if (GetVolumeType() == Platform::Triforce)
+ {
+ const BootID* boot_id = static_cast<const VolumeGC*>(this)->GetTriforceBootID();
+ return DecodeString(boot_id->gameName);
+ }
+
if (!Read(0x20, sizeof(name), reinterpret_cast<u8*>(&name), partition))
return std::string();
diff --git a/Source/Core/DiscIO/VolumeDisc.h b/Source/Core/DiscIO/VolumeDisc.h
index 17c76ae689..f263c40c16 100644
--- a/Source/Core/DiscIO/VolumeDisc.h
+++ b/Source/Core/DiscIO/VolumeDisc.h
@@ -15,6 +15,30 @@ namespace DiscIO
class VolumeDisc : public Volume
{
public:
+ struct BootID
+ {
+ u32 magic; // 0x00 (Always BTID)
+ uint32_t unknown0[3];
+ uint32_t unknown1[4];
+ char mediaboardType[4]; // 0x20 (GCAM for Triforce)
+ uint32_t unknown2;
+ uint16_t year; // 0x28
+ uint8_t month; // 0x2A
+ uint8_t day; // 0x2B
+ uint8_t videoMode; // 0x2C unknown bitmask, resolutions + horizontal/vertical
+ uint8_t unknown3;
+ uint8_t type3Compatible; // 0x2E (Type-3 compatible titles have this set to 1)
+ uint8_t unknown4;
+ char gameId[8]; // 0x30
+ uint32_t regionFlags; // 0x38
+ uint32_t unknown6[9];
+ char manufacturer[0x20]; // 0x60
+ char gameName[0x20]; // 0x80
+ char gameExecutable[0x20]; // 0xA0
+ char testExecutable[0x20]; // 0xC0
+ char creditTypes[8][0x20]; // 0xE0
+ };
+
std::string GetGameID(const Partition& partition = PARTITION_NONE) const override;
Country GetCountry(const Partition& partition = PARTITION_NONE) const override;
std::string GetMakerID(const Partition& partition = PARTITION_NONE) const override;
diff --git a/Source/Core/DiscIO/VolumeGC.cpp b/Source/Core/DiscIO/VolumeGC.cpp
index ad3d8ec74e..52dacd59a4 100644
--- a/Source/Core/DiscIO/VolumeGC.cpp
+++ b/Source/Core/DiscIO/VolumeGC.cpp
@@ -45,13 +45,11 @@ VolumeGC::VolumeGC(std::unique_ptr<BlobReader> reader)
std::unique_ptr<FileInfo> file_info = tmp_fs->FindFileInfo("boot.id");
if (!file_info)
return;
- BootID triforce_header;
const u64 file_size = ReadFile(*this, PARTITION_NONE, file_info.get(),
- reinterpret_cast<u8*>(&triforce_header), sizeof(BootID));
- if (file_size >= 4 && triforce_header.magic == BTID_MAGIC)
+ reinterpret_cast<u8*>(&m_triforce_header), sizeof(BootID));
+ if (file_size >= 4 && m_triforce_header.magic == BTID_MAGIC)
{
m_is_triforce = true;
- m_triforce_id = triforce_header.id;
}
}
}
@@ -90,16 +88,23 @@ std::string VolumeGC::GetGameTDBID(const Partition& partition) const
return GetGameID(partition);
}
-std::string VolumeGC::GetTriforceID() const
+Region VolumeGC::GetRegion() const
{
if (m_is_triforce)
- return (std::string(m_triforce_id.data(), m_triforce_id.size()));
- else
- return "";
-}
+ {
+ switch (m_triforce_header.regionFlags)
+ {
+ default:
+ case 0x02: // JAPAN
+ case 0x08: // ASIA
+ return Region::NTSC_J;
+ case 0x0E: // USA
+ return Region::NTSC_U;
+ case 0x0C: // EXPORT
+ return Region::PAL;
+ }
+ }
-Region VolumeGC::GetRegion() const
-{
return RegionCodeToRegion(m_reader->ReadSwapped<u32>(0x458));
}
@@ -184,6 +189,12 @@ std::array<u8, 20> VolumeGC::GetSyncHash() const
VolumeGC::ConvertedGCBanner VolumeGC::LoadBannerFile() const
{
+ // There is at least one Triforce game that has an opening.bnr file but from a different game.
+ if (m_is_triforce)
+ {
+ return {};
+ }
+
GCBanner banner_file;
const u64 file_size = ReadFile(*this, PARTITION_NONE, "opening.bnr",
reinterpret_cast<u8*>(&banner_file), sizeof(GCBanner));
diff --git a/Source/Core/DiscIO/VolumeGC.h b/Source/Core/DiscIO/VolumeGC.h
index ff768c2e2a..2f85a75535 100644
--- a/Source/Core/DiscIO/VolumeGC.h
+++ b/Source/Core/DiscIO/VolumeGC.h
@@ -34,7 +34,6 @@ public:
const Partition& partition = PARTITION_NONE) const override;
const FileSystem* GetFileSystem(const Partition& partition = PARTITION_NONE) const override;
std::string GetGameTDBID(const Partition& partition = PARTITION_NONE) const override;
- std::string GetTriforceID() const override;
std::map<Language, std::string> GetShortNames() const override;
std::map<Language, std::string> GetLongNames() const override;
std::map<Language, std::string> GetShortMakers() const override;
@@ -50,9 +49,10 @@ public:
DataSizeType GetDataSizeType() const override;
u64 GetRawSize() const override;
const BlobReader& GetBlobReader() const override;
-
std::array<u8, 20> GetSyncHash() const override;
+ const BootID* GetTriforceBootID() const { return &m_triforce_header; }
+
private:
static constexpr u32 GC_BANNER_WIDTH = 96;
static constexpr u32 GC_BANNER_HEIGHT = 32;
@@ -77,13 +77,6 @@ private:
// (only one for BNR1 type)
};
- struct BootID
- {
- u32 magic; // "BTID"
- u32 padding[11];
- std::array<char, 4> id;
- };
-
struct ConvertedGCBanner
{
ConvertedGCBanner();
@@ -112,8 +105,9 @@ private:
std::unique_ptr<BlobReader> m_reader;
+ BootID m_triforce_header;
+
bool m_is_triforce;
- std::array<char, 4> m_triforce_id;
};
} // namespace DiscIO