Files
psxrecomp/runtime/include/iso_reader.h
T
Matthew Stanley cf05a0ab1f runtime + recompiler: Tomba boot through BIOS into Whoopee Camp FMV
Recompiler — `recompiler/src/function_analysis.cpp` now stops a
function's end_addr at its mapped `jr $ra` + delay slot instead of
stretching through trailing data. Fixes Tomba's 0x80076DB0, a real
136-byte function that was being emitted as a psx_unknown_dispatch
stub because data after it poisoned the boundary heuristic.
`code_generator.cpp` emits `func_<next>(cpu); return;` as a tail
call when a mid-function seed split leaves the physical fall-through
in a different C function, plus a `debug_server_log_call_entry` at
every function entry. `full_function_emitter.cpp` splits dispatch
into `psx_dispatch_impl(cpu, addr, stop_addr)` with public
`psx_dispatch` and new `psx_dispatch_call(cpu, addr, return_addr)`
so JAL/JALR into RAM stubs can return cleanly without growing the
native stack.

Runtime — `runtime/src/dma.c`/`include/dma.h`/`src/memory.c` now
expose `dma_write_masked()`; the 16-bit and 8-bit mmio paths route
DICR/DPCR/CHCR partial writes through it so a partial DICR write no
longer accidentally clears unrelated channels' pending IRQ flags,
and CHCR only triggers DMA when the written mask actually includes
the start bit. Unified `complete_transfer(ch)` across all channels,
removed `prev_master_flag` edge detection.

MDEC (`runtime/src/mdec.c`, `include/mdec.h`) gains a full debug
surface: `MDECDebugState` snapshot, an always-on 4096-entry
`MDECDebugEvent` ring with 11 event kinds (reset, ctrl_write,
cmd_begin/done, decode_done, dma_in/out start/end, output_drained,
read_underflow), and `mdec_state` / `mdec_trace` / `mdec_trace_clear`
debug-server commands. DMA ch0/1 emit start/end events around MDEC
transfers.

CDROM (`runtime/src/cdrom.c`, `include/cdrom.h`) — read stream
separated from one-shot pending state (`read_cmd`, `read_delay`,
`start_read_stream`, `stop_read_stream`, `deliver_read_sector`);
`cdrom_state`/`cdrom_trace` report the new fields.

Thread/fiber tracing — `traps.c`'s Win32-fiber TCB save/restore now
emits 12 thread-event kinds; new `ThreadTraceEntry` ring (64 K) plus
`thread_trace` / `thread_trace_clear` debug-server commands report
both live CPU regs and the target TCB's full saved register block.

wtrace / mmio rings — `WriteTraceEntry` grew (cpu_pc, sp, v0/v1/a0-a3/
t0/t1); capacity 1 M -> 4 M. `MmioTraceEntry` grew (pc, cpu_pc, sp,
a0-a3, sr, epc, i_stat, i_mask). Both dump commands accept
`count`/`newest=1` for reverse iteration. `restore_trace` and
`dirty_block_log` emit extended register fields (a2/a3/sp).

Misc — `interrupts.c` polls debug-server every 16K checks so it
stays responsive without a separate thread. `psx_runtime.h`'s
`call_by_address` routes through `psx_dispatch_call`. Dirty-RAM
interp JAL/JALR consult `psx_dispatch_game_compiled` when the game
is wired in. iso_reader gains the entry points needed by the game
disc path. New `tools/_tomba_probe.py` for port-4470 health checks.

Generated — regenerated `SCPH1001_dispatch.c` against the new
`psx_dispatch_impl` shape.

ISSUES.md — opened Issue #3 (BIOS PS-logo glyph missing on the
disc-detected screen, cosmetic).

.gitignore — added `recompiler/build-asan*/`, `build-checked/`,
`__pycache__/`.

Validated end-to-end: BIOS boots, disc detected, Whoopee Camp FMV
plays with audio. Frame count advances through ~40K frames without
freezes. `unknown_dispatch_log.total = 0`. Remaining FMV cluster
issues (1/4-size render, FMV-2 audio missing, no skip) tracked
under TombaRecomp/ISSUES.md.

Co-Authored-By: Codex (OpenAI) <noreply@openai.com>
2026-05-11 12:34:49 -07:00

171 lines
4.9 KiB
C++

#pragma once
#include <cstdint>
#include <string>
#include <fstream>
#include <vector>
/**
* 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 {
/**
* 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
};
class ISOReader {
public:
/**
* Constructor
*/
ISOReader();
/**
* Destructor - ensures file is closed
*/
~ISOReader();
/**
* Open an ISO/BIN file for reading
* @param filename Path to .iso, .bin, or .cue file
* @return true if opened successfully, false otherwise
*/
bool Open(const std::string& filename);
/**
* 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);
/**
* 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 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<ISOFileEntry> 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<ISOFileEntry> ListFilesByLBA(uint32_t lba, uint32_t dir_size);
std::ifstream file_;
bool is_open_;
std::string volume_id_;
std::string bin_path_;
RootDirectoryInfo root_dir_;
};
} // namespace PS1