@@ -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 }
0 commit comments