Skip to content

Commit d45e683

Browse files
committed
Address CodeRabbit PR 2013 review feedback
- Lua dir filter now also skips empty names (ffi.string returns "" for a single NUL byte, so the ".","..'," check needed the empty case) - Gap/System/Hidden hex edit opens with explicit SectorMode so CDRIsoFile doesn't misdetect sector size (RAW for gap/system, M1/M2_FORM1/M2_FORM2 for hidden files based on their detected mode) - Sector span now uses XA attribute Form 2 bit (0x1000) to select 2324-byte sector size for Mode 2 Form 2 files, instead of assuming 2048 everywhere - readerListDir drops the redundant reader->open() check, verifies the target is a directory before returning children - File replace is now a coroutine like extract: yields every 50ms to keep UI responsive, warns when replacement is larger than target, zero-pads the tail when smaller so stale bytes don't leak through - ISO9660Reader constructor consolidates the PVD loop to also track the VD set terminator; new getVDEnd() returns the first sector past the terminator for accurate "Volume Descriptors" system region size - FlatEntry gains a sectors field so gap detection uses the actual on-disc footprint rather than recomputing from logical size Signed-off-by: Nicolas 'Pixel' Noble <nicolas@nobis-crew.org>
1 parent f56c000 commit d45e683

6 files changed

Lines changed: 116 additions & 44 deletions

File tree

src/cdrom/iso9660-reader.cc

Lines changed: 15 additions & 9 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,25 @@ 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)) {
39-
m_failed = true;
42+
if (!foundPVD) m_failed = true;
4043
return;
4144
}
4245

4346
if (vd[0] == 255) {
44-
m_failed = true;
47+
// Terminator: the VD set ends at the sector after this one.
48+
m_vdEnd = pvdSector + 1;
49+
if (!foundPVD) m_failed = true;
4550
return;
4651
}
4752

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

53-
m_pvd = pvd;
54-
break;
60+
pvdSector++;
5561
}
5662
}
5763

src/cdrom/iso9660-reader.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,14 @@ class ISO9660Reader {
4444
std::vector<FullDirEntry> listAllEntriesFrom(const ISO9660LowLevel::DirEntry& entry);
4545
const ISO9660LowLevel::DirEntry& getRootDirEntry() { return m_pvd.get<ISO9660LowLevel::PVD_RootDir>(); }
4646
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; }
4750

4851
private:
4952
std::shared_ptr<CDRIso> m_iso;
5053
bool m_failed = false;
54+
uint32_t m_vdEnd = 17;
5155

5256
std::optional<FullDirEntry> findEntry(const std::string_view& filename);
5357
ISO9660LowLevel::PVD m_pvd;

