mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-03-19 20:01:58 +00:00
We often use game IDs in paths, so we should try to make sure path traversal is impossible in game IDs. Admittedly, doing any kind of real attack using the six bytes available in game IDs is unrealistic, but no game ID should contain non-alphanumeric or non-ASCII characters anyway. Might also fix https://bugs.dolphin-emu.org/issues/13982 by skipping converting between encodings for game IDs.
220 lines
5.8 KiB
C++
220 lines
5.8 KiB
C++
// Copyright 2020 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "DiscIO/VolumeDisc.h"
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Crypto/SHA1.h"
|
|
#include "DiscIO/DiscUtils.h"
|
|
#include "DiscIO/Enums.h"
|
|
#include "DiscIO/Filesystem.h"
|
|
#include "DiscIO/VolumeGC.h"
|
|
|
|
namespace DiscIO
|
|
{
|
|
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->game_id.data() + 2, 2);
|
|
|
|
switch (GetCountry())
|
|
{
|
|
default:
|
|
case Country::Japan:
|
|
id[3] = 'J';
|
|
break;
|
|
case Country::Taiwan:
|
|
id[3] = 'W';
|
|
break;
|
|
case Country::USA:
|
|
id[3] = 'E';
|
|
break;
|
|
case Country::Europe:
|
|
id[3] = 'P';
|
|
break;
|
|
}
|
|
|
|
const std::string maker_id{GetMakerID()};
|
|
memcpy(id + 4, maker_id.c_str(), std::min<std::size_t>(maker_id.size(), 2));
|
|
|
|
return FilterGameID(id);
|
|
}
|
|
|
|
if (!Read(0, sizeof(id), reinterpret_cast<u8*>(id), partition))
|
|
return std::string();
|
|
|
|
return FilterGameID(id);
|
|
}
|
|
|
|
Country VolumeDisc::GetCountry(const Partition& partition) const
|
|
{
|
|
u8 country_byte = 0;
|
|
|
|
if (GetVolumeType() == Platform::Triforce)
|
|
{
|
|
const BootID* boot_id = static_cast<const VolumeGC*>(this)->GetTriforceBootID();
|
|
|
|
switch (boot_id->region_flags)
|
|
{
|
|
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();
|
|
|
|
if (CountryCodeToRegion(country_byte, GetVolumeType(), region, revision) != region)
|
|
return TypicalCountryForRegion(region);
|
|
|
|
return CountryCodeToCountry(country_byte, GetVolumeType(), region, revision);
|
|
}
|
|
|
|
Region VolumeDisc::RegionCodeToRegion(std::optional<u32> region_code) const
|
|
{
|
|
if (!region_code)
|
|
return Region::Unknown;
|
|
|
|
const Region region = static_cast<Region>(*region_code);
|
|
return region <= Region::NTSC_K ? region : Region::Unknown;
|
|
}
|
|
|
|
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();
|
|
|
|
// 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 'S': // SEGA CORPORATION
|
|
case 'H': // Hitmaker co,ltd
|
|
return "6E";
|
|
case 'N': // NAMCO CORPORATION
|
|
return "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();
|
|
|
|
return FilterGameID(maker_id);
|
|
}
|
|
|
|
std::optional<u16> VolumeDisc::GetRevision(const Partition& partition) const
|
|
{
|
|
std::optional<u8> revision = ReadSwapped<u8>(7, partition);
|
|
return revision ? *revision : std::optional<u16>();
|
|
}
|
|
|
|
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->game_name);
|
|
}
|
|
|
|
if (!Read(0x20, sizeof(name), reinterpret_cast<u8*>(&name), partition))
|
|
return std::string();
|
|
|
|
return DecodeString(name);
|
|
}
|
|
|
|
std::string VolumeDisc::GetApploaderDate(const Partition& partition) const
|
|
{
|
|
char date[16];
|
|
|
|
if (!Read(0x2440, sizeof(date), reinterpret_cast<u8*>(&date), partition))
|
|
return std::string();
|
|
|
|
return DecodeString(date);
|
|
}
|
|
|
|
std::optional<u8> VolumeDisc::GetDiscNumber(const Partition& partition) const
|
|
{
|
|
return ReadSwapped<u8>(6, partition);
|
|
}
|
|
|
|
bool VolumeDisc::IsNKit() const
|
|
{
|
|
constexpr u32 NKIT_MAGIC = 0x4E4B4954; // "NKIT"
|
|
return ReadSwapped<u32>(0x200, PARTITION_NONE) == NKIT_MAGIC;
|
|
}
|
|
|
|
void VolumeDisc::AddGamePartitionToSyncHash(Common::SHA1::Context* context) const
|
|
{
|
|
const Partition partition = GetGamePartition();
|
|
|
|
// All headers at the beginning of the partition, plus the apploader
|
|
ReadAndAddToSyncHash(context, 0, 0x2440 + GetApploaderSize(*this, partition).value_or(0),
|
|
partition);
|
|
|
|
// Boot DOL (may be missing if this is a Datel disc)
|
|
const std::optional<u64> dol_offset = GetBootDOLOffset(*this, partition);
|
|
if (dol_offset)
|
|
{
|
|
ReadAndAddToSyncHash(context, *dol_offset,
|
|
GetBootDOLSize(*this, partition, *dol_offset).value_or(0), partition);
|
|
}
|
|
|
|
// File system
|
|
const std::optional<u64> fst_offset = GetFSTOffset(*this, partition);
|
|
if (fst_offset)
|
|
ReadAndAddToSyncHash(context, *fst_offset, GetFSTSize(*this, partition).value_or(0), partition);
|
|
|
|
// opening.bnr (name and banner)
|
|
const FileSystem* file_system = GetFileSystem(partition);
|
|
if (file_system)
|
|
{
|
|
std::unique_ptr<FileInfo> file_info = file_system->FindFileInfo("opening.bnr");
|
|
if (file_info && !file_info->IsDirectory())
|
|
ReadAndAddToSyncHash(context, file_info->GetOffset(), file_info->GetSize(), partition);
|
|
}
|
|
}
|
|
|
|
} // namespace DiscIO
|