#pragma once #include #include #include #include #include #include #include /** * ISO/BIN/CUE Reader for PS1 CD-ROM images * Session 24-25 - Validation-first approach * * Features: * - Open ISO/BIN file * - Read sectors by LBA * - Parse ISO9660 filesystem * - List files in directory * - Read file contents by path */ namespace PS1 { struct CHDState; /** * Information about a file entry in the ISO filesystem */ struct ISOFileEntry { std::string name; // Filename (without version suffix) uint32_t lba; // Logical Block Address (sector number) uint32_t size; // File size in bytes bool is_directory; // True if this is a directory }; /** * Root directory information from Primary Volume Descriptor */ struct RootDirectoryInfo { uint32_t lba; // LBA of root directory data uint32_t size; // Size of root directory in bytes }; /** * A CD track parsed from the .cue sheet (or the synthesized single data * track for a bare .bin/.iso). Multi-track discs can carry a * Red Book CD-DA audio track after the data track; the CD model's TOC * commands (GetTN/GetTD) must report them. */ struct CDTrack { int number; // 1-based track number bool is_audio; // true = CD-DA audio (Red Book); false = data uint32_t start_lba; // disc-relative start LBA (cue INDEX 01; track 1 = 0). // Single-FILE cues: equals the .bin-relative INDEX time. // Multi-FILE cues (redump "(Track N).bin" dumps): the // owning file's first disc sector plus its INDEX time. // Either way, plus every cue PREGAP/POSTGAP before it // (gaps the dump does not store). uint32_t pregap_lba; // disc-relative start of the track's pregap: its cue // PREGAP if any, else INDEX 00, else INDEX 01 }; /** * One BINARY file backing a contiguous run of disc sectors. Single-file * images have exactly one segment; redump-style dumps have one per track * file, concatenated in cue order to form the flat disc-sector space that * ReadSector()/ReadRawSector() address. */ struct BinSegment { std::string path; // resolved on-disk path std::ifstream file; // opened for the reader's lifetime uint32_t start_lba; // disc sector of this file's first stored sector uint32_t sector_count; // sectors stored in this file bool raw; // 2352-byte raw sectors (BIN) vs 2048 (ISO) }; /** * A contiguous run of disc sectors. A stored span maps onto sectors of one * BinSegment; a virtual span is a cue PREGAP/POSTGAP that the dump does not * store and that reads back as zero (silence), as the CHD path already does. * Without PREGAP/POSTGAP lines each file is exactly one stored span, so the * disc layout is the plain concatenation of the files. */ struct BinSpan { uint32_t disc_start; // first disc-relative sector uint32_t sector_count; int segment; // index into segments_, or -1 for a virtual gap uint32_t file_sector; // first sector within the segment's file }; class ISOReader { public: /** * Constructor */ ISOReader(); /** * Destructor - ensures file is closed */ ~ISOReader(); /** * Open an ISO/BIN file for reading * @param filename Path to .iso, .bin, .img, .car, .cue, or .chd file * @return true if opened successfully, false otherwise */ bool Open(const std::string& filename); /** * Sector geometry of the first stored file behind an image, decided the * same way Open() decides it: a .cue's TRACK mode is authoritative; a bare * file is raw when sector 0 starts with the CD sync pattern. File length * is never the evidence (raw dumps can be a multiple of 2048). * @param path .cue or bare .bin/.iso/.img * @param raw set to true for 2352-byte raw sectors, false for 2048 * @return false when the image cannot be opened or its geometry is not * mountable (conflicting or MODE2/2336 track modes) */ static bool FirstFileIsRaw(const std::string& path, bool* raw); /** * Close the currently open file */ void Close(); /** * Read a single sector from the ISO * @param lba Logical Block Address (sector number) * @param buffer Buffer to read into (must be at least 2048 bytes) * @return true if read successfully, false otherwise */ bool ReadSector(uint32_t lba, uint8_t* buffer); /** * Read a raw 2352-byte sector from a BIN image. * @param lba Logical Block Address (sector number) * @param buffer Buffer to read into (must be at least 2352 bytes) * @return true if the image stores raw sectors and read succeeded */ bool ReadRawSector(uint32_t lba, uint8_t* buffer); /** * Read generated or SBI-replacement subchannel Q for a disc LBA. * SBI records intentionally carry invalid CRC data; valid is false for * those sectors so the CD controller can retain its last valid Q packet. */ bool ReadSubChannelQ(uint32_t lba, uint8_t* buffer, bool* valid) const; bool HasSubChannelReplacements() const { return !subq_replacements_.empty(); } /** * Check if a file is currently open * @return true if file is open */ bool IsOpen() const; /** * Get volume ID from Primary Volume Descriptor * @return Volume ID string (disc name) */ std::string GetVolumeID() const; /** * Get the resolved path to the BIN file (set after Open()) */ std::string GetBinPath() const; /** * Get number of addressable sectors in the mounted image. * Raw BIN images count 2352-byte sectors; cooked ISO images count 2048-byte sectors. */ uint32_t GetSectorCount(); /** * CD-track TOC accessors (multi-track .cue support). * TrackCount() is >= 1 (a bare image synthesizes one data track). * TrackStartLBA(n)/TrackPregapLBA(n)/TrackIsAudio(n) take a 1-based track number. */ int TrackCount() const; uint32_t TrackStartLBA(int track) const; uint32_t TrackPregapLBA(int track) const; bool TrackIsAudio(int track) const; /** * Get root directory information * @return RootDirectoryInfo containing LBA and size */ RootDirectoryInfo GetRootDirectory() const; /** * List all files in a directory * @param path Directory path (empty string = root directory) * @return Vector of ISOFileEntry for all files/subdirectories */ std::vector ListFiles(const std::string& path = ""); /** * Find a file by name in the root directory * @param path Filename to search for (e.g., "SYSTEM.CNF" or "SCUS_942.36") * @param entry Output ISOFileEntry to populate with file info * @return true if found, false if not found */ bool FindFile(const std::string& path, ISOFileEntry& entry); /** * Read entire file contents into buffer * @param path Filename to read * @param buffer Buffer to read into (must be large enough) * @param max_size Maximum bytes to read (buffer size limit) * @return Number of bytes read (0 if error or file not found) */ size_t ReadFile(const std::string& path, uint8_t* buffer, size_t max_size); /** * Get file size without reading data * @param path Filename to query * @return File size in bytes (0 if file not found) */ size_t GetFileSize(const std::string& path); private: /** * Parse the Primary Volume Descriptor (sector 16) * Extracts volume ID and root directory information * @return true if parsed successfully, false otherwise */ bool ParseVolumeDescriptor(); /** * Helper: Read a 32-bit value in both-endian format (733 format) * @param data Pointer to 8-byte both-endian value * @return 32-bit value (reads little-endian half) */ uint32_t Read733(const uint8_t* data) const; /** * Helper: Parse a directory record from raw sector data * @param data Pointer to directory record data * @param entry Output ISOFileEntry to populate * @return true if parsed successfully, false if invalid record */ bool ParseDirectoryRecord(const uint8_t* data, ISOFileEntry& entry) const; /** * Helper: List files in a directory given its LBA and size * Used for subdirectory navigation */ std::vector ListFilesByLBA(uint32_t lba, uint32_t dir_size); /** * Helper: find the span containing a disc-relative LBA. * @return span pointer, or nullptr when lba is past the disc end */ const BinSpan* SpanForLBA(uint32_t lba) const; bool LoadSBICompanion(const std::string& image_path); bool is_open_; std::string volume_id_; std::string bin_path_; // first (data) segment; kept for callers RootDirectoryInfo root_dir_; std::vector tracks_; // from the .cue TOC; >=1 entry after Open() std::vector segments_; // cue FILE entries in disc order; >=1 after Open() std::vector spans_; // disc layout over segments_ + cue gaps uint32_t disc_sector_count_ = 0; // BIN/CUE: end of the last span std::unique_ptr chd_; // present only for a directly mounted CHD std::unordered_map> subq_replacements_; }; } // namespace PS1