Skip to content

Commit 4ad775e

Browse files
authored
Merge pull request #2013 from nicolasnoble/feature/isobrowser-filesystem
Isobrowser's filesystem
2 parents 147bace + 2e05407 commit 4ad775e

7 files changed

Lines changed: 978 additions & 14 deletions

File tree

src/cdrom/iso9660-reader.cc

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525

2626
PCSX::ISO9660Reader::ISO9660Reader(std::shared_ptr<CDRIso> iso) : m_iso(iso) {
2727
unsigned pvdSector = 16;
28+
bool foundPVD = false;
2829

30+
// Scan the full Volume Descriptor Set (LBA 16..terminator) to both find
31+
// the PVD and record where the set actually ends.
2932
while (true) {
30-
IO<File> pvdFile(new CDRIsoFile(iso, pvdSector++, 2048));
33+
IO<File> pvdFile(new CDRIsoFile(iso, pvdSector, 2048));
3134
if (pvdFile->failed()) {
3235
m_failed = true;
3336
return;
@@ -36,22 +39,27 @@ PCSX::ISO9660Reader::ISO9660Reader(std::shared_ptr<CDRIso> iso) : m_iso(iso) {
3639
uint8_t vd[7];
3740
pvdFile->readAt(vd, 7, 0);
3841
if ((vd[1] != 'C') || (vd[2] != 'D') || (vd[3] != '0') || (vd[4] != '0') || (vd[5] != '1') || (vd[6] != 1)) {
42+
// Malformed descriptor set: even if we already have the PVD, the
43+
// set is broken so we can't trust m_vdEnd.
3944
m_failed = true;
4045
return;
4146
}
4247

4348
if (vd[0] == 255) {
44-
m_failed = true;
49+
// Terminator: the VD set ends at the sector after this one.
50+
m_vdEnd = pvdSector + 1;
51+
if (!foundPVD) m_failed = true;
4552
return;
4653
}
4754

48-
if (vd[0] != 1) continue;
49-
50-
ISO9660LowLevel::PVD pvd;
51-
pvd.deserialize(pvdFile);
55+
if (vd[0] == 1 && !foundPVD) {
56+
ISO9660LowLevel::PVD pvd;
57+
pvd.deserialize(pvdFile);
58+
m_pvd = pvd;
59+
foundPVD = true;
60+
}
5261

53-
m_pvd = pvd;
54-
break;
62+
pvdSector++;
5563
}
5664
}
5765

src/cdrom/iso9660-reader.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,20 @@ class ISO9660Reader {
4040
return std::string_view(m_pvd.get<ISO9660LowLevel::PVD_VolumeIdent>());
4141
}
4242

43+
typedef std::pair<ISO9660LowLevel::DirEntry, ISO9660LowLevel::DirEntry_XA> FullDirEntry;
44+
std::vector<FullDirEntry> listAllEntriesFrom(const ISO9660LowLevel::DirEntry& entry);
45+
const ISO9660LowLevel::DirEntry& getRootDirEntry() { return m_pvd.get<ISO9660LowLevel::PVD_RootDir>(); }
46+
const ISO9660LowLevel::PVD& getPVD() { return m_pvd; }
47+
// Returns the LBA just past the end of the volume descriptor set, i.e. the
48+
// first sector after the VD terminator (type 255). Returns 17 if not found.
49+
uint32_t getVDEnd() { return m_vdEnd; }
50+
4351
private:
4452
std::shared_ptr<CDRIso> m_iso;
4553
bool m_failed = false;
46-
typedef std::pair<ISO9660LowLevel::DirEntry, ISO9660LowLevel::DirEntry_XA> FullDirEntry;
54+
uint32_t m_vdEnd = 17;
4755

4856
std::optional<FullDirEntry> findEntry(const std::string_view& filename);
49-
std::vector<FullDirEntry> listAllEntriesFrom(const ISO9660LowLevel::DirEntry& entry);
5057
ISO9660LowLevel::PVD m_pvd;
5158
};
5259

src/core/isoffi.lua

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,22 @@ bool isReaderFailed(IsoReader* reader);
4343
LuaFile* readerOpen(IsoReader* reader, const char* path);
4444
LuaFile* fileisoOpen(LuaIso* wrapper, uint32_t lba, uint32_t size, enum SectorMode mode);
4545

46+
typedef struct { char opaque[?]; } DirEntries;
47+
DirEntries* readerListDir(IsoReader* reader, const char* path);
48+
void deleteDirEntries(DirEntries* entries);
49+
uint32_t dirEntriesCount(DirEntries* entries);
50+
const char* dirEntryName(DirEntries* entries, uint32_t index);
51+
uint32_t dirEntryLBA(DirEntries* entries, uint32_t index);
52+
uint32_t dirEntrySize(DirEntries* entries, uint32_t index);
53+
bool dirEntryIsDir(DirEntries* entries, uint32_t index);
54+
55+
typedef struct { char opaque[?]; } GapList;
56+
GapList* readerFindGaps(IsoReader* reader);
57+
void deleteGapList(GapList* list);
58+
uint32_t gapListCount(GapList* list);
59+
uint32_t gapEntryLBA(GapList* list, uint32_t index);
60+
uint32_t gapEntrySectors(GapList* list, uint32_t index);
61+
4662
typedef struct { char opaque[?]; } ISO9660Builder;
4763
ISO9660Builder* createIsoBuilder(LuaFile* out);
4864
void deleteIsoBuilder(ISO9660Builder* builder);
@@ -58,6 +74,34 @@ local function createIsoReaderWrapper(isoReader)
5874
local reader = {
5975
_wrapper = ffi.gc(isoReader, C.deleteIsoReader),
6076
open = function(self, fname) return Support.File._createFileWrapper(C.readerOpen(self._wrapper, fname)) end,
77+
findGaps = function(self)
78+
local gapList = ffi.gc(C.readerFindGaps(self._wrapper), C.deleteGapList)
79+
local count = C.gapListCount(gapList)
80+
local result = {}
81+
for i = 0, count - 1 do
82+
table.insert(result, {
83+
lba = C.gapEntryLBA(gapList, i),
84+
sectors = C.gapEntrySectors(gapList, i),
85+
})
86+
end
87+
return result
88+
end,
89+
listDir = function(self, path)
90+
if path == nil then path = '' end
91+
local entries = ffi.gc(C.readerListDir(self._wrapper, path), C.deleteDirEntries)
92+
local count = C.dirEntriesCount(entries)
93+
local result = {}
94+
for i = 0, count - 1 do
95+
local name = ffi.string(C.dirEntryName(entries, i))
96+
table.insert(result, {
97+
name = name,
98+
lba = C.dirEntryLBA(entries, i),
99+
size = C.dirEntrySize(entries, i),
100+
isDir = C.dirEntryIsDir(entries, i),
101+
})
102+
end
103+
return result
104+
end,
61105
}
62106
return reader
63107
end

src/core/luaiso.cc

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@
1919

2020
#include "core/luaiso.h"
2121

22+
#include <algorithm>
2223
#include <memory>
24+
#include <unordered_set>
2325

2426
#include "cdrom/cdriso.h"
2527
#include "cdrom/file.h"
2628
#include "cdrom/iso9660-reader.h"
2729
#include "core/cdrom.h"
2830
#include "lua/luafile.h"
2931
#include "lua/luawrapper.h"
32+
#include "support/strings-helpers.h"
3033
#include "supportpsx/iso9660-builder.h"
3134

3235
namespace {
@@ -57,6 +60,166 @@ PCSX::LuaFFI::LuaFile* fileisoOpen(LuaIso* wrapper, uint32_t lba, uint32_t size,
5760
return new PCSX::LuaFFI::LuaFile(new PCSX::CDRIsoFile(wrapper->iso, lba, size, mode));
5861
}
5962

63+
struct DirEntries {
64+
std::vector<PCSX::ISO9660Reader::FullDirEntry> entries;
65+
};
66+
67+
// Drop ISO9660 "." (\0) and ".." (\1) sentinel entries from a listing.
68+
static std::vector<PCSX::ISO9660Reader::FullDirEntry> stripSelfParent(
69+
std::vector<PCSX::ISO9660Reader::FullDirEntry>&& entries) {
70+
std::vector<PCSX::ISO9660Reader::FullDirEntry> out;
71+
out.reserve(entries.size());
72+
for (auto& e : entries) {
73+
const auto& name = e.first.get<PCSX::ISO9660LowLevel::DirEntry_Filename>().value;
74+
if (name.size() == 1 && (name[0] == '\0' || name[0] == '\1')) continue;
75+
out.push_back(std::move(e));
76+
}
77+
return out;
78+
}
79+
80+
DirEntries* readerListDir(PCSX::ISO9660Reader* reader, const char* path) {
81+
if (reader->failed()) return new DirEntries{};
82+
auto root = reader->getRootDirEntry();
83+
if (path == nullptr || path[0] == '\0') {
84+
return new DirEntries{stripSelfParent(reader->listAllEntriesFrom(root))};
85+
}
86+
// Walk the path using listAllEntriesFrom. ISO9660 directory entries
87+
// don't carry version suffixes (only files do), so exact match on each
88+
// path component is correct for directory listing.
89+
auto parts = PCSX::StringsHelpers::split(std::string_view(path), "/");
90+
PCSX::ISO9660LowLevel::DirEntry current = root;
91+
for (auto& part : parts) {
92+
auto entries = reader->listAllEntriesFrom(current);
93+
bool found = false;
94+
for (auto& [entry, xa] : entries) {
95+
if (entry.get<PCSX::ISO9660LowLevel::DirEntry_Filename>().value == part) {
96+
current = entry;
97+
found = true;
98+
break;
99+
}
100+
}
101+
if (!found) return new DirEntries{};
102+
}
103+
// Only return children if the target is actually a directory.
104+
if ((current.get<PCSX::ISO9660LowLevel::DirEntry_Flags>().value & 2) == 0) {
105+
return new DirEntries{};
106+
}
107+
return new DirEntries{stripSelfParent(reader->listAllEntriesFrom(current))};
108+
}
109+
110+
void deleteDirEntries(DirEntries* entries) { delete entries; }
111+
uint32_t dirEntriesCount(DirEntries* entries) { return entries->entries.size(); }
112+
const char* dirEntryName(DirEntries* entries, uint32_t index) {
113+
if (index >= entries->entries.size()) return "";
114+
return entries->entries[index].first.get<PCSX::ISO9660LowLevel::DirEntry_Filename>().value.c_str();
115+
}
116+
uint32_t dirEntryLBA(DirEntries* entries, uint32_t index) {
117+
if (index >= entries->entries.size()) return 0;
118+
return entries->entries[index].first.get<PCSX::ISO9660LowLevel::DirEntry_LBA>();
119+
}
120+
uint32_t dirEntrySize(DirEntries* entries, uint32_t index) {
121+
if (index >= entries->entries.size()) return 0;
122+
return entries->entries[index].first.get<PCSX::ISO9660LowLevel::DirEntry_Size>();
123+
}
124+
bool dirEntryIsDir(DirEntries* entries, uint32_t index) {
125+
if (index >= entries->entries.size()) return false;
126+
return (entries->entries[index].first.get<PCSX::ISO9660LowLevel::DirEntry_Flags>().value & 2) != 0;
127+
}
128+
129+
struct GapEntry {
130+
uint32_t lba;
131+
uint32_t sectors;
132+
};
133+
134+
struct GapList {
135+
std::vector<GapEntry> gaps;
136+
};
137+
138+
// XA attribute bits (CD-XA spec, stored big-endian in directory record).
139+
// Bit 11 (0x0800): Mode 2 Form 1 data file
140+
// Bit 12 (0x1000): Mode 2 Form 2 interleaved audio/video
141+
static constexpr uint16_t XA_ATTR_FORM2 = 0x1000;
142+
143+
static void collectAllEntries(PCSX::ISO9660Reader* reader, const PCSX::ISO9660LowLevel::DirEntry& dir,
144+
std::vector<std::pair<uint32_t, uint32_t>>& out,
145+
std::unordered_set<uint32_t>& visitedDirs) {
146+
auto entries = reader->listAllEntriesFrom(dir);
147+
for (auto& [entry, xa] : entries) {
148+
const auto& name = entry.get<PCSX::ISO9660LowLevel::DirEntry_Filename>().value;
149+
if (name.size() == 1 && (name[0] == '\0' || name[0] == '\1')) continue;
150+
uint32_t lba = entry.get<PCSX::ISO9660LowLevel::DirEntry_LBA>();
151+
uint32_t size = entry.get<PCSX::ISO9660LowLevel::DirEntry_Size>();
152+
uint16_t attribs = xa.get<PCSX::ISO9660LowLevel::DirEntry_XA_Attribs>();
153+
uint32_t sectorSize = (attribs & XA_ATTR_FORM2) ? 2324 : 2048;
154+
uint32_t sectors = (size + sectorSize - 1) / sectorSize;
155+
// Skip zero-length extents: they don't consume sectors, and emitting
156+
// them would confuse the gap aggregation pass.
157+
if (sectors != 0) out.push_back({lba, sectors});
158+
bool isDir = (entry.get<PCSX::ISO9660LowLevel::DirEntry_Flags>().value & 2) != 0;
159+
// Guard against malformed ISOs with directory cycles.
160+
if (isDir && visitedDirs.insert(lba).second) {
161+
collectAllEntries(reader, entry, out, visitedDirs);
162+
}
163+
}
164+
}
165+
166+
GapList* readerFindGaps(PCSX::ISO9660Reader* reader) {
167+
if (reader->failed()) return new GapList{};
168+
std::vector<std::pair<uint32_t, uint32_t>> allFiles;
169+
170+
// Account for ISO9660 system structures
171+
allFiles.push_back({0, 16}); // License/system area
172+
auto& pvd = reader->getPVD();
173+
uint32_t vdEnd = reader->getVDEnd();
174+
allFiles.push_back({16, vdEnd > 16 ? vdEnd - 16 : 1}); // Volume descriptors including terminator
175+
uint32_t lPathLoc = pvd.get<PCSX::ISO9660LowLevel::PVD_LPathTableLocation>();
176+
uint32_t pathTableSize = pvd.get<PCSX::ISO9660LowLevel::PVD_PathTableSize>();
177+
uint32_t pathTableSectors = (pathTableSize + 2047) / 2048;
178+
allFiles.push_back({lPathLoc, pathTableSectors});
179+
uint32_t lPathOptLoc = pvd.get<PCSX::ISO9660LowLevel::PVD_LPathTableOptLocation>();
180+
if (lPathOptLoc != 0) allFiles.push_back({lPathOptLoc, pathTableSectors});
181+
uint32_t mPathLoc = pvd.get<PCSX::ISO9660LowLevel::PVD_MPathTableLocation>();
182+
allFiles.push_back({mPathLoc, pathTableSectors});
183+
uint32_t mPathOptLoc = pvd.get<PCSX::ISO9660LowLevel::PVD_MPathTableOptLocation>();
184+
if (mPathOptLoc != 0) allFiles.push_back({mPathOptLoc, pathTableSectors});
185+
auto& rootDir = reader->getRootDirEntry();
186+
uint32_t rootLBA = rootDir.get<PCSX::ISO9660LowLevel::DirEntry_LBA>();
187+
uint32_t rootSize = rootDir.get<PCSX::ISO9660LowLevel::DirEntry_Size>();
188+
allFiles.push_back({rootLBA, (rootSize + 2047) / 2048});
189+
190+
std::unordered_set<uint32_t> visitedDirs;
191+
visitedDirs.insert(rootLBA);
192+
collectAllEntries(reader, reader->getRootDirEntry(), allFiles, visitedDirs);
193+
std::sort(allFiles.begin(), allFiles.end());
194+
195+
auto* result = new GapList{};
196+
uint32_t nextExpected = 0;
197+
for (auto& [lba, sectors] : allFiles) {
198+
if (lba > nextExpected) {
199+
result->gaps.push_back({nextExpected, lba - nextExpected});
200+
}
201+
uint32_t end = lba + sectors;
202+
if (end > nextExpected) nextExpected = end;
203+
}
204+
// Trailing gap: anything between the last occupied extent and the disc end.
205+
uint32_t volumeSpaceSize = pvd.get<PCSX::ISO9660LowLevel::PVD_VolumeSpaceSize>();
206+
if (volumeSpaceSize > nextExpected) {
207+
result->gaps.push_back({nextExpected, volumeSpaceSize - nextExpected});
208+
}
209+
return result;
210+
}
211+
212+
void deleteGapList(GapList* list) { delete list; }
213+
uint32_t gapListCount(GapList* list) { return list->gaps.size(); }
214+
uint32_t gapEntryLBA(GapList* list, uint32_t index) {
215+
if (index >= list->gaps.size()) return 0;
216+
return list->gaps[index].lba;
217+
}
218+
uint32_t gapEntrySectors(GapList* list, uint32_t index) {
219+
if (index >= list->gaps.size()) return 0;
220+
return list->gaps[index].sectors;
221+
}
222+
60223
PCSX::ISO9660Builder* createIsoBuilder(PCSX::LuaFFI::LuaFile* wrapper) {
61224
return new PCSX::ISO9660Builder(wrapper->file);
62225
}
@@ -98,6 +261,20 @@ static void registerAllSymbols(PCSX::Lua L) {
98261
REGISTER(L, readerOpen);
99262
REGISTER(L, fileisoOpen);
100263

264+
REGISTER(L, readerListDir);
265+
REGISTER(L, deleteDirEntries);
266+
REGISTER(L, dirEntriesCount);
267+
REGISTER(L, dirEntryName);
268+
REGISTER(L, dirEntryLBA);
269+
REGISTER(L, dirEntrySize);
270+
REGISTER(L, dirEntryIsDir);
271+
272+
REGISTER(L, readerFindGaps);
273+
REGISTER(L, deleteGapList);
274+
REGISTER(L, gapListCount);
275+
REGISTER(L, gapEntryLBA);
276+
REGISTER(L, gapEntrySectors);
277+
101278
REGISTER(L, createIsoBuilder);
102279
REGISTER(L, deleteIsoBuilder);
103280
REGISTER(L, isoBuilderWriteLicense);

src/gui/gui.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ PCSX::GUI::GUI(std::vector<std::string>& favorites)
179179
m_openArchiveDialog(l_("Open Archive"), favorites),
180180
m_selectBiosDialog(l_("Select BIOS"), favorites),
181181
m_selectEXP1Dialog(l_("Select EXP1"), favorites),
182-
m_isoBrowser(settings.get<ShowIsoBrowser>().value, favorites),
182+
m_isoBrowser(settings.get<ShowIsoBrowser>().value, favorites, [this]() { useMonoFont(); }),
183183
m_pioCart(settings.get<ShowPIOCartConfig>().value, favorites) {
184184
assert(g_gui == nullptr);
185185
g_gui = this;

0 commit comments

Comments
 (0)