From 52266d7ac0e0ecae083453a65ca6c98cb4def7d0 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Wed, 28 Dec 2022 22:14:43 +1000 Subject: [PATCH] CDVD: Remove exceptions (completely this time) The actual reads are still missing error checking... --- pcsx2/Achievements.cpp | 62 +++++---- pcsx2/CDVD/CDVD.cpp | 218 ++++++++++++++------------------ pcsx2/CDVD/CDVD.h | 3 +- pcsx2/CDVD/CDVDcommon.cpp | 39 +----- pcsx2/CDVD/IsoFS/IsoDirectory.h | 19 +-- pcsx2/CDVD/IsoFS/IsoFS.cpp | 94 +++++++++----- pcsx2/CDVD/IsoFS/IsoFS.h | 2 +- pcsx2/CDVD/IsoFS/IsoFSCDVD.h | 2 +- pcsx2/CDVD/IsoFS/IsoFile.cpp | 40 ++++-- pcsx2/CDVD/IsoFS/IsoFile.h | 22 ++-- pcsx2/Elfheader.cpp | 214 +++++++++++++++++-------------- pcsx2/Elfheader.h | 62 ++++----- pcsx2/GameList.cpp | 15 +-- pcsx2/VMManager.cpp | 39 +++--- 14 files changed, 423 insertions(+), 408 deletions(-) diff --git a/pcsx2/Achievements.cpp b/pcsx2/Achievements.cpp index e6b779a623..c3d5ba401d 100644 --- a/pcsx2/Achievements.cpp +++ b/pcsx2/Achievements.cpp @@ -1357,42 +1357,40 @@ std::string_view Achievements::GetELFNameForHash(const std::string& elf_path) std::optional> Achievements::ReadELFFromCurrentDisc(const std::string& elf_path) { - // This CDVD stuff is super nasty and full of exceptions.. std::optional> ret; - try + + // ELF suffix hack. Taken from CDVD::loadElf + // MLB2k6 has an elf whose suffix is actually ;2 + std::string filepath(elf_path); + const std::string::size_type semi_pos = filepath.rfind(';'); + if (semi_pos != std::string::npos && std::string_view(filepath).substr(semi_pos) != ";1") { - // ELF suffix hack. Taken from CDVD::loadElf - // MLB2k6 has an elf whose suffix is actually ;2 - std::string filepath(elf_path); - const std::string::size_type semi_pos = filepath.rfind(';'); - if (semi_pos != std::string::npos && std::string_view(filepath).substr(semi_pos) != ";1") - { - Console.Warning("(Achievements) Non-conforming version suffix (%s) detected and replaced.", elf_path.c_str()); - filepath.erase(semi_pos); - filepath += ";1"; - } - - IsoFSCDVD isofs; - IsoFile file(isofs, filepath); - const u32 size = file.getLength(); - - ret = std::vector(); - ret->resize(size); - - if (size > 0) - { - const s32 bytes_read = file.read(ret->data(), static_cast(size)); - if (bytes_read != static_cast(size)) - { - Console.Error("(Achievements) Only read %d of %u bytes of ELF '%s'", bytes_read, size, elf_path.c_str()); - ret.reset(); - } - } + Console.Warning(fmt::format("(Achievements) Non-conforming version suffix ({}) detected and replaced.", elf_path)); + filepath.erase(semi_pos); + filepath += ";1"; } - catch (...) + + IsoFSCDVD isofs; + IsoFile file(isofs); + Error error; + if (!file.open(filepath, &error)) { - Console.Error("(Achievements) Caught exception while trying to read ELF '%s'.", elf_path.c_str()); - ret.reset(); + Console.Error(fmt::format("(Achievements) Failed to open ELF '{}' on disc: {}", elf_path, error.GetDescription())); + return ret; + } + + const u32 size = file.getLength(); + ret = std::vector(); + ret->resize(size); + + if (size > 0) + { + const s32 bytes_read = file.read(ret->data(), static_cast(size)); + if (bytes_read != static_cast(size)) + { + Console.Error(fmt::format("(Achievements) Only read {} of {} bytes of ELF '{}'", bytes_read, size, elf_path)); + ret.reset(); + } } return ret; diff --git a/pcsx2/CDVD/CDVD.cpp b/pcsx2/CDVD/CDVD.cpp index 4c37387aa5..d83678ba09 100644 --- a/pcsx2/CDVD/CDVD.cpp +++ b/pcsx2/CDVD/CDVD.cpp @@ -383,140 +383,118 @@ s32 cdvdWriteConfig(const u8* config) return 0; } -// Sets ElfCRC to the CRC of the game bound to the CDVD source. -std::unique_ptr cdvdLoadElf(std::string filename, bool isPSXElf) +bool cdvdLoadElf(ElfObject* elfo, std::string elfpath, bool isPSXElf, Error* error) { - if (StringUtil::StartsWith(filename, "host:")) + if (StringUtil::StartsWith(elfpath, "host:")) { - std::string host_filename(filename.substr(5)); - s64 host_size = FileSystem::GetPathFileSize(host_filename.c_str()); - return std::make_unique(std::move(host_filename), static_cast(std::max(host_size, 0)), isPSXElf); + std::string host_filename(elfpath.substr(5)); + if (!elfo->OpenFile(host_filename, isPSXElf, error)) + return false; + } + else + { + // Mimic PS2 behavior! + // Much trial-and-error with changing the ISOFS and BOOT2 contents of an image have shown that + // the PS2 BIOS performs the peculiar task of *ignoring* the version info from the parsed BOOT2 + // filename *and* the ISOFS, when loading the game's ELF image. What this means is: + // + // 1. a valid PS2 ELF can have any version (ISOFS), and the version need not match the one in SYSTEM.CNF. + // 2. the version info on the file in the BOOT2 parameter of SYSTEM.CNF can be missing, 10 chars long, + // or anything else. Its all ignored. + // 3. Games loading their own files do *not* exhibit this behavior; likely due to using newer IOP modules + // or lower level filesystem APIs (fortunately that doesn't affect us). + // + // FIXME: Properly mimicing this behavior is troublesome since we need to add support for "ignoring" + // version information when doing file searches. I'll add this later. For now, assuming a ;1 should + // be sufficient (no known games have their ELF binary as anything but version ;1) + const std::string::size_type semi_pos = elfpath.rfind(';'); + if (semi_pos != std::string::npos && std::string_view(elfpath).substr(semi_pos) != ";1") + { + Console.WriteLn(Color_Blue, "(LoadELF) Non-conforming version suffix (%s) detected and replaced.", elfpath.c_str()); + elfpath.erase(semi_pos); + elfpath += ";1"; + } + + // Fix cdrom:path, the iso reader doesn't like it. + if (StringUtil::StartsWith(elfpath, "cdrom:") && elfpath[6] != '\\' && elfpath[6] != '/') + elfpath.insert(6, 1, '\\'); + + IsoFSCDVD isofs; + IsoFile file(isofs); + if (!file.open(elfpath, error) || !elfo->OpenIsoFile(elfpath, file, isPSXElf, error)) + return false; } - // Mimic PS2 behavior! - // Much trial-and-error with changing the ISOFS and BOOT2 contents of an image have shown that - // the PS2 BIOS performs the peculiar task of *ignoring* the version info from the parsed BOOT2 - // filename *and* the ISOFS, when loading the game's ELF image. What this means is: - // - // 1. a valid PS2 ELF can have any version (ISOFS), and the version need not match the one in SYSTEM.CNF. - // 2. the version info on the file in the BOOT2 parameter of SYSTEM.CNF can be missing, 10 chars long, - // or anything else. Its all ignored. - // 3. Games loading their own files do *not* exhibit this behavior; likely due to using newer IOP modules - // or lower level filesystem APIs (fortunately that doesn't affect us). - // - // FIXME: Properly mimicing this behavior is troublesome since we need to add support for "ignoring" - // version information when doing file searches. I'll add this later. For now, assuming a ;1 should - // be sufficient (no known games have their ELF binary as anything but version ;1) - const std::string::size_type semi_pos = filename.rfind(';'); - if (semi_pos != std::string::npos && std::string_view(filename).substr(semi_pos) != ";1") - { - Console.WriteLn(Color_Blue, "(LoadELF) Non-conforming version suffix (%s) detected and replaced.", filename.c_str()); - filename.erase(semi_pos); - filename += ";1"; - } - - // Fix cdrom:path, the iso reader doesn't like it. - if (StringUtil::StartsWith(filename, "cdrom:") && filename[6] != '\\' && filename[6] != '/') - filename.insert(6, 1, '\\'); - - IsoFSCDVD isofs; - IsoFile file(isofs, filename); - return std::make_unique(std::move(filename), file, isPSXElf); + return true; } u32 cdvdGetElfCRC(const std::string& path) { - try - { - // Yay for write-after-read here. Isn't our ELF parser great.... - const s64 host_size = FileSystem::GetPathFileSize(path.c_str()); - if (host_size <= 0) - return 0; - - std::unique_ptr elfptr(std::make_unique(path, static_cast(std::max(host_size, 0)), false)); - elfptr->loadHeaders(); - return elfptr->getCRC(); - } - catch ([[maybe_unused]] Exception::FileNotFound& e) - { + ElfObject elfo; + if (!elfo.OpenFile(path, false, nullptr)) return 0; - } + + return elfo.GetCRC(); } -// return value: -// 0 - Invalid or unknown disc. -// 1 - PS1 CD -// 2 - PS2 CD static CDVDDiscType GetPS2ElfName(std::string* name, std::string* version) { CDVDDiscType retype = CDVDDiscType::Other; name->clear(); version->clear(); - try { - IsoFSCDVD isofs; - IsoFile file( isofs, "SYSTEM.CNF;1"); + Error error; + IsoFSCDVD isofs; + IsoFile file(isofs); + if (!file.open("SYSTEM.CNF;1", &error)) + { + Console.Error(fmt::format("(GetElfName) Failed to open SYSTEM.CNF: {}", error.GetDescription())); + return CDVDDiscType::Other; + } - int size = file.getLength(); - if( size == 0 ) return CDVDDiscType::Other; + const int size = file.getLength(); + if (size == 0) + return CDVDDiscType::Other; - while( !file.eof() ) - { - const std::string line(file.readLine()); - std::string_view key, value; - if (!StringUtil::ParseAssignmentString(line, &key, &value)) - continue; + while (!file.eof()) + { + const std::string line(file.readLine()); + std::string_view key, value; + if (!StringUtil::ParseAssignmentString(line, &key, &value)) + continue; - if( value.empty() && file.getLength() != file.getSeekPos() ) - { // Some games have a character on the last line of the file, don't print the error in those cases. - Console.Warning( "(SYSTEM.CNF) Unusual or malformed entry in SYSTEM.CNF ignored:" ); - Console.Indent().WriteLn(line); - continue; - } - - if( key == "BOOT2" ) - { - Console.WriteLn( Color_StrongBlue, "(SYSTEM.CNF) Detected PS2 Disc = %.*s", - static_cast(value.size()), value.data()); - *name = value; - retype = CDVDDiscType::PS2Disc; - } - else if( key == "BOOT" ) - { - Console.WriteLn( Color_StrongBlue, "(SYSTEM.CNF) Detected PSX/PSone Disc = %.*s", - static_cast(value.size()), value.data()); - *name = value; - retype = CDVDDiscType::PS1Disc; - } - else if( key == "VMODE" ) - { - Console.WriteLn( Color_Blue, "(SYSTEM.CNF) Disc region type = %.*s", - static_cast(value.size()), value.data()); - } - else if( key == "VER" ) - { - Console.WriteLn( Color_Blue, "(SYSTEM.CNF) Software version = %.*s", - static_cast(value.size()), value.data()); - *version = value; - } + if (value.empty() && file.getLength() != file.getSeekPos()) + { // Some games have a character on the last line of the file, don't print the error in those cases. + Console.Warning("(SYSTEM.CNF) Unusual or malformed entry in SYSTEM.CNF ignored:"); + Console.Indent().WriteLn(line); + continue; } - if( retype == CDVDDiscType::Other ) + if (key == "BOOT2") { - Console.Error("(GetElfName) Disc image is *not* a PlayStation or PS2 game!"); - return CDVDDiscType::Other; + Console.WriteLn(Color_StrongBlue, fmt::format("(SYSTEM.CNF) Detected PS2 Disc = {}", value)); + *name = value; + retype = CDVDDiscType::PS2Disc; + } + else if (key == "BOOT") + { + Console.WriteLn(Color_StrongBlue, fmt::format("(SYSTEM.CNF) Detected PSX/PSone Disc = {}", value)); + *name = value; + retype = CDVDDiscType::PS1Disc; + } + else if (key == "VMODE") + { + Console.WriteLn(Color_Blue, fmt::format("(SYSTEM.CNF) Disc region type = {}", value)); + } + else if (key == "VER") + { + Console.WriteLn(Color_Blue, fmt::format("(SYSTEM.CNF) Software version = {}", value)); + *version = value; } } - catch( Exception::FileNotFound& ) - { - //Console.Warning(ex.FormatDiagnosticMessage()); - return CDVDDiscType::Other; // no SYSTEM.CNF, not a PS1/PS2 disc. - } - catch (Exception::BadStream& ex) - { - Console.Error(ex.FormatDiagnosticMessage()); - return CDVDDiscType::Other; // ISO error - } + + if (retype == CDVDDiscType::Other) + Console.Error("(GetElfName) Disc image is *not* a PlayStation or PS2 game!"); return retype; } @@ -586,21 +564,13 @@ void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::st if (disc_type == CDVDDiscType::PS2Disc || disc_type == CDVDDiscType::PS1Disc) { - try - { - const bool isPSXElf = (disc_type == CDVDDiscType::PS1Disc); - std::unique_ptr elfptr(cdvdLoadElf(elfpath, isPSXElf)); - elfptr->loadHeaders(); - crc = elfptr->getCRC(); - } - catch ([[maybe_unused]] Exception::FileNotFound& e) - { - Console.Error(fmt::format("Failed to load ELF info for {}", elfpath)); - } - catch (Exception::BadStream& ex) - { - Console.Error(ex.FormatDiagnosticMessage()); - } + Error error; + ElfObject elfo; + const bool isPSXElf = (disc_type == CDVDDiscType::PS1Disc); + if (!cdvdLoadElf(&elfo, elfpath, isPSXElf, &error)) + Console.Error(fmt::format("Failed to load ELF info for {}: {}", elfpath, error.GetDescription())); + else + crc = elfo.GetCRC(); } *out_crc = crc; diff --git a/pcsx2/CDVD/CDVD.h b/pcsx2/CDVD/CDVD.h index 0fe45de973..205e14be29 100644 --- a/pcsx2/CDVD/CDVD.h +++ b/pcsx2/CDVD/CDVD.h @@ -21,6 +21,7 @@ #include #include +class Error; class ElfObject; #define btoi(b) ((b) / 16 * 10 + (b) % 16) /* BCD to u_char */ @@ -189,7 +190,7 @@ extern void cdvdWrite(u8 key, u8 rt); extern void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::string* out_version, u32* out_crc, CDVDDiscType* out_disc_type); extern u32 cdvdGetElfCRC(const std::string& path); -extern std::unique_ptr cdvdLoadElf(std::string filename, bool isPSXElf); +extern bool cdvdLoadElf(ElfObject* elfo, std::string filename, bool isPSXElf, Error* error); extern s32 cdvdCtrlTrayOpen(); extern s32 cdvdCtrlTrayClose(); diff --git a/pcsx2/CDVD/CDVDcommon.cpp b/pcsx2/CDVD/CDVDcommon.cpp index d73ac4fb33..ee6ede5b4b 100644 --- a/pcsx2/CDVD/CDVDcommon.cpp +++ b/pcsx2/CDVD/CDVDcommon.cpp @@ -73,14 +73,11 @@ static void CheckNullCDVD() static int CheckDiskTypeFS(int baseType) { IsoFSCDVD isofs; - try + IsoDirectory rootdir(isofs); + if (rootdir.OpenRootDirectory()) { - IsoDirectory rootdir(isofs); - - try + if (IsoFile file(isofs); file.open(rootdir, "SYSTEM.CNF;1")) { - IsoFile file(rootdir, "SYSTEM.CNF;1"); - const int size = file.getLength(); const std::unique_ptr buffer = std::make_unique(size + 1); file.read(buffer.get(), size); @@ -97,40 +94,16 @@ static int CheckDiskTypeFS(int baseType) return (baseType == CDVD_TYPE_DETCTCD) ? CDVD_TYPE_PS2CD : CDVD_TYPE_PS2DVD; } - catch (Exception::FileNotFound&) - { - } // PS2 Linux disc 2, doesn't have a System.CNF or a normal ELF - try - { - IsoFile file(rootdir, "P2L_0100.02;1"); + if (rootdir.Exists("P2L_0100.02;1")) return CDVD_TYPE_PS2DVD; - } - catch (Exception::FileNotFound&) - { - } - try - { - IsoFile file(rootdir, "PSX.EXE;1"); + if (rootdir.Exists("PSX.EXE;1")) return CDVD_TYPE_PSCD; - } - catch (Exception::FileNotFound&) - { - } - try - { - IsoFile file(rootdir, "VIDEO_TS/VIDEO_TS.IFO;1"); + if (rootdir.Exists("VIDEO_TS/VIDEO_TS.IFO;1")) return CDVD_TYPE_DVDV; - } - catch (Exception::FileNotFound&) - { - } - } - catch (Exception::FileNotFound&) - { } #ifdef PCSX2_DEVBUILD diff --git a/pcsx2/CDVD/IsoFS/IsoDirectory.h b/pcsx2/CDVD/IsoFS/IsoDirectory.h index b5e2d1f611..5a2c800533 100644 --- a/pcsx2/CDVD/IsoFS/IsoDirectory.h +++ b/pcsx2/CDVD/IsoFS/IsoDirectory.h @@ -16,26 +16,31 @@ #pragma once #include "common/Pcsx2Defs.h" +#include #include #include +class Error; + enum IsoFS_Type { FStype_ISO9660 = 1, FStype_Joliet = 2, }; -class IsoDirectory +class IsoDirectory final { public: SectorSource& internalReader; std::vector files; - IsoFS_Type m_fstype; + IsoFS_Type m_fstype = FStype_ISO9660; public: IsoDirectory(SectorSource& r); - IsoDirectory(SectorSource& r, const IsoFileDescriptor& directoryEntry); - virtual ~IsoDirectory() = default; + ~IsoDirectory(); + + bool OpenRootDirectory(Error* error = nullptr); + bool Open(const IsoFileDescriptor& directoryEntry, Error* error = nullptr); std::string FStype_ToString() const; SectorSource& GetReader() const { return internalReader; } @@ -46,12 +51,10 @@ public: u32 GetFileSize(const std::string_view& filePath) const; - IsoFileDescriptor FindFile(const std::string_view& filePath) const; + std::optional FindFile(const std::string_view& filePath) const; protected: - const IsoFileDescriptor& GetEntry(const std::string_view& fileName) const; - const IsoFileDescriptor& GetEntry(int index) const; + const IsoFileDescriptor& GetEntry(size_t index) const; - void Init(const IsoFileDescriptor& directoryEntry); int GetIndexOf(const std::string_view& fileName) const; }; diff --git a/pcsx2/CDVD/IsoFS/IsoFS.cpp b/pcsx2/CDVD/IsoFS/IsoFS.cpp index 9b41482b20..f405552984 100644 --- a/pcsx2/CDVD/IsoFS/IsoFS.cpp +++ b/pcsx2/CDVD/IsoFS/IsoFS.cpp @@ -20,7 +20,7 @@ #include "IsoFile.h" #include "common/Assertions.h" -#include "common/Exceptions.h" +#include "common/Error.h" #include "common/FileSystem.h" #include "common/Path.h" #include "common/StringUtil.h" @@ -51,6 +51,12 @@ std::string IsoDirectory::FStype_ToString() const // Used to load the Root directory from an image IsoDirectory::IsoDirectory(SectorSource& r) : internalReader(r) +{ +} + +IsoDirectory::~IsoDirectory() = default; + +bool IsoDirectory::OpenRootDirectory(Error* error /* = nullptr */) { IsoFileDescriptor rootDirEntry; bool isValid = false; @@ -107,22 +113,17 @@ IsoDirectory::IsoDirectory(SectorSource& r) } if (!isValid) - throw Exception::FileNotFound("IsoFileSystem") // FIXME: Should report the name of the ISO here... - .SetDiagMsg("IsoFS could not find the root directory on the ISO image."); + { + Error::SetString(error, "IsoFS could not find the root directory on the ISO image."); + return false; + } DevCon.WriteLn("(IsoFS) Filesystem is %s", FStype_ToString().c_str()); - Init(rootDirEntry); + return Open(rootDirEntry); } // Used to load a specific directory from a file descriptor -IsoDirectory::IsoDirectory(SectorSource& r, const IsoFileDescriptor& directoryEntry) - : internalReader(r) -{ - m_fstype = FStype_ISO9660; - Init(directoryEntry); -} - -void IsoDirectory::Init(const IsoFileDescriptor& directoryEntry) +bool IsoDirectory::Open(const IsoFileDescriptor& directoryEntry, Error* error /* = nullptr */) { // parse directory sector IsoFile dataStream(internalReader, directoryEntry); @@ -163,9 +164,10 @@ void IsoDirectory::Init(const IsoFileDescriptor& directoryEntry) } b[0] = 0; + return true; } -const IsoFileDescriptor& IsoDirectory::GetEntry(int index) const +const IsoFileDescriptor& IsoDirectory::GetEntry(size_t index) const { return files[index]; } @@ -178,25 +180,19 @@ int IsoDirectory::GetIndexOf(const std::string_view& fileName) const return i; } - throw Exception::FileNotFound(std::string(fileName)); + return -1; } -const IsoFileDescriptor& IsoDirectory::GetEntry(const std::string_view& fileName) const -{ - return GetEntry(GetIndexOf(fileName)); -} - -IsoFileDescriptor IsoDirectory::FindFile(const std::string_view& filePath) const +std::optional IsoDirectory::FindFile(const std::string_view& filePath) const { if (filePath.empty()) - throw Exception::FileNotFound(); + return std::nullopt; // DOS-style parser should work fine for ISO 9660 path names. Only practical difference // is case sensitivity, and that won't matter for path splitting. std::vector parts(Path::SplitWindowsPath(filePath)); - IsoFileDescriptor info; const IsoDirectory* dir = this; - std::unique_ptr deleteme; + IsoDirectory subdir(internalReader); // walk through path ("." and ".." entries are in the directories themselves, so even if the // path included . and/or .., it still works) @@ -206,35 +202,67 @@ IsoFileDescriptor IsoDirectory::FindFile(const std::string_view& filePath) const for (size_t index = has_device ? 1 : 0; index < (parts.size() - 1); index++) { - info = dir->GetEntry(parts[index]); - if (info.IsFile()) - throw Exception::FileNotFound(std::string(filePath)); + const int subdir_index = GetIndexOf(parts[index]); + if (subdir_index < 0) + return std::nullopt; - deleteme.reset(new IsoDirectory(internalReader, info)); - dir = deleteme.get(); + const IsoFileDescriptor& subdir_entry = GetEntry(static_cast(index)); + if (subdir_entry.IsFile() || !subdir.Open(subdir_entry, nullptr)) + return std::nullopt; + + dir = &subdir; } - info = dir->GetEntry(parts.back()); - return info; + const int file_index = dir->GetIndexOf(parts.back()); + if (file_index < 0) + return std::nullopt; + + return GetEntry(static_cast(file_index)); +} + +bool IsoDirectory::Exists(const std::string_view& filePath) const +{ + if (filePath.empty()) + return false; + + const std::optional fd(FindFile(filePath)); + return fd.has_value(); } bool IsoDirectory::IsFile(const std::string_view& filePath) const { if (filePath.empty()) return false; - return (FindFile(filePath).flags & 2) != 2; + + const std::optional fd(FindFile(filePath)); + if (fd.has_value()) + return false; + + return ((fd->flags & 2) != 2); } bool IsoDirectory::IsDir(const std::string_view& filePath) const { if (filePath.empty()) return false; - return (FindFile(filePath).flags & 2) == 2; + + const std::optional fd(FindFile(filePath)); + if (fd.has_value()) + return false; + + return ((fd->flags & 2) == 2); } u32 IsoDirectory::GetFileSize(const std::string_view& filePath) const { - return FindFile(filePath).size; + if (filePath.empty()) + return 0; + + const std::optional fd(FindFile(filePath)); + if (fd.has_value()) + return 0; + + return fd->size; } IsoFileDescriptor::IsoFileDescriptor() diff --git a/pcsx2/CDVD/IsoFS/IsoFS.h b/pcsx2/CDVD/IsoFS/IsoFS.h index 25bffa6d47..b51745b334 100644 --- a/pcsx2/CDVD/IsoFS/IsoFS.h +++ b/pcsx2/CDVD/IsoFS/IsoFS.h @@ -17,7 +17,7 @@ class IsoFile; class IsoDirectory; -struct ISoFileDescriptor; +struct IsoFileDescriptor; #include "SectorSource.h" #include "IsoFileDescriptor.h" diff --git a/pcsx2/CDVD/IsoFS/IsoFSCDVD.h b/pcsx2/CDVD/IsoFS/IsoFSCDVD.h index 9817dbc1c1..72239429c8 100644 --- a/pcsx2/CDVD/IsoFS/IsoFSCDVD.h +++ b/pcsx2/CDVD/IsoFS/IsoFSCDVD.h @@ -19,7 +19,7 @@ #include "SectorSource.h" -class IsoFSCDVD : public SectorSource +class IsoFSCDVD final : public SectorSource { public: IsoFSCDVD(); diff --git a/pcsx2/CDVD/IsoFS/IsoFile.cpp b/pcsx2/CDVD/IsoFS/IsoFile.cpp index 0d9be5c8cb..1bec502f25 100644 --- a/pcsx2/CDVD/IsoFS/IsoFile.cpp +++ b/pcsx2/CDVD/IsoFS/IsoFile.cpp @@ -16,26 +16,19 @@ #include "PrecompiledHeader.h" #include "common/Assertions.h" -#include "common/Exceptions.h" +#include "common/Error.h" #include "IsoFS.h" #include "IsoFile.h" #include -IsoFile::IsoFile(SectorSource& reader, const std::string_view& filename) +IsoFile::IsoFile(SectorSource& reader) : internalReader(reader) - , fileEntry(IsoDirectory(reader).FindFile(filename)) { - Init(); } -IsoFile::IsoFile(const IsoDirectory& dir, const std::string_view& filename) - : internalReader(dir.GetReader()) - , fileEntry(dir.FindFile(filename)) -{ - Init(); -} +IsoFile::~IsoFile() = default; IsoFile::IsoFile(SectorSource& reader, const IsoFileDescriptor& fileEntry) : internalReader(reader) @@ -51,12 +44,35 @@ void IsoFile::Init() currentSectorNumber = fileEntry.lba; currentOffset = 0; sectorOffset = 0; - maxOffset = std::max(0, fileEntry.size); + maxOffset = fileEntry.size; if (maxOffset > 0) internalReader.readSector(currentSector, currentSectorNumber); } +bool IsoFile::open(const IsoDirectory& dir, const std::string_view& filename, Error* error /*= nullptr*/) +{ + const std::optional fd(dir.FindFile(filename)); + if (!fd.has_value()) + { + Error::SetString(error, fmt::format("Failed to find file '{}'", filename)); + return false; + } + + fileEntry = std::move(fd.value()); + Init(); + return true; +} + +bool IsoFile::open(const std::string_view& filename, Error* error /*= nullptr*/) +{ + IsoDirectory dir(internalReader); + if (!dir.OpenRootDirectory(error)) + return false; + + return open(dir, filename, error); +} + u32 IsoFile::seek(u32 absoffset) { u32 endOffset = absoffset; @@ -142,7 +158,7 @@ void IsoFile::makeDataAvailable() u8 IsoFile::readByte() { if (currentOffset >= maxOffset) - throw Exception::EndOfStream(); + return 0; makeDataAvailable(); diff --git a/pcsx2/CDVD/IsoFS/IsoFile.h b/pcsx2/CDVD/IsoFS/IsoFile.h index 13e06ff67b..f2cc4fe195 100644 --- a/pcsx2/CDVD/IsoFS/IsoFile.h +++ b/pcsx2/CDVD/IsoFS/IsoFile.h @@ -21,27 +21,31 @@ #include "common/Pcsx2Defs.h" #include -class IsoFile +class Error; + +class IsoFile final { public: static const int sectorLength = 2048; protected: SectorSource& internalReader; - IsoFileDescriptor fileEntry; + IsoFileDescriptor fileEntry = {}; - u32 currentOffset; - u32 maxOffset; + u32 currentOffset = 0; + u32 maxOffset = 0; - int currentSectorNumber; + int currentSectorNumber = 0; + int sectorOffset = 0; u8 currentSector[sectorLength]; - int sectorOffset; public: - IsoFile(const IsoDirectory& dir, const std::string_view& filename); - IsoFile(SectorSource& reader, const std::string_view& filename); + IsoFile(SectorSource& reader); IsoFile(SectorSource& reader, const IsoFileDescriptor& fileEntry); - virtual ~IsoFile() = default; + ~IsoFile(); + + bool open(const IsoDirectory& dir, const std::string_view& filename, Error* error = nullptr); + bool open(const std::string_view& filename, Error* error = nullptr); u32 seek(u32 absoffset); u32 seek(s64 offset, int mode); diff --git a/pcsx2/Elfheader.cpp b/pcsx2/Elfheader.cpp index 37a03280c8..0d92906671 100644 --- a/pcsx2/Elfheader.cpp +++ b/pcsx2/Elfheader.cpp @@ -43,50 +43,79 @@ struct PSXEXEHeader static_assert(sizeof(PSXEXEHeader) == 0x800); #pragma pack(pop) -// All of ElfObjects functions. -ElfObject::ElfObject(std::string srcfile, IsoFile& isofile, bool isPSXElf_) - : data(isofile.getLength(), "ELF headers") - , header(*(ELF_HEADER*)data.GetPtr()) - , filename(std::move(srcfile)) - , isPSXElf(isPSXElf_) +ElfObject::ElfObject() = default; + +ElfObject::~ElfObject() = default; + +bool ElfObject::OpenIsoFile(std::string srcfile, IsoFile& isofile, bool isPSXElf_, Error* error) { - checkElfSize(data.GetSizeInBytes()); - readIso(isofile); - initElfHeaders(); + const u32 length = isofile.getLength(); + if (!CheckElfSize(length, error)) + return false; + + data.resize(length); + + const s32 rsize = isofile.read(data.data(), static_cast(length)); + if (rsize < static_cast(length)) + { + Error::SetString(error, "Failed to read ELF from ISO"); + return false; + } + + filename = std::move(srcfile); + isPSXElf = isPSXElf_; + InitElfHeaders(); + return true; } -ElfObject::ElfObject(std::string srcfile, u32 hdrsize, bool isPSXElf_) - : data(hdrsize, "ELF headers") - , header(*(ELF_HEADER*)data.GetPtr()) - , filename(std::move(srcfile)) - , isPSXElf(isPSXElf_) +bool ElfObject::OpenFile(std::string srcfile, bool isPSXElf_, Error* error) { - checkElfSize(data.GetSizeInBytes()); - readFile(); - initElfHeaders(); + auto fp = FileSystem::OpenManagedCFile(srcfile.c_str(), "rb", error); + FILESYSTEM_STAT_DATA sd; + if (!fp || !FileSystem::StatFile(fp.get(), &sd)) + { + Error::SetString(error, fmt::format("Failed to read ELF from '{}'", srcfile)); + return false; + } + + if (!isPSXElf_ && !CheckElfSize(sd.Size, error)) + return false; + + data.resize(static_cast(sd.Size)); + if (std::fread(data.data(), data.size(), 1, fp.get()) != 1) + { + Error::SetString(error, fmt::format("Failed to read ELF from '{}'", srcfile)); + return false; + } + + filename = std::move(srcfile); + isPSXElf = isPSXElf_; + InitElfHeaders(); + return true; } -void ElfObject::initElfHeaders() +void ElfObject::InitElfHeaders() { if (isPSXElf) return; - DevCon.WriteLn("Initializing Elf: %d bytes", data.GetSizeInBytes()); + DevCon.WriteLn("Initializing Elf: %zu bytes", data.size()); + const ELF_HEADER& header = GetHeader(); if (header.e_phnum > 0) { - if ((header.e_phoff + sizeof(ELF_PHR)) <= static_cast(data.GetSizeInBytes())) + if ((header.e_phoff + sizeof(ELF_PHR)) <= data.size()) proghead = reinterpret_cast(&data[header.e_phoff]); else - Console.Error("(ELF) Program header offset %u is larger than file size %u", header.e_phoff, data.GetSizeInBytes()); + Console.Error("(ELF) Program header offset %u is larger than file size %zu", header.e_phoff, data.size()); } if (header.e_shnum > 0) { - if ((header.e_shoff + sizeof(ELF_SHR)) <= static_cast(data.GetSizeInBytes())) + if ((header.e_shoff + sizeof(ELF_SHR)) <= data.size()) secthead = reinterpret_cast(&data[header.e_shoff]); else - Console.Error("(ELF) Section header offset %u is larger than file size %u", header.e_shoff, data.GetSizeInBytes()); + Console.Error("(ELF) Section header offset %u is larger than file size %zu", header.e_shoff, data.size()); } if ((header.e_shnum > 0) && (header.e_shentsize != sizeof(ELF_SHR))) @@ -147,49 +176,61 @@ void ElfObject::initElfHeaders() //applyPatches(); } -bool ElfObject::hasValidPSXHeader() +bool ElfObject::HasValidPSXHeader() const { - if (data.GetSizeInBytes() < static_cast(sizeof(PSXEXEHeader))) + if (data.size() < sizeof(PSXEXEHeader)) return false; - const PSXEXEHeader* header = reinterpret_cast(data.GetPtr()); + const PSXEXEHeader* header = reinterpret_cast(data.data()); static constexpr char expected_id[] = {'P', 'S', '-', 'X', ' ', 'E', 'X', 'E'}; if (std::memcmp(header->id, expected_id, sizeof(expected_id)) != 0) return false; - if (static_cast(header->file_size + sizeof(PSXEXEHeader)) > data.GetSizeInBytes()) + if ((header->file_size + sizeof(PSXEXEHeader)) > data.size()) { Console.Warning("Incorrect file size in PS-EXE header: %u bytes should not be greater than %u bytes", - header->file_size, static_cast(data.GetSizeInBytes() - sizeof(PSXEXEHeader))); + header->file_size, static_cast(data.size() - sizeof(PSXEXEHeader))); } return true; } -bool ElfObject::hasProgramHeaders() { return (proghead != NULL); } -bool ElfObject::hasSectionHeaders() { return (secthead != NULL); } -bool ElfObject::hasHeaders() { return (hasProgramHeaders() && hasSectionHeaders()); } +bool ElfObject::HasProgramHeaders() const +{ + return (proghead != nullptr); +} -u32 ElfObject::getEntryPoint() +bool ElfObject::HasSectionHeaders() const +{ + return (secthead != nullptr); +} + +bool ElfObject::HasHeaders() const +{ + return (HasProgramHeaders() && HasSectionHeaders()); +} + +u32 ElfObject::GetEntryPoint() const { if (isPSXElf) { - if (hasValidPSXHeader()) - return reinterpret_cast(data.GetPtr())->initial_pc; + if (HasValidPSXHeader()) + return reinterpret_cast(data.data())->initial_pc; else return 0xFFFFFFFFu; } else { - return header.e_entry; + return GetHeader().e_entry; } } -std::pair ElfObject::getTextRange() +std::pair ElfObject::GetTextRange() const { - if (!isPSXElf && hasProgramHeaders()) + if (!isPSXElf && HasProgramHeaders()) { + const ELF_HEADER& header = GetHeader(); for (int i = 0; i < header.e_phnum; i++) { const u32 start = proghead[i].p_vaddr; @@ -203,62 +244,36 @@ std::pair ElfObject::getTextRange() return std::make_pair(0,0); } -void ElfObject::readIso(IsoFile& file) +bool ElfObject::CheckElfSize(s64 size, Error* error) { - int rsize = file.read(data.GetPtr(), data.GetSizeInBytes()); - if (rsize < data.GetSizeInBytes()) throw Exception::EndOfStream(filename); + if (size > 0xfffffff) + Error::SetString(error, "Illegal ELF file size over 2GB!"); + else if (size == -1) + Error::SetString(error, "ELF file does not exist!"); + else if (size <= static_cast(sizeof(ELF_HEADER))) + Error::SetString(error, "Unexpected end of ELF file."); + else + return true; + + return false; } -void ElfObject::readFile() -{ - int rsize = 0; - FILE *f = FileSystem::OpenCFile( filename.c_str(), "rb"); - if (f == NULL) throw Exception::FileNotFound(filename); - - fseek(f, 0, SEEK_SET); - rsize = fread(data.GetPtr(), 1, data.GetSizeInBytes(), f); - fclose( f ); - - if (rsize < data.GetSizeInBytes()) throw Exception::EndOfStream(filename); -} - -static std::string GetMsg_InvalidELF() -{ - return - "Cannot load ELF binary image. The file may be corrupt or incomplete." - "\n\n" - "If loading from an ISO image, this error may be caused by an unsupported ISO image type or a bug in PCSX2 ISO image support."; -} - - -void ElfObject::checkElfSize(s64 elfsize) -{ - const char* diagMsg = NULL; - if (elfsize > 0xfffffff) diagMsg = "Illegal ELF file size over 2GB!"; - else if (elfsize == -1) diagMsg = "ELF file does not exist!"; - else if (elfsize == 0) diagMsg = "Unexpected end of ELF file."; - - if (diagMsg) - throw Exception::BadStream(filename) - .SetDiagMsg(diagMsg) - .SetUserMsg(GetMsg_InvalidELF()); -} - -u32 ElfObject::getCRC() +u32 ElfObject::GetCRC() const { u32 CRC = 0; - const u32* srcdata = (u32*)data.GetPtr(); - for(u32 i=data.GetSizeInBytes()/4; i; --i, ++srcdata) + const u32* srcdata = reinterpret_cast(data.data()); + for (u32 i = static_cast(data.size()) / 4; i; --i, ++srcdata) CRC ^= *srcdata; return CRC; } -void ElfObject::loadProgramHeaders() +void ElfObject::LoadProgramHeaders() { if (proghead == NULL) return; + const ELF_HEADER& header = GetHeader(); for( int i = 0 ; i < header.e_phnum ; i++ ) { ELF_LOG( "Elf32 Program Header" ); @@ -289,11 +304,17 @@ void ElfObject::loadProgramHeaders() } } -void ElfObject::loadSectionHeaders() +void ElfObject::LoadSectionHeaders() { - if (secthead == NULL || header.e_shoff > (u32)data.GetLength()) return; + const ELF_HEADER& header = GetHeader(); + if (!secthead || header.e_shoff > data.size()) + return; - const u8* sections_names = data.GetPtr( secthead[ (header.e_shstrndx == 0xffff ? 0 : header.e_shstrndx) ].sh_offset ); + // This function scares me a lot. There's a lot of potential for buffer overreads. + // All the accesses should be wrapped in bounds checked read() calls. + + const u32 section_names_offset = secthead[(header.e_shstrndx == 0xffff ? 0 : header.e_shstrndx)].sh_offset; + const u8* sections_names = data.data() + section_names_offset; int i_st = -1, i_dt = -1; @@ -343,28 +364,33 @@ void ElfObject::loadSectionHeaders() if ((i_st >= 0) && (i_dt >= 0)) { - const char * SymNames; - Elf32_Sym * eS; + const char* SymNames; + Elf32_Sym* eS; - SymNames = (char*)data.GetPtr(secthead[i_dt].sh_offset); - eS = (Elf32_Sym*)data.GetPtr(secthead[i_st].sh_offset); - Console.WriteLn("found %d symbols", secthead[i_st].sh_size / sizeof(Elf32_Sym)); + if (secthead[i_dt].sh_offset < data.size() && + secthead[i_st].sh_offset < data.size()) + { + SymNames = (char*)(data.data() + secthead[i_dt].sh_offset); + eS = (Elf32_Sym*)(data.data() + secthead[i_st].sh_offset); + Console.WriteLn("found %d symbols", secthead[i_st].sh_size / sizeof(Elf32_Sym)); - R5900SymbolMap.Clear(); - for(uint i = 1; i < (secthead[i_st].sh_size / sizeof(Elf32_Sym)); i++) { - if ((eS[i].st_value != 0) && (ELF32_ST_TYPE(eS[i].st_info) == 2)) + R5900SymbolMap.Clear(); + for (uint i = 1; i < (secthead[i_st].sh_size / sizeof(Elf32_Sym)); i++) { - R5900SymbolMap.AddLabel(&SymNames[eS[i].st_name],eS[i].st_value); + if ((eS[i].st_value != 0) && (ELF32_ST_TYPE(eS[i].st_info) == 2)) + { + R5900SymbolMap.AddLabel(&SymNames[eS[i].st_name], eS[i].st_value); + } } } } } -void ElfObject::loadHeaders() +void ElfObject::LoadHeaders() { if (isPSXElf) return; - loadProgramHeaders(); - loadSectionHeaders(); + LoadProgramHeaders(); + LoadSectionHeaders(); } diff --git a/pcsx2/Elfheader.h b/pcsx2/Elfheader.h index 211cece47d..6b2fb8f68b 100644 --- a/pcsx2/Elfheader.h +++ b/pcsx2/Elfheader.h @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 PCSX2 Dev Team + * Copyright (C) 2002-2023 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- @@ -15,10 +15,10 @@ #pragma once -#include "common/SafeArray.h" -#include "common/SafeArray.inl" +#include "common/Error.h" #include "CDVD/IsoFS/IsoFSCDVD.h" #include "CDVD/IsoFS/IsoFS.h" +#include struct ELF_HEADER { u8 e_ident[16]; //0x7f,"ELF" (ELF file identifier) @@ -118,39 +118,43 @@ struct Elf32_Rel { u32 r_info; }; -class ElfObject +class ElfObject final { - private: - SafeArray data; - ELF_HEADER& header; - ELF_PHR* proghead = nullptr; - ELF_SHR* secthead = nullptr; - std::string filename; - bool isPSXElf; +public: + ElfObject(); + ElfObject(const ElfObject&) = delete; + ~ElfObject(); - void initElfHeaders(); - bool hasValidPSXHeader(); - void readIso(IsoFile& file); - void readFile(); - void checkElfSize(s64 elfsize); + __fi const ELF_HEADER& GetHeader() const { return *reinterpret_cast(data.data()); } + __fi u32 GetSize() const { return static_cast(data.size()); } - public: - ElfObject(std::string srcfile, IsoFile& isofile, bool isPSXElf_); - ElfObject(std::string srcfile, u32 hdrsize, bool isPSXElf_); + bool OpenFile(std::string srcfile, bool isPSXElf_, Error* error); + bool OpenIsoFile(std::string srcfile, IsoFile& isofile, bool isPSXElf_, Error* error); - bool IsPSXElf() const { return isPSXElf; } + void LoadHeaders(); - void loadProgramHeaders(); - void loadSectionHeaders(); - void loadHeaders(); + bool HasProgramHeaders() const; + bool HasSectionHeaders() const; + bool HasHeaders() const; - bool hasProgramHeaders(); - bool hasSectionHeaders(); - bool hasHeaders(); + std::pair GetTextRange() const; + u32 GetEntryPoint() const; + u32 GetCRC() const; - std::pair getTextRange(); - u32 getEntryPoint(); - u32 getCRC(); +private: + std::vector data; + ELF_PHR* proghead = nullptr; + ELF_SHR* secthead = nullptr; + std::string filename; + bool isPSXElf; + + bool CheckElfSize(s64 size, Error* error); + + void InitElfHeaders(); + void LoadProgramHeaders(); + void LoadSectionHeaders(); + + bool HasValidPSXHeader() const; }; //------------------- diff --git a/pcsx2/GameList.cpp b/pcsx2/GameList.cpp index 06eb3b90fd..17a6d6f432 100644 --- a/pcsx2/GameList.cpp +++ b/pcsx2/GameList.cpp @@ -182,16 +182,8 @@ bool GameList::GetIsoSerialAndCRC(const std::string& path, s32* disc_type, std:: bool GameList::GetElfListEntry(const std::string& path, GameList::Entry* entry) { - const s64 file_size = FileSystem::GetPathFileSize(path.c_str()); - if (file_size <= 0) - return false; - - try - { - ElfObject eo(path, static_cast(file_size), false); - entry->crc = eo.getCRC(); - } - catch (...) + ElfObject eo; + if (!eo.OpenFile(path, false, nullptr)) { Console.Error("Failed to parse ELF '%s'", path.c_str()); return false; @@ -201,9 +193,10 @@ bool GameList::GetElfListEntry(const std::string& path, GameList::Entry* entry) entry->serial.clear(); entry->title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); entry->region = Region::Other; - entry->total_size = static_cast(file_size); entry->type = EntryType::ELF; entry->compatibility_rating = CompatibilityRating::Unknown; + entry->crc = eo.GetCRC(); + entry->total_size = eo.GetSize(); std::string disc_path(VMManager::GetDiscOverrideFromGameSettings(path)); if (!disc_path.empty()) diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index b66f8744aa..7d1eaa0fcd 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -589,7 +589,12 @@ std::string VMManager::GetGameSettingsPath(const std::string_view& game_serial, std::string VMManager::GetDiscOverrideFromGameSettings(const std::string& elf_path) { std::string iso_path; - if (const u32 crc = cdvdGetElfCRC(elf_path); crc != 0) + ElfObject elfo; + if (!elfo.OpenFile(elf_path, false, nullptr)) + return iso_path; + + const u32 crc = elfo.GetCRC(); + if (crc != 0) { INISettingsInterface si(GetGameSettingsPath(std::string_view(), crc)); if (si.Load()) @@ -941,29 +946,23 @@ void VMManager::HandleELFChange(bool verbose_patches_if_changed) void VMManager::UpdateELFInfo(std::string elf_path) { - try + Error error; + ElfObject elfo; + if (!cdvdLoadElf(&elfo, elf_path, false, &error)) { - std::unique_ptr elfptr = cdvdLoadElf(elf_path, false); - elfptr->loadHeaders(); - s_current_crc = elfptr->getCRC(); - s_elf_entry_point = elfptr->getEntryPoint(); - s_elf_text_range = elfptr->getTextRange(); - s_elf_path = std::move(elf_path); + Console.Error(fmt::format("Failed to read ELF being loaded: {}: {}", elf_path, error.GetDescription())); + s_elf_path = {}; + s_elf_text_range = {}; + s_elf_entry_point = 0xFFFFFFFFu; + s_current_crc = 0; return; } - catch ([[maybe_unused]] Exception::FileNotFound& e) - { - } - catch (Exception::BadStream& ex) - { - Console.Error(ex.FormatDiagnosticMessage()); - } - Console.Error(fmt::format("Failed to read ELF being loaded: {}", elf_path)); - s_elf_path = {}; - s_elf_text_range = {}; - s_elf_entry_point = 0xFFFFFFFFu; - s_current_crc = 0; + elfo.LoadHeaders(); + s_current_crc = elfo.GetCRC(); + s_elf_entry_point = elfo.GetEntryPoint(); + s_elf_text_range = elfo.GetTextRange(); + s_elf_path = std::move(elf_path); } void VMManager::ClearELFInfo()