@@ -149,88 +149,127 @@ void PCSX::Widgets::IsoBrowser::collectFlatEntries(const ISO9660LowLevel::DirEnt
149149 }
150150}
151151
152- void PCSX::Widgets::IsoBrowser::scanGapSectors (std::vector<FlatEntry>& out, uint32_t startLBA, uint32_t sectorCount,
153- std::shared_ptr<CDRIso> iso) {
154- uint32_t lba = startLBA;
155- uint32_t end = startLBA + sectorCount;
156-
157- while (lba < end) {
158- uint8_t sector[2352 ];
159- if (iso->readSectors (lba, sector, 1 ) != 1 ) {
160- // Read failed, treat rest as gap
161- uint32_t remaining = end - lba;
162- auto label = fmt::format (f_ (" <gap {} sectors>" ), remaining);
163- out.push_back ({label, lba, remaining * 2352 , remaining, FlatEntry::Gap, {}});
164- break ;
152+ PCSX::Coroutine<> PCSX::Widgets::IsoBrowser::scanAllGaps (std::shared_ptr<CDRIso> iso) {
153+ auto time = std::chrono::steady_clock::now ();
154+ std::vector<FlatEntry> scanned;
155+
156+ // Count total gap sectors to scan for progress reporting.
157+ uint32_t totalGapSectors = 0 ;
158+ for (auto & entry : m_flatEntries) {
159+ if (entry.isGap ()) totalGapSectors += entry.sectors ;
160+ }
161+ uint32_t scannedSectors = 0 ;
162+
163+ for (auto & entry : m_flatEntries) {
164+ if (!entry.isGap ()) {
165+ scanned.push_back (entry);
166+ continue ;
165167 }
166168
167- uint8_t mode = sector[15 ];
169+ uint32_t lba = entry.lba ;
170+ uint32_t end = entry.lba + entry.sectors ;
171+
172+ while (lba < end) {
173+ uint8_t sector[2352 ];
174+ if (iso->readSectors (lba, sector, 1 ) != 1 ) {
175+ uint32_t remaining = end - lba;
176+ auto label = fmt::format (f_ (" <gap {} sectors>" ), remaining);
177+ scanned.push_back ({label, lba, remaining * 2352 , remaining, FlatEntry::Gap, {}});
178+ scannedSectors += remaining;
179+ break ;
180+ }
168181
169- if (mode == 0 ) {
170- // Mode 0: true gap. Accumulate consecutive mode 0 sectors.
171- uint32_t gapStart = lba;
172- while (lba < end) {
173- if (lba != gapStart) {
174- if (iso->readSectors (lba, sector, 1 ) != 1 ) break ;
175- if (sector[15 ] != 0 ) break ;
182+ uint8_t mode = sector[15 ];
183+
184+ if (mode == 0 ) {
185+ uint32_t gapStart = lba;
186+ while (lba < end) {
187+ if (lba != gapStart) {
188+ if (iso->readSectors (lba, sector, 1 ) != 1 ) break ;
189+ if (sector[15 ] != 0 ) break ;
190+ }
191+ lba++;
192+ scannedSectors++;
193+ if (std::chrono::steady_clock::now () - time > std::chrono::milliseconds (50 )) {
194+ m_gapScanProgress =
195+ totalGapSectors > 0 ? (float )scannedSectors / (float )totalGapSectors : 1 .0f ;
196+ co_yield m_gapScanCoroutine.awaiter ();
197+ time = std::chrono::steady_clock::now ();
198+ }
176199 }
177- lba++;
200+ uint32_t count = lba - gapStart;
201+ auto label = fmt::format (f_ (" <gap {} sectors>" ), count);
202+ scanned.push_back ({label, gapStart, count * 2352 , count, FlatEntry::Gap, {}});
203+ continue ;
178204 }
179- uint32_t count = lba - gapStart;
180- auto label = fmt::format (f_ (" <gap {} sectors>" ), count);
181- out.push_back ({label, gapStart, count * 2352 , count, FlatEntry::Gap, {}});
182- continue ;
183- }
184205
185- if (mode == 1 ) {
186- // Mode 1 hidden data. Accumulate until mode changes or gap ends.
187- uint32_t fileStart = lba;
188- lba++;
189- while (lba < end) {
190- if (iso->readSectors (lba, sector, 1 ) != 1 ) break ;
191- if (sector[15 ] != 1 ) break ;
206+ if (mode == 1 ) {
207+ uint32_t fileStart = lba;
192208 lba++;
209+ scannedSectors++;
210+ while (lba < end) {
211+ if (iso->readSectors (lba, sector, 1 ) != 1 ) break ;
212+ if (sector[15 ] != 1 ) break ;
213+ lba++;
214+ scannedSectors++;
215+ if (std::chrono::steady_clock::now () - time > std::chrono::milliseconds (50 )) {
216+ m_gapScanProgress =
217+ totalGapSectors > 0 ? (float )scannedSectors / (float )totalGapSectors : 1 .0f ;
218+ co_yield m_gapScanCoroutine.awaiter ();
219+ time = std::chrono::steady_clock::now ();
220+ }
221+ }
222+ uint32_t count = lba - fileStart;
223+ auto label = fmt::format (f_ (" <hidden M1 {} sectors>" ), count);
224+ scanned.push_back ({label, fileStart, count * 2048 , count, FlatEntry::HiddenM1, {}});
225+ continue ;
193226 }
194- uint32_t count = lba - fileStart;
195- auto label = fmt::format (f_ (" <hidden M1 {} sectors>" ), count);
196- out.push_back ({label, fileStart, count * 2048 , count, FlatEntry::HiddenM1, {}});
197- continue ;
198- }
199227
200- if (mode == 2 ) {
201- // Mode 2: parse subheader for form and file boundaries
202- uint8_t * sub = sector + 16 ;
203- uint8_t fileNum = sub[0 ];
204- uint8_t channelNum = sub[1 ];
205- uint8_t submode = sub[2 ];
206- bool isForm2 = (submode & 0x20 ) != 0 ;
207- auto type = isForm2 ? FlatEntry::HiddenM2F2 : FlatEntry::HiddenM2F1;
208-
209- uint32_t fileStart = lba;
210- bool hitEof = (submode & 0x80 ) != 0 ;
211- lba++;
228+ if (mode == 2 ) {
229+ uint8_t * sub = sector + 16 ;
230+ uint8_t fileNum = sub[0 ];
231+ uint8_t channelNum = sub[1 ];
232+ uint8_t submode = sub[2 ];
233+ bool isForm2 = (submode & 0x20 ) != 0 ;
234+ auto type = isForm2 ? FlatEntry::HiddenM2F2 : FlatEntry::HiddenM2F1;
212235
213- while (lba < end && !hitEof) {
214- if (iso->readSectors (lba, sector, 1 ) != 1 ) break ;
215- if (sector[15 ] != 2 ) break ;
216- sub = sector + 16 ;
217- // Different file/channel = new subfile
218- if (sub[0 ] != fileNum || sub[1 ] != channelNum) break ;
219- hitEof = (sub[2 ] & 0x80 ) != 0 ;
236+ uint32_t fileStart = lba;
237+ bool hitEof = (submode & 0x80 ) != 0 ;
220238 lba++;
239+ scannedSectors++;
240+
241+ while (lba < end && !hitEof) {
242+ if (iso->readSectors (lba, sector, 1 ) != 1 ) break ;
243+ if (sector[15 ] != 2 ) break ;
244+ sub = sector + 16 ;
245+ if (sub[0 ] != fileNum || sub[1 ] != channelNum) break ;
246+ hitEof = (sub[2 ] & 0x80 ) != 0 ;
247+ lba++;
248+ scannedSectors++;
249+ if (std::chrono::steady_clock::now () - time > std::chrono::milliseconds (50 )) {
250+ m_gapScanProgress =
251+ totalGapSectors > 0 ? (float )scannedSectors / (float )totalGapSectors : 1 .0f ;
252+ co_yield m_gapScanCoroutine.awaiter ();
253+ time = std::chrono::steady_clock::now ();
254+ }
255+ }
256+
257+ uint32_t count = lba - fileStart;
258+ uint32_t dataSize = isForm2 ? count * 2324 : count * 2048 ;
259+ auto label = fmt::format (f_ (" <hidden {} f={} ch={} {} sectors>" ),
260+ isForm2 ? " M2F2" : " M2F1" , fileNum, channelNum, count);
261+ scanned.push_back ({label, fileStart, dataSize, count, type, {}});
262+ continue ;
221263 }
222264
223- uint32_t count = lba - fileStart;
224- uint32_t dataSize = isForm2 ? count * 2324 : count * 2048 ;
225- auto label = fmt::format (f_ (" <hidden {} f={} ch={} {} sectors>" ),
226- isForm2 ? " M2F2" : " M2F1" , fileNum, channelNum, count);
227- out.push_back ({label, fileStart, dataSize, count, type, {}});
228- continue ;
265+ lba++;
266+ scannedSectors++;
229267 }
230-
231- // Unknown mode, skip sector
232- lba++;
233268 }
269+
270+ m_flatEntries = std::move (scanned);
271+ m_gapsScanned = true ;
272+ m_gapScanProgress = 1 .0f ;
234273}
235274
236275void PCSX::Widgets::IsoBrowser::drawFilesystemFlat () {
@@ -287,32 +326,35 @@ void PCSX::Widgets::IsoBrowser::drawFilesystemFlat() {
287326 uint32_t end = entry.lba + entry.sectors ;
288327 if (end > nextExpected) nextExpected = end;
289328 }
329+ // Trailing gap: append any unreferenced sectors at the end of the image.
330+ uint32_t discEnd = pvd.get <ISO9660LowLevel::PVD_VolumeSpaceSize>();
331+ if (discEnd > nextExpected) {
332+ uint32_t gapSectors = discEnd - nextExpected;
333+ auto label = fmt::format (f_ (" <gap {} sectors>" ), gapSectors);
334+ withGaps.push_back ({label, nextExpected, gapSectors * 2352 , gapSectors, FlatEntry::Gap, {}});
335+ }
290336 m_flatEntries = std::move (withGaps);
291337 m_flatEntriesDirty = false ;
292338 m_gapsScanned = false ;
293339 }
294340
295341 if (!m_gapsScanned) {
296- if (ImGui::Button (_ (" Scan gaps for hidden files" ))) {
297- auto iso = m_cachedIso.lock ();
298- if (iso) {
299- std::vector<FlatEntry> scanned;
300- for (auto & entry : m_flatEntries) {
301- if (entry.isGap ()) {
302- uint32_t sectorCount = entry.size / 2352 ;
303- scanGapSectors (scanned, entry.lba , sectorCount, iso);
304- } else {
305- scanned.push_back (entry);
306- }
342+ if (m_gapScanCoroutine.done ()) {
343+ if (ImGui::Button (_ (" Scan gaps for hidden files" ))) {
344+ auto iso = m_cachedIso.lock ();
345+ if (iso) {
346+ m_gapScanProgress = 0 .0f ;
347+ m_gapScanCoroutine = scanAllGaps (iso);
307348 }
308- m_flatEntries = std::move (scanned);
309- m_gapsScanned = true ;
310349 }
311- }
312- ImGuiHelpers::ShowHelpMarker (_ (R"( Reads sector headers in gap regions to detect
350+ ImGuiHelpers::ShowHelpMarker (_ (R"( Reads sector headers in gap regions to detect
313351hidden files that were removed from the ISO9660
314352directory but still have intact Mode 1/2 sector
315353headers and subheader file boundary markers.)" ));
354+ } else {
355+ ImGui::ProgressBar (m_gapScanProgress);
356+ m_gapScanCoroutine.resume ();
357+ }
316358 }
317359
318360 if (ImGui::BeginTable (" FilesystemFlat" , 4 ,
@@ -329,7 +371,7 @@ headers and subheader file boundary markers.)"));
329371 ImGui::TableNextRow ();
330372 ImGui::TableSetColumnIndex (0 );
331373 bool selected = m_hasSelection && m_selectedLBA == entry.lba &&
332- m_selectedIsGap == entry.isGap ();
374+ m_selectedIsGap == ( entry.isGap () || entry. isHidden () );
333375 auto id = fmt::format (" {}##{}" , entry.path , i);
334376 if (ImGui::Selectable (id.c_str (), selected,
335377 ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) {
@@ -520,6 +562,7 @@ significantly by caching the files beforehand.)"));
520562 m_hasSelection = false ;
521563 m_selectedPath.clear ();
522564 m_flatEntriesDirty = true ;
565+ m_gapScanCoroutine = {};
523566 if (currentIso && !currentIso->failed ()) {
524567 m_reader = std::make_unique<ISO9660Reader>(currentIso);
525568 if (m_reader->failed ()) m_reader.reset ();
0 commit comments