diff --git a/common/StringUtil.h b/common/StringUtil.h index 4b93490617..1b1138cd2d 100644 --- a/common/StringUtil.h +++ b/common/StringUtil.h @@ -15,6 +15,7 @@ #pragma once #include "Pcsx2Types.h" +#include #include #include #include @@ -340,4 +341,15 @@ namespace StringUtil /// Converts unsigned 128-bit data to string. std::string U128ToString(const u128& u); std::string& AppendU128ToString(const u128& u, std::string& s); + + template + static inline bool ContainsSubString(const ContainerType& haystack, const std::string_view& needle) + { + using ValueType = typename ContainerType::value_type; + if (needle.empty()) + return std::empty(haystack); + + return std::search(std::begin(haystack), std::end(haystack), reinterpret_cast(needle.data()), + reinterpret_cast(needle.data() + needle.length())) != std::end(haystack); + } } // namespace StringUtil diff --git a/pcsx2/Achievements.cpp b/pcsx2/Achievements.cpp index 1c78433e48..e936db50d3 100644 --- a/pcsx2/Achievements.cpp +++ b/pcsx2/Achievements.cpp @@ -19,8 +19,6 @@ #include "Achievements.h" #include "CDVD/CDVD.h" -#include "CDVD/IsoFS/IsoFS.h" -#include "CDVD/IsoFS/IsoFSCDVD.h" #include "Elfheader.h" #include "Host.h" #include "ImGui/FullscreenUI.h" @@ -37,6 +35,7 @@ #include "common/Assertions.h" #include "common/Console.h" #include "common/FileSystem.h" +#include "common/Error.h" #include "common/General.h" #include "common/HTTPDownloader.h" #include "common/MD5Digest.h" diff --git a/pcsx2/CDVD/CDVD.cpp b/pcsx2/CDVD/CDVD.cpp index 50c40f003a..d5c1ae3d18 100644 --- a/pcsx2/CDVD/CDVD.cpp +++ b/pcsx2/CDVD/CDVD.cpp @@ -1,5 +1,5 @@ /* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2021 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- @@ -14,6 +14,17 @@ */ #include "PrecompiledHeader.h" + +#include "CDVD/CDVD.h" +#include "CDVD/Ps1CD.h" +#include "CDVD/CDVD_internal.h" +#include "CDVD/IsoReader.h" +#include "CDVD/IsoFileFormats.h" +#include "GS.h" +#include "Elfheader.h" +#include "ps2/BiosTools.h" +#include "Recording/InputRecording.h" +#include "Host.h" #include "R3000A.h" #include "Common.h" #include "IopHw.h" @@ -21,25 +32,15 @@ #include "VMManager.h" #include "Sio.h" -#include -#include -#include - +#include "common/Error.h" #include "common/FileSystem.h" #include "common/Path.h" #include "common/StringUtil.h" #include "common/Threading.h" -#include "Ps1CD.h" -#include "CDVD.h" -#include "CDVD_internal.h" -#include "IsoFileFormats.h" - -#include "GS.h" // for gsVideoMode -#include "Elfheader.h" -#include "ps2/BiosTools.h" -#include "Recording/InputRecording.h" -#include "Host.h" +#include +#include +#include cdvdStruct cdvd; @@ -383,6 +384,44 @@ s32 cdvdWriteConfig(const u8* config) return 0; } +static bool cdvdUncheckedLoadDiscElf(ElfObject* elfo, IsoReader& isor, const std::string_view& elfpath, bool isPSXElf, Error* error) +{ + // Strip out cdrom: prefix, and any leading slashes. + size_t start_pos = (elfpath[5] == '0') ? 7 : 6; + while (start_pos < elfpath.size() && (elfpath[start_pos] == '\\' || elfpath[start_pos] == '/')) + start_pos++; + + // Strip out any version information. Some games use ;2 (MLB2k6), others put multiple versions in + // (Syphon Filter Omega Strain). The PS2 BIOS appears to ignore the suffix entirely, so we'll do + // the same, and hope that no games actually have multiple ELFs with different versions. + // Previous notes: + // 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). + // + size_t length = elfpath.length() - start_pos; + const size_t semi_pos = elfpath.find(';', start_pos); + if (semi_pos != std::string::npos) + length = semi_pos - start_pos; + + std::string iso_filename(elfpath.substr(start_pos, length)); + DevCon.WriteLn(fmt::format("cvdLoadElf(): '{}' -> '{}' in ISO.", elfpath, iso_filename)); + if (iso_filename.empty()) + { + Error::SetString(error, "ISO filename is empty."); + return false; + } + + return elfo->OpenIsoFile(std::move(iso_filename), isor, isPSXElf, error); +} + bool cdvdLoadElf(ElfObject* elfo, const std::string_view& elfpath, bool isPSXElf, Error* error) { if (StringUtil::StartsWith(elfpath, "host:")) @@ -392,52 +431,25 @@ bool cdvdLoadElf(ElfObject* elfo, const std::string_view& elfpath, bool isPSXElf } else if (StringUtil::StartsWith(elfpath, "cdrom:") || StringUtil::StartsWith(elfpath, "cdrom0:")) { - // Strip out cdrom: prefix, and any leading slashes. - size_t start_pos = (elfpath[5] == '0') ? 7 : 6; - while (start_pos < elfpath.size() && (elfpath[start_pos] == '\\' || elfpath[start_pos] == '/')) - start_pos++; - - // Strip out any version information. Some games use ;2 (MLB2k6), others put multiple versions in - // (Syphon Filter Omega Strain). The PS2 BIOS appears to ignore the suffix entirely, so we'll do - // the same, and hope that no games actually have multiple ELFs with different versions. - // Previous notes: - // 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). - // - size_t length = elfpath.length() - start_pos; - const size_t semi_pos = elfpath.find(';', start_pos); - if (semi_pos != std::string::npos) - length = semi_pos - start_pos; - - std::string iso_filename(elfpath.substr(start_pos, length)); - DevCon.WriteLn(fmt::format("cvdLoadElf(): '{}' -> '{}' in ISO.", elfpath, iso_filename)); - if (iso_filename.empty()) - { - Error::SetString(error, "ISO filename is empty."); - return false; - } - - IsoFSCDVD isofs; - IsoFile file(isofs); - if (!file.open(iso_filename, error)) + IsoReader isor; + if (!isor.Open(error)) return false; - return elfo->OpenIsoFile(std::move(iso_filename), file, isPSXElf, error); + return cdvdLoadDiscElf(elfo, isor, elfpath, isPSXElf, error); } else { Console.Error(fmt::format("cdvdLoadElf(): Unknown device in ELF path '{}'", elfpath)); + return false; } +} - return true; +bool cdvdLoadDiscElf(ElfObject* elfo, IsoReader& isor, const std::string_view& elfpath, bool isPSXElf, Error* error) +{ + if (!StringUtil::StartsWith(elfpath, "cdrom:") && !StringUtil::StartsWith(elfpath, "cdrom0:")) + return false; + + return cdvdUncheckedLoadDiscElf(elfo, isor, elfpath, isPSXElf, error); } u32 cdvdGetElfCRC(const std::string& path) @@ -449,65 +461,57 @@ u32 cdvdGetElfCRC(const std::string& path) return elfo.GetCRC(); } -static CDVDDiscType GetPS2ElfName(std::string* name, std::string* version) +static CDVDDiscType GetPS2ElfName(IsoReader& isor, std::string* name, std::string* version, Error* error) { CDVDDiscType retype = CDVDDiscType::Other; name->clear(); version->clear(); - Error error; - IsoFSCDVD isofs; - IsoFile file(isofs); - if (!file.open("SYSTEM.CNF", &error)) - { - Console.Error(fmt::format("(GetElfName) Failed to open SYSTEM.CNF: {}", error.GetDescription())); - return CDVDDiscType::Other; - } - - const int size = file.getLength(); - if (size == 0) + std::vector data; + if (!isor.ReadFile("SYSTEM.CNF", &data, error)) return CDVDDiscType::Other; - while (!file.eof()) + const std::vector lines = + StringUtil::SplitString(std::string_view(reinterpret_cast(data.data()), data.size()), '\n'); + for (size_t lineno = 0; lineno < lines.size(); lineno++) { - const std::string line(file.readLine()); + const std::string_view line = StringUtil::StripWhitespace(lines[lineno]); 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. + // Some games have a character on the last line of the file, don't print the error in those cases. + if (value.empty() && (lineno == (lines.size() - 1))) + { Console.Warning("(SYSTEM.CNF) Unusual or malformed entry in SYSTEM.CNF ignored:"); - Console.Indent().WriteLn(line); + Console.Indent().WriteLn(std::string(line)); continue; } if (key == "BOOT2") { - Console.WriteLn(Color_StrongBlue, fmt::format("(SYSTEM.CNF) Detected PS2 Disc = {}", value)); + DevCon.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)); + DevCon.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)); + DevCon.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)); + DevCon.WriteLn(Color_Blue, fmt::format("(SYSTEM.CNF) Software version = {}", value)); *version = value; } } - if (retype == CDVDDiscType::Other) - Console.Error("(GetElfName) Disc image is *not* a PlayStation or PS2 game!"); - + Error::SetString(error, "Disc image is *not* a PlayStation or PS2 game"); return retype; } @@ -566,8 +570,13 @@ static std::string ExecutablePathToSerial(const std::string& path) void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::string* out_version, u32* out_crc, CDVDDiscType* out_disc_type) { + Error error; + IsoReader isor; + std::string elfpath, version; - const CDVDDiscType disc_type = GetPS2ElfName(&elfpath, &version); + CDVDDiscType disc_type = CDVDDiscType::Other; + if (!isor.Open(&error) || (disc_type = GetPS2ElfName(isor, &elfpath, &version, &error)) == CDVDDiscType::Other) + Console.Error(fmt::format("Failed to get ELF name: {}", error.GetDescription())); // Don't bother parsing it if we don't need the CRC. if (out_crc) @@ -576,10 +585,9 @@ void cdvdGetDiscInfo(std::string* out_serial, std::string* out_elf_path, std::st if (disc_type == CDVDDiscType::PS2Disc || disc_type == CDVDDiscType::PS1Disc) { - Error error; ElfObject elfo; const bool isPSXElf = (disc_type == CDVDDiscType::PS1Disc); - if (!cdvdLoadElf(&elfo, elfpath, isPSXElf, &error)) + if (!cdvdLoadDiscElf(&elfo, isor, elfpath, isPSXElf, &error)) Console.Error(fmt::format("Failed to load ELF info for {}: {}", elfpath, error.GetDescription())); else crc = elfo.GetCRC(); diff --git a/pcsx2/CDVD/CDVD.h b/pcsx2/CDVD/CDVD.h index b31bdf9f50..1bb0478324 100644 --- a/pcsx2/CDVD/CDVD.h +++ b/pcsx2/CDVD/CDVD.h @@ -23,6 +23,7 @@ class Error; class ElfObject; +class IsoReader; #define btoi(b) ((b) / 16 * 10 + (b) % 16) /* BCD to u_char */ #define itob(i) ((i) / 10 * 16 + (i) % 10) /* u_char to BCD */ @@ -190,7 +191,8 @@ 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 bool cdvdLoadElf(ElfObject* elfo, const std::string_view& filename, bool isPSXElf, Error* error); +extern bool cdvdLoadElf(ElfObject* elfo, const std::string_view& elfpath, bool isPSXElf, Error* error); +extern bool cdvdLoadDiscElf(ElfObject* elfo, IsoReader& isor, const std::string_view& elfpath, bool isPSXElf, Error* error); extern s32 cdvdCtrlTrayOpen(); extern s32 cdvdCtrlTrayClose(); diff --git a/pcsx2/CDVD/CDVD_internal.h b/pcsx2/CDVD/CDVD_internal.h index ab3ee5d91f..27688aa106 100644 --- a/pcsx2/CDVD/CDVD_internal.h +++ b/pcsx2/CDVD/CDVD_internal.h @@ -15,6 +15,8 @@ #pragma once +#include "Common.h" + /* Interrupts - values are flag bits. diff --git a/pcsx2/CDVD/CDVDcommon.cpp b/pcsx2/CDVD/CDVDcommon.cpp index 90d2903abc..602c3efc1e 100644 --- a/pcsx2/CDVD/CDVDcommon.cpp +++ b/pcsx2/CDVD/CDVDcommon.cpp @@ -16,29 +16,29 @@ #include "PrecompiledHeader.h" -#define ENABLE_TIMESTAMPS - -#include -#include -#include -#include - -#include "fmt/core.h" - -#include "IsoFS/IsoFS.h" -#include "IsoFS/IsoFSCDVD.h" -#include "IsoFileFormats.h" - -#include "common/Assertions.h" -#include "common/FileSystem.h" -#include "common/Path.h" -#include "common/StringUtil.h" +#include "CDVD/CDVDcommon.h" +#include "CDVD/IsoReader.h" +#include "CDVD/IsoFileFormats.h" #include "DebugTools/SymbolMap.h" #include "Config.h" #include "Host.h" #include "IconsFontAwesome5.h" -CDVD_API* CDVD = NULL; +#include "common/Assertions.h" +#include "common/FileSystem.h" +#include "common/Path.h" +#include "common/StringUtil.h" + +#include +#include +#include +#include + +#include "fmt/core.h" + +#define ENABLE_TIMESTAMPS + +CDVD_API* CDVD = nullptr; // ---------------------------------------------------------------------------- // diskTypeCached @@ -71,37 +71,35 @@ static void CheckNullCDVD() // static int CheckDiskTypeFS(int baseType) { - IsoFSCDVD isofs; - IsoDirectory rootdir(isofs); - if (rootdir.OpenRootDirectory()) + IsoReader isor; + if (isor.Open()) { - if (IsoFile file(isofs); file.open(rootdir, "SYSTEM.CNF")) + std::vector data; + if (isor.ReadFile("SYSTEM.CNF", &data)) { - const int size = file.getLength(); - const std::unique_ptr buffer = std::make_unique(size + 1); - file.read(buffer.get(), size); - buffer[size] = '\0'; - - char* pos = strstr(buffer.get(), "BOOT2"); - if (pos == NULL) + if (StringUtil::ContainsSubString(data, "BOOT2")) { - pos = strstr(buffer.get(), "BOOT"); - if (pos == NULL) - return CDVD_TYPE_ILLEGAL; + // PS2 DVD/CD. + return (baseType == CDVD_TYPE_DETCTCD) ? CDVD_TYPE_PS2CD : CDVD_TYPE_PS2DVD; + } + + if (StringUtil::ContainsSubString(data, "BOOT")) + { + // PSX CD. return CDVD_TYPE_PSCD; } - return (baseType == CDVD_TYPE_DETCTCD) ? CDVD_TYPE_PS2CD : CDVD_TYPE_PS2DVD; + return CDVD_TYPE_ILLEGAL; } // PS2 Linux disc 2, doesn't have a System.CNF or a normal ELF - if (rootdir.Exists("P2L_0100.02")) + if (isor.FileExists("P2L_0100.02")) return CDVD_TYPE_PS2DVD; - if (rootdir.Exists("PSX.EXE")) + if (isor.FileExists("PSX.EXE")) return CDVD_TYPE_PSCD; - if (rootdir.Exists("VIDEO_TS/VIDEO_TS.IFO")) + if (isor.FileExists("VIDEO_TS/VIDEO_TS.IFO")) return CDVD_TYPE_DVDV; } diff --git a/pcsx2/CDVD/IsoFS/IsoDirectory.h b/pcsx2/CDVD/IsoFS/IsoDirectory.h deleted file mode 100644 index 5a2c800533..0000000000 --- a/pcsx2/CDVD/IsoFS/IsoDirectory.h +++ /dev/null @@ -1,60 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 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- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#include "common/Pcsx2Defs.h" -#include -#include -#include - -class Error; - -enum IsoFS_Type -{ - FStype_ISO9660 = 1, - FStype_Joliet = 2, -}; - -class IsoDirectory final -{ -public: - SectorSource& internalReader; - std::vector files; - IsoFS_Type m_fstype = FStype_ISO9660; - -public: - IsoDirectory(SectorSource& r); - ~IsoDirectory(); - - bool OpenRootDirectory(Error* error = nullptr); - bool Open(const IsoFileDescriptor& directoryEntry, Error* error = nullptr); - - std::string FStype_ToString() const; - SectorSource& GetReader() const { return internalReader; } - - bool Exists(const std::string_view& filePath) const; - bool IsFile(const std::string_view& filePath) const; - bool IsDir(const std::string_view& filePath) const; - - u32 GetFileSize(const std::string_view& filePath) const; - - std::optional FindFile(const std::string_view& filePath) const; - -protected: - const IsoFileDescriptor& GetEntry(size_t index) const; - - int GetIndexOf(const std::string_view& fileName) const; -}; diff --git a/pcsx2/CDVD/IsoFS/IsoFS.cpp b/pcsx2/CDVD/IsoFS/IsoFS.cpp deleted file mode 100644 index c3fe0fccaa..0000000000 --- a/pcsx2/CDVD/IsoFS/IsoFS.cpp +++ /dev/null @@ -1,310 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 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- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - - -#include "PrecompiledHeader.h" - -#include "IsoFS.h" -#include "IsoFile.h" - -#include "common/Assertions.h" -#include "common/Error.h" -#include "common/FileSystem.h" -#include "common/Path.h" -#include "common/StringUtil.h" - -#include - -////////////////////////////////////////////////////////////////////////// -// IsoDirectory -////////////////////////////////////////////////////////////////////////// - -//u8 filesystemType; // 0x01 = ISO9660, 0x02 = Joliet, 0xFF = NULL -//u8 volID[5]; // "CD001" - - -std::string IsoDirectory::FStype_ToString() const -{ - switch (m_fstype) - { - case FStype_ISO9660: - return "ISO9660"; - case FStype_Joliet: - return "Joliet"; - } - - return StringUtil::StdStringFromFormat("Unrecognized Code (0x%x)", m_fstype); -} - -// 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; - bool done = false; - uint i = 16; - - m_fstype = FStype_ISO9660; - - while (!done) - { - u8 sector[2048]; - // If this fails, we're not reading an iso, or it's bad. - if (!internalReader.readSector(sector, i)) - break; - - if (memcmp(§or[1], "CD001", 5) == 0) - { - switch (sector[0]) - { - case 0: - DevCon.WriteLn(Color_Green, "(IsoFS) Block 0x%x: Boot partition info.", i); - break; - - case 1: - DevCon.WriteLn("(IsoFS) Block 0x%x: Primary partition info.", i); - rootDirEntry.Load(sector + 156, 38); - isValid = true; - break; - - case 2: - // Probably means Joliet (long filenames support), which PCSX2 doesn't care about. - DevCon.WriteLn(Color_Green, "(IsoFS) Block 0x%x: Extended partition info.", i); - m_fstype = FStype_Joliet; - break; - - case 0xff: - // Null terminator. End of partition information. - done = true; - break; - - default: - Console.Error("(IsoFS) Unknown partition type ID=%d, encountered at block 0x%x", sector[0], i); - break; - } - } - else - { - sector[9] = 0; - Console.Error("(IsoFS) Invalid partition descriptor encountered at block 0x%x: '%s'", i, §or[1]); - break; // if no valid root partition was found, an exception will be thrown below. - } - - ++i; - } - - if (!isValid) - { - 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()); - return Open(rootDirEntry); -} - -// Used to load a specific directory from a file descriptor -bool IsoDirectory::Open(const IsoFileDescriptor& directoryEntry, Error* error /* = nullptr */) -{ - // parse directory sector - IsoFile dataStream(internalReader, directoryEntry); - - files.clear(); - - u32 remainingSize = directoryEntry.size; - - u8 b[257]; - - while (remainingSize >= 4) // hm hack :P - { - b[0] = dataStream.read(); - - if (b[0] == 0) - { - break; // or continue? - } - - remainingSize -= b[0]; - - if (dataStream.read(b + 1, static_cast(b[0] - 1)) != static_cast(b[0] - 1)) - break; - - files.emplace_back(b, b[0]); - } - - return true; -} - -const IsoFileDescriptor& IsoDirectory::GetEntry(size_t index) const -{ - return files[index]; -} - -int IsoDirectory::GetIndexOf(const std::string_view& fileName) const -{ - for (unsigned int i = 0; i < files.size(); i++) - { - if (files[i].name == fileName) - return i; - } - - return -1; -} - -std::optional IsoDirectory::FindFile(const std::string_view& filePath) const -{ - if (filePath.empty()) - 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)); - const IsoDirectory* dir = this; - IsoDirectory subdir(internalReader); - - for (size_t index = 0; index < (parts.size() - 1); index++) - { - const int subdir_index = dir->GetIndexOf(parts[index]); - if (subdir_index < 0) - return std::nullopt; - - const IsoFileDescriptor& subdir_entry = GetEntry(static_cast(subdir_index)); - if (subdir_entry.IsFile() || !subdir.Open(subdir_entry, nullptr)) - return std::nullopt; - - dir = &subdir; - } - - const int file_index = dir->GetIndexOf(parts.back()); - if (file_index < 0) - return std::nullopt; - - return dir->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; - - 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; - - 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 -{ - if (filePath.empty()) - return 0; - - const std::optional fd(FindFile(filePath)); - if (fd.has_value()) - return 0; - - return fd->size; -} - -IsoFileDescriptor::IsoFileDescriptor() - : lba(0) - , size(0) - , flags(0) -{ - memset(&date, 0, sizeof(date)); -} - -IsoFileDescriptor::IsoFileDescriptor(const u8* data, int length) -{ - Load(data, length); -} - -void IsoFileDescriptor::Load(const u8* data, int length) -{ - lba = (u32&)data[2]; - size = (u32&)data[10]; - - date.year = data[18] + 1900; - date.month = data[19]; - date.day = data[20]; - date.hour = data[21]; - date.minute = data[22]; - date.second = data[23]; - date.gmtOffset = data[24]; - - flags = data[25]; - - int file_name_length = data[32]; - - if (file_name_length == 1) - { - u8 c = data[33]; - - switch (c) - { - case 0: - name = "."; - break; - case 1: - name = ".."; - break; - default: - name = static_cast(c); - break; - } - } - else - { - const u8* fnsrc = data + 33; - - // Strip any version information like the PS2 BIOS does. - int length_without_version = 0; - for (; length_without_version < file_name_length; length_without_version++) - { - if (fnsrc[length_without_version] == ';' || fnsrc[length_without_version] == '\0') - break; - } - - name.assign(reinterpret_cast(fnsrc), length_without_version); - } -} diff --git a/pcsx2/CDVD/IsoFS/IsoFS.h b/pcsx2/CDVD/IsoFS/IsoFS.h deleted file mode 100644 index b51745b334..0000000000 --- a/pcsx2/CDVD/IsoFS/IsoFS.h +++ /dev/null @@ -1,25 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 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- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -class IsoFile; -class IsoDirectory; -struct IsoFileDescriptor; - -#include "SectorSource.h" -#include "IsoFileDescriptor.h" -#include "IsoDirectory.h" -#include "IsoFile.h" diff --git a/pcsx2/CDVD/IsoFS/IsoFSCDVD.cpp b/pcsx2/CDVD/IsoFS/IsoFSCDVD.cpp deleted file mode 100644 index 5fa89a1872..0000000000 --- a/pcsx2/CDVD/IsoFS/IsoFSCDVD.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 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- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - - -#include "PrecompiledHeader.h" - -#include "IsoFSCDVD.h" -#include "CDVD/CDVDcommon.h" - -IsoFSCDVD::IsoFSCDVD() -{ -} - -bool IsoFSCDVD::readSector(unsigned char* buffer, int lba) -{ - return DoCDVDreadSector(buffer, lba, CDVD_MODE_2048) >= 0; -} - -int IsoFSCDVD::getNumSectors() -{ - cdvdTD td; - CDVD->getTD(0, &td); - - return td.lsn; -} diff --git a/pcsx2/CDVD/IsoFS/IsoFSCDVD.h b/pcsx2/CDVD/IsoFS/IsoFSCDVD.h deleted file mode 100644 index 72239429c8..0000000000 --- a/pcsx2/CDVD/IsoFS/IsoFSCDVD.h +++ /dev/null @@ -1,31 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 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- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#include - -#include "SectorSource.h" - -class IsoFSCDVD final : public SectorSource -{ -public: - IsoFSCDVD(); - virtual ~IsoFSCDVD() = default; - - virtual bool readSector(unsigned char* buffer, int lba); - - virtual int getNumSectors(); -}; diff --git a/pcsx2/CDVD/IsoFS/IsoFile.cpp b/pcsx2/CDVD/IsoFS/IsoFile.cpp deleted file mode 100644 index 1bec502f25..0000000000 --- a/pcsx2/CDVD/IsoFS/IsoFile.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 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- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - - -#include "PrecompiledHeader.h" -#include "common/Assertions.h" -#include "common/Error.h" - -#include "IsoFS.h" -#include "IsoFile.h" - -#include - -IsoFile::IsoFile(SectorSource& reader) - : internalReader(reader) -{ -} - -IsoFile::~IsoFile() = default; - -IsoFile::IsoFile(SectorSource& reader, const IsoFileDescriptor& fileEntry) - : internalReader(reader) - , fileEntry(fileEntry) -{ - Init(); -} - -void IsoFile::Init() -{ - //pxAssertDev( fileEntry.IsFile(), "IsoFile Error: Filename points to a directory." ); - - currentSectorNumber = fileEntry.lba; - currentOffset = 0; - sectorOffset = 0; - 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; - - int oldSectorNumber = currentSectorNumber; - int newSectorNumber = fileEntry.lba + (int)(endOffset / sectorLength); - - if (oldSectorNumber != newSectorNumber) - { - internalReader.readSector(currentSector, newSectorNumber); - } - - currentOffset = endOffset; - currentSectorNumber = newSectorNumber; - sectorOffset = (int)(currentOffset % sectorLength); - - return currentOffset; -} - -// Returns the new offset in the file. Out-of-bounds seeks are automatically truncated at 0 -// and fileLength. -u32 IsoFile::seek(s64 offset, int mode) -{ - switch (mode) - { - case SEEK_SET: - pxAssertDev(offset >= 0 && offset <= (s64)ULONG_MAX, "Invalid seek position from start."); - return seek(offset); - - case SEEK_CUR: - // truncate negative values to zero, and positive values to 4gb - return seek(std::min(std::max(0, (s64)currentOffset + offset), (s64)ULONG_MAX)); - - case SEEK_END: - // truncate negative values to zero, and positive values to 4gb - return seek(std::min(std::max(0, (s64)fileEntry.size + offset), (s64)ULONG_MAX)); - - jNO_DEFAULT; - } - - return 0; // unreachable -} - -void IsoFile::reset() -{ - seek(0); -} - -// Returns the number of bytes actually skipped. -s32 IsoFile::skip(s32 n) -{ - s32 oldOffset = currentOffset; - - if (n < 0) - return 0; - - seek(currentOffset + n); - - return currentOffset - oldOffset; -} - -u32 IsoFile::getSeekPos() const -{ - return currentOffset; -} - -bool IsoFile::eof() const -{ - return (currentOffset == maxOffset); -} - -// loads the current sector index into the CurrentSector buffer. -void IsoFile::makeDataAvailable() -{ - if (sectorOffset >= sectorLength) - { - currentSectorNumber++; - internalReader.readSector(currentSector, currentSectorNumber); - sectorOffset -= sectorLength; - } -} - -u8 IsoFile::readByte() -{ - if (currentOffset >= maxOffset) - return 0; - - makeDataAvailable(); - - currentOffset++; - - return currentSector[sectorOffset++]; -} - -// Reads data from a single sector at a time. Reads cannot cross sector boundaries. -int IsoFile::internalRead(void* dest, int off, int len) -{ - if (len > 0) - { - size_t slen = len; - if (slen > (maxOffset - currentOffset)) - { - slen = (int)(maxOffset - currentOffset); - } - - memcpy((u8*)dest + off, currentSector + sectorOffset, slen); - - sectorOffset += slen; - currentOffset += slen; - return slen; - } - return 0; -} - -// returns the number of bytes actually read. -s32 IsoFile::read(void* dest, s32 len) -{ - pxAssert(dest != NULL); - pxAssert(len >= 0); // should we silent-fail on negative length reads? prolly not... - - if (len <= 0) - return 0; - - int off = 0; - - int totalLength = 0; - - int firstSector = internalRead(dest, off, std::min(len, sectorLength - sectorOffset)); - off += firstSector; - len -= firstSector; - totalLength += firstSector; - - // Read whole sectors - while ((len >= sectorLength) && (currentOffset < maxOffset)) - { - makeDataAvailable(); - int n = internalRead(dest, off, sectorLength); - off += n; - len -= n; - totalLength += n; - } - - // Read remaining, if any - if (len > 0) - { - makeDataAvailable(); - int lastSector = internalRead(dest, off, len); - totalLength += lastSector; - } - - return totalLength; -} - -// Reads data until it reaches a newline character (either \n, \r, or ASCII-Z). The caller is -// responsible for handling files with DOS-style newlines (CR/LF pairs), if needed. The resulting -// string has no newlines. -// -// Read data is unformatted 8 bit / Ascii. If the source file is known to be UTF8, use the fromUTF8() -// conversion helper provided by PCSX2 utility classes. -// -std::string IsoFile::readLine() -{ - std::string s; - s.reserve(512); - - while (!eof()) - { - u8 c = read(); - - if ((c == '\n') || (c == '\r') || (c == 0)) - break; - - s += c; - } - - return s; -} - -u32 IsoFile::getLength() -{ - return maxOffset; -} - -const IsoFileDescriptor& IsoFile::getEntry() -{ - return fileEntry; -} diff --git a/pcsx2/CDVD/IsoFS/IsoFile.h b/pcsx2/CDVD/IsoFS/IsoFile.h deleted file mode 100644 index f2cc4fe195..0000000000 --- a/pcsx2/CDVD/IsoFS/IsoFile.h +++ /dev/null @@ -1,83 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 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- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#include "IsoFileDescriptor.h" -#include "SectorSource.h" - -#include "common/Pcsx2Defs.h" -#include - -class Error; - -class IsoFile final -{ -public: - static const int sectorLength = 2048; - -protected: - SectorSource& internalReader; - IsoFileDescriptor fileEntry = {}; - - u32 currentOffset = 0; - u32 maxOffset = 0; - - int currentSectorNumber = 0; - int sectorOffset = 0; - u8 currentSector[sectorLength]; - -public: - IsoFile(SectorSource& reader); - IsoFile(SectorSource& reader, const IsoFileDescriptor& fileEntry); - ~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); - void reset(); - - s32 skip(s32 n); - u32 getSeekPos() const; - u32 getLength(); - bool eof() const; - - const IsoFileDescriptor& getEntry(); - - u8 readByte(); - s32 read(void* dest, s32 len); - std::string readLine(); - - // Tool to read a specific value type, including structs. - template - T read() - { - if (sizeof(T) == 1) - return (T)readByte(); - else - { - T t; - read((u8*)&t, sizeof(t)); - return t; - } - } - -protected: - void makeDataAvailable(); - int internalRead(void* dest, int off, int len); - void Init(); -}; diff --git a/pcsx2/CDVD/IsoFS/IsoFileDescriptor.h b/pcsx2/CDVD/IsoFS/IsoFileDescriptor.h deleted file mode 100644 index 3ce37838fb..0000000000 --- a/pcsx2/CDVD/IsoFS/IsoFileDescriptor.h +++ /dev/null @@ -1,47 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 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- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -#include "common/Pcsx2Defs.h" -#include - -struct IsoFileDescriptor -{ - struct FileDate // not 1:1 with iso9660 date struct - { - s32 year; - u8 month; - u8 day; - u8 hour; - u8 minute; - u8 second; - u8 gmtOffset; // Offset from Greenwich Mean Time in number of 15 min intervals from -48 (West) to + 52 (East) - - } date; - - u32 lba; - u32 size; - int flags; - std::string name; - - IsoFileDescriptor(); - IsoFileDescriptor(const u8* data, int length); - - void Load(const u8* data, int length); - - bool IsFile() const { return !(flags & 2); } - bool IsDir() const { return !IsFile(); } -}; diff --git a/pcsx2/CDVD/IsoFS/SectorSource.h b/pcsx2/CDVD/IsoFS/SectorSource.h deleted file mode 100644 index ae79b69750..0000000000 --- a/pcsx2/CDVD/IsoFS/SectorSource.h +++ /dev/null @@ -1,24 +0,0 @@ -/* PCSX2 - PS2 Emulator for PCs - * Copyright (C) 2002-2010 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- - * ation, either version 3 of the License, or (at your option) any later version. - * - * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with PCSX2. - * If not, see . - */ - -#pragma once - -class SectorSource -{ -public: - virtual int getNumSectors() = 0; - virtual bool readSector(unsigned char* buffer, int lba) = 0; - virtual ~SectorSource() = default; -}; diff --git a/pcsx2/CDVD/IsoReader.cpp b/pcsx2/CDVD/IsoReader.cpp new file mode 100644 index 0000000000..91e3df89dd --- /dev/null +++ b/pcsx2/CDVD/IsoReader.cpp @@ -0,0 +1,315 @@ +/* PCSX2 - PS2 Emulator for PCs + * 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- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "CDVD/CDVDcommon.h" +#include "CDVD/IsoReader.h" + +#include "common/Assertions.h" +#include "common/Error.h" +#include "common/StringUtil.h" + +#include "fmt/format.h" + +#include + +IsoReader::IsoReader() = default; + +IsoReader::~IsoReader() = default; + +bool IsoReader::Open(Error* error) +{ + if (!ReadPVD(error)) + return false; + + return true; +} + +bool IsoReader::ReadSector(u8* buf, u32 lsn, Error* error) +{ + if (DoCDVDreadSector(buf, lsn, CDVD_MODE_2048) != 0) + { + Error::SetString(error, fmt::format("Failed to read sector LSN #{}", lsn)); + return false; + } + + return true; +} + +bool IsoReader::ReadPVD(Error* error) +{ + // volume descriptor start at sector 16 + static constexpr u32 START_SECTOR = 16; + + // try only a maximum of 256 volume descriptors + for (u32 i = 0; i < 256; i++) + { + u8 buffer[SECTOR_SIZE]; + if (!ReadSector(buffer, START_SECTOR + i, error)) + return false; + + const ISOVolumeDescriptorHeader* header = reinterpret_cast(buffer); + if (std::memcmp(header->standard_identifier, "CD001", 5) != 0) + continue; + else if (header->type_code != 1) + continue; + else if (header->type_code == 255) + break; + + std::memcpy(&m_pvd, buffer, sizeof(ISOPrimaryVolumeDescriptor)); + DevCon.WriteLn(fmt::format("ISOReader: PVD found at index {}", i)); + return true; + } + + Error::SetString(error, "Failed to find the Primary Volume Descriptor."); + return false; +} + +std::optional IsoReader::LocateFile(const std::string_view& path, Error* error) +{ + const ISODirectoryEntry* root_de = reinterpret_cast(m_pvd.root_directory_entry); + if (path.empty() || path == "/" || path == "\\") + { + // locating the root directory + return *root_de; + } + + // start at the root directory + u8 sector_buffer[SECTOR_SIZE]; + return LocateFile(path, sector_buffer, root_de->location_le, root_de->length_le, error); +} + +std::string_view IsoReader::GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset) +{ + const ISODirectoryEntry* de = reinterpret_cast(sector + de_sector_offset); + if ((sizeof(ISODirectoryEntry) + de->filename_length) > de->entry_length || + (sizeof(ISODirectoryEntry) + de->filename_length + de_sector_offset) > SECTOR_SIZE) + { + return std::string_view(); + } + + const char* str = reinterpret_cast(sector + de_sector_offset + sizeof(ISODirectoryEntry)); + if (de->filename_length == 1) + { + if (str[0] == '\0') + return "."; + else if (str[0] == '\1') + return ".."; + } + + // Strip any version information like the PS2 BIOS does. + u32 length_without_version = 0; + for (; length_without_version < de->filename_length; length_without_version++) + { + if (str[length_without_version] == ';' || str[length_without_version] == '\0') + break; + } + + return std::string_view(str, length_without_version); +} + +std::optional IsoReader::LocateFile( + const std::string_view& path, u8* sector_buffer, u32 directory_record_lba, u32 directory_record_size, Error* error) +{ + if (directory_record_size == 0) + { + Error::SetString(error, fmt::format("Directory entry record size 0 while looking for '{}'", path)); + return std::nullopt; + } + + // strip any leading slashes + size_t path_component_start = 0; + while (path_component_start < path.length() && + (path[path_component_start] == '/' || path[path_component_start] == '\\')) + { + path_component_start++; + } + + size_t path_component_length = 0; + while ((path_component_start + path_component_length) < path.length() && + path[path_component_start + path_component_length] != '/' && + path[path_component_start + path_component_length] != '\\') + { + path_component_length++; + } + + const std::string_view path_component = path.substr(path_component_start, path_component_length); + if (path_component.empty()) + { + Error::SetString(error, fmt::format("Empty path component in {}", path)); + return std::nullopt; + } + + // start reading directory entries + const u32 num_sectors = (directory_record_size + (SECTOR_SIZE - 1)) / SECTOR_SIZE; + for (u32 i = 0; i < num_sectors; i++) + { + if (!ReadSector(sector_buffer, directory_record_lba + i, error)) + return std::nullopt; + + u32 sector_offset = 0; + while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE) + { + const ISODirectoryEntry* de = reinterpret_cast(§or_buffer[sector_offset]); + if (de->entry_length < sizeof(ISODirectoryEntry)) + break; + + const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset); + sector_offset += de->entry_length; + + // Empty file would be pretty strange.. + if (de_filename.empty() || de_filename == "." || de_filename == "..") + continue; + + if (!StringUtil::compareNoCase(de_filename, path_component)) + continue; + + // found it. is this the file we're looking for? + if ((path_component_start + path_component_length) == path.length()) + return *de; + + // if it is a directory, recurse into it + if (de->flags & ISODirectoryEntryFlag_Directory) + { + return LocateFile(path.substr(path_component_start + path_component_length), sector_buffer, + de->location_le, de->length_le, error); + } + + // we're looking for a directory but got a file + Error::SetString(error, fmt::format("Looking for directory '{}' but got file", path_component)); + return std::nullopt; + } + } + + Error::SetString(error, fmt::format("Path component '{}' not found", path_component)); + return std::nullopt; +} + +std::vector IsoReader::GetFilesInDirectory(const std::string_view& path, Error* error) +{ + std::string base_path(path); + u32 directory_record_lsn; + u32 directory_record_length; + if (base_path.empty()) + { + // root directory + const ISODirectoryEntry* root_de = reinterpret_cast(m_pvd.root_directory_entry); + directory_record_lsn = root_de->location_le; + directory_record_length = root_de->length_le; + } + else + { + auto directory_de = LocateFile(base_path, error); + if (!directory_de.has_value()) + return {}; + + if ((directory_de->flags & ISODirectoryEntryFlag_Directory) == 0) + { + Error::SetString(error, fmt::format("Path '{}' is not a directory, can't list", path)); + return {}; + } + + directory_record_lsn = directory_de->location_le; + directory_record_length = directory_de->length_le; + + if (base_path[base_path.size() - 1] != '/') + base_path += '/'; + } + + // start reading directory entries + const u32 num_sectors = (directory_record_length + (SECTOR_SIZE - 1)) / SECTOR_SIZE; + std::vector files; + u8 sector_buffer[SECTOR_SIZE]; + for (u32 i = 0; i < num_sectors; i++) + { + if (!ReadSector(sector_buffer, directory_record_lsn + i, error)) + break; + + u32 sector_offset = 0; + while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE) + { + const ISODirectoryEntry* de = reinterpret_cast(§or_buffer[sector_offset]); + if (de->entry_length < sizeof(ISODirectoryEntry)) + break; + + const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset); + sector_offset += de->entry_length; + + // Empty file would be pretty strange.. + if (de_filename.empty() || de_filename == "." || de_filename == "..") + continue; + + files.push_back(fmt::format("{}/{}", base_path, de_filename)); + } + } + + return files; +} + +bool IsoReader::FileExists(const std::string_view& path, Error* error) +{ + auto de = LocateFile(path, error); + if (!de) + return false; + + return (de->flags & ISODirectoryEntryFlag_Directory) == 0; +} + +bool IsoReader::DirectoryExists(const std::string_view& path, Error* error) +{ + auto de = LocateFile(path, error); + if (!de) + return false; + + return (de->flags & ISODirectoryEntryFlag_Directory) == ISODirectoryEntryFlag_Directory; +} + +bool IsoReader::ReadFile(const std::string_view& path, std::vector* data, Error* error) +{ + auto de = LocateFile(path, error); + if (!de) + return false; + + return ReadFile(de.value(), data, error); +} + +bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector* data, Error* error /*= nullptr*/) +{ + if (de.flags & ISODirectoryEntryFlag_Directory) + { + Error::SetString(error, "File is a directory"); + return false; + } + + if (de.length_le == 0) + { + data->clear(); + return true; + } + + static_assert(sizeof(size_t) == sizeof(u64)); + const u32 num_sectors = (de.length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE; + data->resize(num_sectors * static_cast(SECTOR_SIZE)); + for (u32 i = 0, lsn = de.location_le; i < num_sectors; i++, lsn++) + { + if (!ReadSector(data->data() + (i * SECTOR_SIZE), lsn, error)) + return false; + } + + // Might not be sector aligned, so reduce it back. + data->resize(de.length_le); + return true; +} diff --git a/pcsx2/CDVD/IsoReader.h b/pcsx2/CDVD/IsoReader.h new file mode 100644 index 0000000000..c8c2e0fdcf --- /dev/null +++ b/pcsx2/CDVD/IsoReader.h @@ -0,0 +1,176 @@ +/* PCSX2 - PS2 Emulator for PCs + * 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- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include "common/Pcsx2Defs.h" + +#include +#include +#include +#include + +class Error; + +class IsoReader +{ +public: + enum : u32 + { + SECTOR_SIZE = 2048 + }; + +#pragma pack(push, 1) + + struct ISOVolumeDescriptorHeader + { + u8 type_code; + char standard_identifier[5]; + u8 version; + }; + static_assert(sizeof(ISOVolumeDescriptorHeader) == 7); + + struct ISOBootRecord + { + ISOVolumeDescriptorHeader header; + char boot_system_identifier[32]; + char boot_identifier[32]; + u8 data[1977]; + }; + static_assert(sizeof(ISOBootRecord) == 2048); + + struct ISOPVDDateTime + { + char year[4]; + char month[2]; + char day[2]; + char hour[2]; + char minute[2]; + char second[2]; + char milliseconds[2]; + s8 gmt_offset; + }; + static_assert(sizeof(ISOPVDDateTime) == 17); + + struct ISOPrimaryVolumeDescriptor + { + ISOVolumeDescriptorHeader header; + u8 unused; + char system_identifier[32]; + char volume_identifier[32]; + char unused2[8]; + u32 total_sectors_le; + u32 total_sectors_be; + char unused3[32]; + u16 volume_set_size_le; + u16 volume_set_size_be; + u16 volume_sequence_number_le; + u16 volume_sequence_number_be; + u16 block_size_le; + u16 block_size_be; + u32 path_table_size_le; + u32 path_table_size_be; + u32 path_table_location_le; + u32 optional_path_table_location_le; + u32 path_table_location_be; + u32 optional_path_table_location_be; + u8 root_directory_entry[34]; + char volume_set_identifier[128]; + char publisher_identifier[128]; + char data_preparer_identifier[128]; + char application_identifier[128]; + char copyright_file_identifier[38]; + char abstract_file_identifier[36]; + char bibliographic_file_identifier[37]; + ISOPVDDateTime volume_creation_time; + ISOPVDDateTime volume_modification_time; + ISOPVDDateTime volume_expiration_time; + ISOPVDDateTime volume_effective_time; + u8 structure_version; + u8 unused4; + u8 application_used[512]; + u8 reserved[653]; + }; + static_assert(sizeof(ISOPrimaryVolumeDescriptor) == 2048); + + struct ISODirectoryEntryDateTime + { + u8 years_since_1900; + u8 month; + u8 day; + u8 hour; + u8 minute; + u8 second; + s8 gmt_offset; + }; + + enum ISODirectoryEntryFlags : u8 + { + ISODirectoryEntryFlag_Hidden = (1 << 0), + ISODirectoryEntryFlag_Directory = (1 << 1), + ISODirectoryEntryFlag_AssociatedFile = (1 << 2), + ISODirectoryEntryFlag_ExtendedAttributePresent = (1 << 3), + ISODirectoryEntryFlag_OwnerGroupPermissions = (1 << 4), + ISODirectoryEntryFlag_MoreExtents = (1 << 7), + }; + + struct ISODirectoryEntry + { + u8 entry_length; + u8 extended_attribute_length; + u32 location_le; + u32 location_be; + u32 length_le; + u32 length_be; + ISODirectoryEntryDateTime recoding_time; + ISODirectoryEntryFlags flags; + u8 interleaved_unit_size; + u8 interleaved_gap_size; + u16 sequence_le; + u16 sequence_be; + u8 filename_length; + }; + +#pragma pack(pop) + + IsoReader(); + ~IsoReader(); + + const ISOPrimaryVolumeDescriptor& GetPVD() const { return m_pvd; } + + // TODO: Eventually we'll want to pass a handle to the currently-open file here. + // ... once I have the energy to make CDVD not depend on a global object. + bool Open(Error* error = nullptr); + + std::vector GetFilesInDirectory(const std::string_view& path, Error* error = nullptr); + + std::optional LocateFile(const std::string_view& path, Error* error); + + bool FileExists(const std::string_view& path, Error* error = nullptr); + bool DirectoryExists(const std::string_view& path, Error* error = nullptr); + bool ReadFile(const std::string_view& path, std::vector* data, Error* error = nullptr); + bool ReadFile(const ISODirectoryEntry& de, std::vector* data, Error* error = nullptr); + +private: + static std::string_view GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset); + + bool ReadSector(u8* buf, u32 lsn, Error* error); + bool ReadPVD(Error* error); + + std::optional LocateFile(const std::string_view& path, u8* sector_buffer, + u32 directory_record_lba, u32 directory_record_size, Error* error); + + ISOPrimaryVolumeDescriptor m_pvd = {}; +}; diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 7b30bba330..8f58291fcf 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -247,6 +247,7 @@ set(pcsx2CDVDSources CDVD/CDVDisoReader.cpp CDVD/CDVDdiscThread.cpp CDVD/InputIsoFile.cpp + CDVD/IsoReader.cpp CDVD/OutputIsoFile.cpp CDVD/ChunksCache.cpp CDVD/CompressedFileReader.cpp @@ -254,9 +255,6 @@ set(pcsx2CDVDSources CDVD/CsoFileReader.cpp CDVD/GzippedFileReader.cpp CDVD/ThreadedFileReader.cpp - CDVD/IsoFS/IsoFile.cpp - CDVD/IsoFS/IsoFSCDVD.cpp - CDVD/IsoFS/IsoFS.cpp ) # CDVD headers @@ -273,12 +271,7 @@ set(pcsx2CDVDHeaders CDVD/GzippedFileReader.h CDVD/ThreadedFileReader.h CDVD/IsoFileFormats.h - CDVD/IsoFS/IsoDirectory.h - CDVD/IsoFS/IsoFileDescriptor.h - CDVD/IsoFS/IsoFile.h - CDVD/IsoFS/IsoFSCDVD.h - CDVD/IsoFS/IsoFS.h - CDVD/IsoFS/SectorSource.h + CDVD/IsoReader.h CDVD/zlib_indexed.h ) diff --git a/pcsx2/Elfheader.cpp b/pcsx2/Elfheader.cpp index 0d92906671..64004d7692 100644 --- a/pcsx2/Elfheader.cpp +++ b/pcsx2/Elfheader.cpp @@ -14,14 +14,16 @@ */ #include "PrecompiledHeader.h" -#include "Common.h" + +#include "Elfheader.h" +#include "CDVD/IsoReader.h" +#include "DebugTools/Debug.h" +#include "DebugTools/SymbolMap.h" + +#include "common/Error.h" #include "common/FileSystem.h" #include "common/StringUtil.h" -#include "GS.h" // for sending game crc to mtgs -#include "Elfheader.h" -#include "DebugTools/SymbolMap.h" - #pragma pack(push, 1) struct PSXEXEHeader { @@ -47,20 +49,17 @@ ElfObject::ElfObject() = default; ElfObject::~ElfObject() = default; -bool ElfObject::OpenIsoFile(std::string srcfile, IsoFile& isofile, bool isPSXElf_, Error* error) +bool ElfObject::OpenIsoFile(std::string srcfile, IsoReader& isor, bool isPSXElf_, Error* error) { - const u32 length = isofile.getLength(); - if (!CheckElfSize(length, error)) + const auto de = isor.LocateFile(srcfile, error); + if (!de) 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"); + if (!CheckElfSize(de->length_le, error)) + return false; + + if (!isor.ReadFile(de.value(), &data, error)) return false; - } filename = std::move(srcfile); isPSXElf = isPSXElf_; diff --git a/pcsx2/Elfheader.h b/pcsx2/Elfheader.h index 592a4bcf03..99f8e59e85 100644 --- a/pcsx2/Elfheader.h +++ b/pcsx2/Elfheader.h @@ -15,11 +15,11 @@ #pragma once -#include "common/Error.h" -#include "CDVD/IsoFS/IsoFSCDVD.h" -#include "CDVD/IsoFS/IsoFS.h" #include +class Error; +class IsoReader; + struct ELF_HEADER { u8 e_ident[16]; //0x7f,"ELF" (ELF file identifier) u16 e_type; //ELF type: 0=NONE, 1=REL, 2=EXEC, 3=SHARED, 4=CORE @@ -130,7 +130,7 @@ public: __fi u32 GetSize() const { return static_cast(data.size()); } bool OpenFile(std::string srcfile, bool isPSXElf_, Error* error); - bool OpenIsoFile(std::string srcfile, IsoFile& isofile, bool isPSXElf_, Error* error); + bool OpenIsoFile(std::string srcfile, IsoReader& isor, bool isPSXElf_, Error* error); void LoadHeaders(); diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 222ede1c80..bbc2509043 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -949,9 +949,11 @@ void VMManager::UpdateELFInfo(std::string elf_path) { Error error; ElfObject elfo; - if (!cdvdLoadElf(&elfo, elf_path, false, &error)) + if (elf_path.empty() || !cdvdLoadElf(&elfo, elf_path, false, &error)) { - Console.Error(fmt::format("Failed to read ELF being loaded: {}: {}", elf_path, error.GetDescription())); + if (!elf_path.empty()) + 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; @@ -1039,7 +1041,6 @@ bool VMManager::AutoDetectSource(const std::string& filename) { // make sure we're not fast booting when we have no filename CDVDsys_ChangeSource(CDVD_SourceType::NoDisc); - s_fast_boot_requested = false; return true; } } @@ -1157,6 +1158,7 @@ bool VMManager::Initialize(VMBootParameters boot_params) // ELFs must be fast booted, and GS dumps are never fast booted. s_fast_boot_requested = (boot_params.fast_boot.value_or(static_cast(EmuConfig.EnableFastBoot)) || !s_elf_override.empty()) && + (CDVDsys_GetSourceType() != CDVD_SourceType::NoDisc || !s_elf_override.empty()) && !GSDumpReplayer::IsReplayingDump(); if (!s_elf_override.empty()) @@ -1169,7 +1171,6 @@ bool VMManager::Initialize(VMBootParameters boot_params) } Hle_SetElfPath(s_elf_override.c_str()); - s_fast_boot_requested = true; } else { diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index f2e7e29bea..9e0eb0c1ee 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -114,6 +114,7 @@ + @@ -466,9 +467,6 @@ - - - @@ -480,6 +478,7 @@ + @@ -802,12 +801,6 @@ - - - - - - diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters index 9583cf75ea..c4628011e1 100644 --- a/pcsx2/pcsx2.vcxproj.filters +++ b/pcsx2/pcsx2.vcxproj.filters @@ -680,15 +680,6 @@ System\Ps2\iCore - - System\IsoFS - - - System\IsoFS - - - System\IsoFS - Misc @@ -1406,6 +1397,7 @@ Misc\ImGui + @@ -1636,24 +1628,6 @@ System\Ps2\iCore - - System\IsoFS - - - System\IsoFS - - - System\IsoFS - - - System\IsoFS - - - System\IsoFS - - - System\IsoFS - Misc @@ -2354,6 +2328,7 @@ Misc\ImGui +