src/core/isoffi.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ local function createIsoReaderWrapper(isoReader)
9393
local result = {}
9494
for i = 0, count - 1 do
9595
local name = ffi.string(C.dirEntryName(entries, i))
96-
if name ~= '\0' and name ~= '\1' then
96+
if name ~= '' and name ~= '\0' and name ~= '\1' then
9797
table.insert(result, {
9898
name = name,
9999
lba = C.dirEntryLBA(entries, i),

src/core/luaiso.cc

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,9 @@ DirEntries* readerListDir(PCSX::ISO9660Reader* reader, const char* path) {
6767
if (path == nullptr || path[0] == '\0') {
6868
return new DirEntries{reader->listAllEntriesFrom(root)};
6969
}
70-
auto* file = reader->open(path);
71-
if (file->failed()) {
72-
delete file;
73-
return new DirEntries{};
74-
}
75-
delete file;
76-
// Need to find the directory entry for the given path to list its contents.
77-
// Walk the path manually using listAllEntriesFrom.
70+
// Walk the path using listAllEntriesFrom. ISO9660 directory entries
71+
// don't carry version suffixes (only files do), so exact match on each
72+
// path component is correct for directory listing.
7873
auto parts = PCSX::StringsHelpers::split(std::string_view(path), "/");
7974
PCSX::ISO9660LowLevel::DirEntry current = root;
8075
for (auto& part : parts) {
@@ -89,6 +84,10 @@ DirEntries* readerListDir(PCSX::ISO9660Reader* reader, const char* path) {
8984
}
9085
if (!found) return new DirEntries{};
9186
}
87+
// Only return children if the target is actually a directory.
88+
if ((current.get<PCSX::ISO9660LowLevel::DirEntry_Flags>().value & 2) == 0) {
89+
return new DirEntries{};
90+
}
9291
return new DirEntries{reader->listAllEntriesFrom(current)};
9392
}
9493

@@ -120,6 +119,11 @@ struct GapList {
120119
std::vector<GapEntry> gaps;
121120
};
122121

122+
// XA attribute bits (CD-XA spec, stored big-endian in directory record).
123+
// Bit 11 (0x0800): Mode 2 Form 1 data file
124+
// Bit 12 (0x1000): Mode 2 Form 2 interleaved audio/video
125+
static constexpr uint16_t XA_ATTR_FORM2 = 0x1000;
126+
123127
static void collectAllEntries(PCSX::ISO9660Reader* reader, const PCSX::ISO9660LowLevel::DirEntry& dir,
124128
std::vector<std::pair<uint32_t, uint32_t>>& out) {
125129
auto entries = reader->listAllEntriesFrom(dir);
@@ -128,7 +132,9 @@ static void collectAllEntries(PCSX::ISO9660Reader* reader, const PCSX::ISO9660Lo
128132
if (name.size() == 1 && (name[0] == '\0' || name[0] == '\1')) continue;
129133
uint32_t lba = entry.get<PCSX::ISO9660LowLevel::DirEntry_LBA>();
130134
uint32_t size = entry.get<PCSX::ISO9660LowLevel::DirEntry_Size>();
131-
uint32_t sectors = (size + 2047) / 2048;
135+
uint16_t attribs = xa.get<PCSX::ISO9660LowLevel::DirEntry_XA_Attribs>();
136+
uint32_t sectorSize = (attribs & XA_ATTR_FORM2) ? 2324 : 2048;
137+
uint32_t sectors = (size + sectorSize - 1) / sectorSize;
132138
out.push_back({lba, sectors});
133139
bool isDir = (entry.get<PCSX::ISO9660LowLevel::DirEntry_Flags>().value & 2) != 0;
134140
if (isDir) collectAllEntries(reader, entry, out);
@@ -142,8 +148,9 @@ GapList* readerFindGaps(PCSX::ISO9660Reader* reader) {
142148
// Account for ISO9660 system structures
143149
allFiles.push_back({0, 16}); // License/system area
144150
auto& pvd = reader->getPVD();
151+
uint32_t vdEnd = reader->getVDEnd();
152+
allFiles.push_back({16, vdEnd > 16 ? vdEnd - 16 : 1}); // Volume descriptors including terminator
145153
uint32_t lPathLoc = pvd.get<PCSX::ISO9660LowLevel::PVD_LPathTableLocation>();
146-
allFiles.push_back({16, lPathLoc > 16 ? lPathLoc - 16 : 1}); // Volume descriptors
147154
uint32_t pathTableSize = pvd.get<PCSX::ISO9660LowLevel::PVD_PathTableSize>();
148155
uint32_t pathTableSectors = (pathTableSize + 2047) / 2048;
149156
allFiles.push_back({lPathLoc, pathTableSectors});

src/gui/widgets/isobrowser.cc

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,12 @@ void PCSX::Widgets::IsoBrowser::collectFlatEntries(const ISO9660LowLevel::DirEnt
139139
uint32_t size = dirEntry.get<ISO9660LowLevel::DirEntry_Size>();
140140
auto fullPath = path.empty() ? filename : path + "/" + filename;
141141

142-
m_flatEntries.push_back({fullPath, lba, size, isDir ? FlatEntry::Directory : FlatEntry::File, dirEntry});
142+
// Form 2 files use 2324-byte data sectors; everything else logical uses 2048.
143+
uint16_t xaAttribs = xa.get<ISO9660LowLevel::DirEntry_XA_Attribs>();
144+
uint32_t sectorSize = (xaAttribs & 0x1000) ? 2324 : 2048;
145+
uint32_t sectors = (size + sectorSize - 1) / sectorSize;
146+
m_flatEntries.push_back(
147+
{fullPath, lba, size, sectors, isDir ? FlatEntry::Directory : FlatEntry::File, dirEntry});
143148
if (isDir) collectFlatEntries(dirEntry, fullPath);
144149
}
145150
}
@@ -155,7 +160,7 @@ void PCSX::Widgets::IsoBrowser::scanGapSectors(std::vector<FlatEntry>& out, uint
155160
// Read failed, treat rest as gap
156161
uint32_t remaining = end - lba;
157162
auto label = fmt::format(f_("<gap {} sectors>"), remaining);
158-
out.push_back({label, lba, remaining * 2352, FlatEntry::Gap, {}});
163+
out.push_back({label, lba, remaining * 2352, remaining, FlatEntry::Gap, {}});
159164
break;
160165
}
161166

@@ -173,7 +178,7 @@ void PCSX::Widgets::IsoBrowser::scanGapSectors(std::vector<FlatEntry>& out, uint
173178
}
174179
uint32_t count = lba - gapStart;
175180
auto label = fmt::format(f_("<gap {} sectors>"), count);
176-
out.push_back({label, gapStart, count * 2352, FlatEntry::Gap, {}});
181+
out.push_back({label, gapStart, count * 2352, count, FlatEntry::Gap, {}});
177182
continue;
178183
}
179184

@@ -188,7 +193,7 @@ void PCSX::Widgets::IsoBrowser::scanGapSectors(std::vector<FlatEntry>& out, uint
188193
}
189194
uint32_t count = lba - fileStart;
190195
auto label = fmt::format(f_("<hidden M1 {} sectors>"), count);
191-
out.push_back({label, fileStart, count * 2048, FlatEntry::HiddenM1, {}});
196+
out.push_back({label, fileStart, count * 2048, count, FlatEntry::HiddenM1, {}});
192197
continue;
193198
}
194199

@@ -219,7 +224,7 @@ void PCSX::Widgets::IsoBrowser::scanGapSectors(std::vector<FlatEntry>& out, uint
219224
uint32_t dataSize = isForm2 ? count * 2324 : count * 2048;
220225
auto label = fmt::format(f_("<hidden {} f={} ch={} {} sectors>"),
221226
isForm2 ? "M2F2" : "M2F1", fileNum, channelNum, count);
222-
out.push_back({label, fileStart, dataSize, type, {}});
227+
out.push_back({label, fileStart, dataSize, count, type, {}});
223228
continue;
224229
}
225230

@@ -234,30 +239,36 @@ void PCSX::Widgets::IsoBrowser::drawFilesystemFlat() {
234239

235240
// Add ISO9660 system structures
236241
auto& pvd = m_reader->getPVD();
237-
m_flatEntries.push_back({_("<License/System Area>"), 0, 16 * 2352, FlatEntry::System, {}});
238-
m_flatEntries.push_back({_("<Volume Descriptors>"), 16, 2048, FlatEntry::System, {}});
239-
// End volume descriptor at sector 17 (minimum), but path table location tells us where it ends
242+
uint32_t vdEnd = m_reader->getVDEnd();
243+
m_flatEntries.push_back({_("<License/System Area>"), 0, 16 * 2352, 16, FlatEntry::System, {}});
244+
// Volume descriptor set spans from LBA 16 up to (but not including) vdEnd,
245+
// including the PVD, any SVDs, and the terminator.
246+
uint32_t vdSectors = vdEnd > 16 ? vdEnd - 16 : 1;
247+
m_flatEntries.push_back(
248+
{_("<Volume Descriptors>"), 16, vdSectors * 2048, vdSectors, FlatEntry::System, {}});
240249
uint32_t lPathLoc = pvd.get<ISO9660LowLevel::PVD_LPathTableLocation>();
241-
if (lPathLoc > 17) {
242-
m_flatEntries.push_back({_("<Volume Descriptors cont.>"), 17, (lPathLoc - 17) * 2048, FlatEntry::System, {}});
243-
}
244250
uint32_t pathTableSize = pvd.get<ISO9660LowLevel::PVD_PathTableSize>();
245251
uint32_t pathTableSectors = (pathTableSize + 2047) / 2048;
246-
m_flatEntries.push_back({_("<L Path Table>"), lPathLoc, pathTableSize, FlatEntry::System, {}});
252+
m_flatEntries.push_back(
253+
{_("<L Path Table>"), lPathLoc, pathTableSize, pathTableSectors, FlatEntry::System, {}});
247254
uint32_t lPathOptLoc = pvd.get<ISO9660LowLevel::PVD_LPathTableOptLocation>();
248255
if (lPathOptLoc != 0) {
249-
m_flatEntries.push_back({_("<L Path Table (opt)>"), lPathOptLoc, pathTableSize, FlatEntry::System, {}});
256+
m_flatEntries.push_back(
257+
{_("<L Path Table (opt)>"), lPathOptLoc, pathTableSize, pathTableSectors, FlatEntry::System, {}});
250258
}
251259
uint32_t mPathLoc = pvd.get<ISO9660LowLevel::PVD_MPathTableLocation>();
252-
m_flatEntries.push_back({_("<M Path Table>"), mPathLoc, pathTableSize, FlatEntry::System, {}});
260+
m_flatEntries.push_back(
261+
{_("<M Path Table>"), mPathLoc, pathTableSize, pathTableSectors, FlatEntry::System, {}});
253262
uint32_t mPathOptLoc = pvd.get<ISO9660LowLevel::PVD_MPathTableOptLocation>();
254263
if (mPathOptLoc != 0) {
255-
m_flatEntries.push_back({_("<M Path Table (opt)>"), mPathOptLoc, pathTableSize, FlatEntry::System, {}});
264+
m_flatEntries.push_back(
265+
{_("<M Path Table (opt)>"), mPathOptLoc, pathTableSize, pathTableSectors, FlatEntry::System, {}});
256266
}
257267
auto& rootDir = m_reader->getRootDirEntry();
258268
uint32_t rootLBA = rootDir.get<ISO9660LowLevel::DirEntry_LBA>();
259269
uint32_t rootSize = rootDir.get<ISO9660LowLevel::DirEntry_Size>();
260-
m_flatEntries.push_back({_("<Root Directory>"), rootLBA, rootSize, FlatEntry::System, {}});
270+
uint32_t rootSectors = (rootSize + 2047) / 2048;
271+
m_flatEntries.push_back({_("<Root Directory>"), rootLBA, rootSize, rootSectors, FlatEntry::System, {}});
261272

262273
collectFlatEntries(m_reader->getRootDirEntry(), "");
263274
std::sort(m_flatEntries.begin(), m_flatEntries.end(),
@@ -270,11 +281,10 @@ void PCSX::Widgets::IsoBrowser::drawFilesystemFlat() {
270281
if (entry.lba > nextExpected) {
271282
uint32_t gapSectors = entry.lba - nextExpected;
272283
auto label = fmt::format(f_("<gap {} sectors>"), gapSectors);
273-
withGaps.push_back({label, nextExpected, gapSectors * 2352, FlatEntry::Gap, {}});
284+
withGaps.push_back({label, nextExpected, gapSectors * 2352, gapSectors, FlatEntry::Gap, {}});
274285
}
275286
withGaps.push_back(entry);
276-
uint32_t sectors = (entry.size + 2047) / 2048;
277-
uint32_t end = entry.lba + sectors;
287+
uint32_t end = entry.lba + entry.sectors;
278288
if (end > nextExpected) nextExpected = end;
279289
}
280290
m_flatEntries = std::move(withGaps);
@@ -349,7 +359,16 @@ headers and subheader file boundary markers.)"));
349359
if (ImGui::MenuItem(_("Hex Edit"))) {
350360
auto isoPtr = m_cachedIso.lock();
351361
if (isoPtr) {
352-
openHexEditor(entry.path, IO<File>(new CDRIsoFile(isoPtr, entry.lba, entry.size)));
362+
IEC60908b::SectorMode mode = IEC60908b::SectorMode::GUESS;
363+
switch (entry.type) {
364+
case FlatEntry::Gap:
365+
case FlatEntry::System: mode = IEC60908b::SectorMode::RAW; break;
366+
case FlatEntry::HiddenM1: mode = IEC60908b::SectorMode::M1; break;
367+
case FlatEntry::HiddenM2F1: mode = IEC60908b::SectorMode::M2_FORM1; break;
368+
case FlatEntry::HiddenM2F2: mode = IEC60908b::SectorMode::M2_FORM2; break;
369+
default: break;
370+
}
371+
openHexEditor(entry.path, IO<File>(new CDRIsoFile(isoPtr, entry.lba, entry.size, mode)));
353372
}
354373
}
355374
ImGui::EndPopup();
@@ -563,20 +582,55 @@ significantly by caching the files beforehand.)"));
563582
uint32_t originalSize = m_selectedSize;
564583
auto isoPtr = m_cachedIso.lock();
565584
if (isoPtr) {
566-
IO<File> replacement(new UvFile(srcPath));
567-
if (!replacement->failed()) {
568-
IO<File> isoFile(new CDRIsoFile(isoPtr, lba, originalSize));
585+
m_extractionProgress = 0.0f;
586+
m_extractionCoroutine = [](IsoBrowser* self, std::shared_ptr<CDRIso> iso, uint32_t lba,
587+
uint32_t originalSize, std::string src) -> Coroutine<> {
588+
auto time = std::chrono::steady_clock::now();
589+
IO<File> replacement(new UvFile(src));
590+
if (replacement->failed()) co_return;
591+
IO<File> isoFile(new CDRIsoFile(iso, lba, originalSize));
569592
uint32_t replaceSize = std::min((uint32_t)replacement->size(), originalSize);
593+
if (replacement->size() > originalSize) {
594+
// Replacement too large; truncated to original size.
595+
g_system->printf(
596+
_("ISO replace: replacement file is larger than target (%zu > %u). Truncating.\n"),
597+
replacement->size(), originalSize);
598+
}
570599
uint8_t buffer[2048];
571600
uint32_t remaining = replaceSize;
601+
uint32_t written = 0;
572602
while (remaining > 0) {
573603
uint32_t chunk = std::min(remaining, (uint32_t)sizeof(buffer));
574604
auto read = replacement->read(buffer, chunk);
575605
if (read <= 0) break;
576606
isoFile->write(buffer, read);
577607
remaining -= read;
608+
written += read;
609+
if (std::chrono::steady_clock::now() - time > std::chrono::milliseconds(50)) {
610+
self->m_extractionProgress = (float)written / (float)originalSize;
611+
co_yield self->m_extractionCoroutine.awaiter();
612+
time = std::chrono::steady_clock::now();
613+
}
578614
}
579-
}
615+
// Zero-pad the tail if the replacement was smaller than the original,
616+
// so stale bytes from the original don't leak through.
617+
if (written < originalSize) {
618+
uint8_t zeros[2048] = {0};
619+
uint32_t padRemaining = originalSize - written;
620+
while (padRemaining > 0) {
621+
uint32_t chunk = std::min(padRemaining, (uint32_t)sizeof(zeros));
622+
isoFile->write(zeros, chunk);
623+
padRemaining -= chunk;
624+
written += chunk;
625+
if (std::chrono::steady_clock::now() - time > std::chrono::milliseconds(50)) {
626+
self->m_extractionProgress = (float)written / (float)originalSize;
627+
co_yield self->m_extractionCoroutine.awaiter();
628+
time = std::chrono::steady_clock::now();
629+
}
630+
}
631+
}
632+
self->m_extractionProgress = 1.0f;
633+
}(this, isoPtr, lba, originalSize, srcPath);
580634
}
581635
}
582636
}

src/gui/widgets/isobrowser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class IsoBrowser {
8080
std::string path;
8181
uint32_t lba;
8282
uint32_t size;
83+
uint32_t sectors; // Actual sector span on disc, derived from XA form for Form 2 files.
8384
Type type;
8485
ISO9660LowLevel::DirEntry dirEntry;
8586

0 commit comments

Comments
 (0)