Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
81f50cf
Add host file handles to IOP IOMAN
LTDCrissOK Nov 5, 2026
7762d9a
Implement read-only IOP IOMAN file operations
LTDCrissOK Nov 5, 2026
34736c5
Pass IOP host adapter into IOMAN
LTDCrissOK Nov 5, 2026
aaa76b1
Test IOP IOMAN open read seek and close
LTDCrissOK Nov 5, 2026
303d3c9
Add explicit standard headers for IOMAN tests
LTDCrissOK Nov 5, 2026
7d0db93
Run IOP import tests in CI
LTDCrissOK Nov 5, 2026
eff85c0
Document read-only IOMAN file operation scope
LTDCrissOK Nov 5, 2026
d208e1c
Allow manual GitHub Actions runs
LTDCrissOK Nov 5, 2026
71ddb74
Allow manual GitHub Actions runs
LTDCrissOK Nov 5, 2026
483fc89
Merge pull request #1 from LTDCrissOK/fix/ioman-read-only-file-io
LTDCrissOK Nov 5, 2026
6fcb1aa
Add read-only ISO9660 image reader interface
LTDCrissOK Nov 6, 2026
92b9e6b
Implement ISO9660 volume and directory reader
LTDCrissOK Nov 6, 2026
0bb6941
Register ISO9660 reader in runtime build
LTDCrissOK Nov 6, 2026
d972e56
Accept ISO9660 root directory identifier
LTDCrissOK Nov 6, 2026
65c2886
Validate ISO9660 extended attribute extent overflow
LTDCrissOK Nov 6, 2026
c07ab03
Add ISO9660 reader fixture tests
LTDCrissOK Nov 6, 2026
db01a85
Compile ISO9660 reader tests
LTDCrissOK Nov 6, 2026
20dd584
Register ISO9660 reader tests
LTDCrissOK Nov 6, 2026
7154072
Validate ISO9660 block size and close failed mounts
LTDCrissOK Nov 6, 2026
81bfbb3
Add ISO image mount to VFS mount configuration
LTDCrissOK Nov 6, 2026
3571fd3
Expose mounted ISO image through runtime I/O paths
LTDCrissOK Nov 6, 2026
26100a2
Route CD-ROM VFS reads through mounted ISO9660 images
LTDCrissOK Nov 6, 2026
737b48c
Support ISO-backed IOP host file handles
LTDCrissOK Nov 6, 2026
6db4085
Read ISO-backed paths through the IOP host file adapter
LTDCrissOK Nov 6, 2026
99d577a
Test VFS access to ISO-backed CD-ROM files
LTDCrissOK Nov 6, 2026
8322a5c
Report sizes for ISO-backed IOP file handles
LTDCrissOK Nov 6, 2026
8a7f28c
Protect ISO image reads from concurrent file access
LTDCrissOK Nov 6, 2026
21158bc
Serialize ISO image seek and read operations
LTDCrissOK Nov 6, 2026
cf0bda5
Add runtime API to validate and mount a PS2 CD ISO
LTDCrissOK Nov 6, 2026
4df344c
Validate SYSTEM.CNF and PS2 ELF before mounting ISO
LTDCrissOK Nov 6, 2026
a38d67c
Include character conversion declarations for ISO validation
LTDCrissOK Nov 6, 2026
1c6589f
Test runtime ISO validation and mounted VFS boot-file reads
LTDCrissOK Nov 6, 2026
cba5259
Correct ISO fixture ELF signature and SYSTEM.CNF line endings
LTDCrissOK Nov 6, 2026
ca07f96
Expose extraction of the validated ISO boot ELF
LTDCrissOK Nov 6, 2026
e3bba43
Extract only the validated PS2 boot ELF from the mounted ISO
LTDCrissOK Nov 6, 2026
a1e5984
Boot Android build from selected Manhunt 2 ISO
LTDCrissOK Nov 6, 2026
cd520a5
Test extraction of the boot ELF from a mounted ISO
LTDCrissOK Nov 6, 2026
0ed508f
Test IOP host adapter reads CD-ROM files from ISO
LTDCrissOK Nov 6, 2026
3c2ad5f
Fix VCALLMSR access to VU0 CMSAR0
LTDCrissOK Nov 6, 2026
9c617d9
Test VCALLMSR VI and CMSAR0 register sources
LTDCrissOK Nov 6, 2026
bc33060
Fix GS privileged display state during sceGsResetGraph
LTDCrissOK Nov 6, 2026
57fb4a9
Add Manhunt frame pixel diagnostics
LTDCrissOK Nov 7, 2026
a3fa3fa
Fix frame stats for pinned GS snapshot
LTDCrissOK Nov 7, 2026
7d26857
Fix CDVD search against mounted ISO9660 image
LTDCrissOK Nov 7, 2026
3034a18
Fix CdFileInfo nested type in CDVD search
LTDCrissOK Nov 7, 2026
58b522f
Fix nested CdFileInfo type in IOP host adapter
LTDCrissOK Nov 7, 2026
d35b287
Fix nested CdFileInfo type in IOP host adapter
LTDCrissOK Nov 7, 2026
57f6efe
Add persistent Android runtime diagnostics for Manhunt 2
LTDCrissOK Nov 7, 2026
c578eb6
Fix diagnostic log line endings and runtime start record
LTDCrissOK Nov 7, 2026
9d326cd
Record Android ISO boot and runtime stages to a persistent diagnostic…
LTDCrissOK Nov 7, 2026
fe6c24d
Preserve startup diagnostics and record ELF load failures
LTDCrissOK Nov 7, 2026
20221d9
Correct startup diagnostic line endings
LTDCrissOK Nov 7, 2026
d5a3a26
Fix synchronous sceCdStatus idle state for boot loops
LTDCrissOK Nov 7, 2026
edd5de5
Add CDVD ROM RPC service for boot handshake
LTDCrissOK Nov 7, 2026
e18983c
Register CDVD ROM service factory
LTDCrissOK Nov 7, 2026
f744eb3
Compile CDVD ROM RPC service
LTDCrissOK Nov 7, 2026
43ac56a
Register CDVD ROM RPC endpoints
LTDCrissOK Nov 7, 2026
b3dc28c
Keep existing IOP test source paths
LTDCrissOK Nov 7, 2026
3a536a6
Test CDVD ROM RPC boot endpoints
LTDCrissOK Nov 7, 2026
e0804d0
Log guest instruction and dispatch trace for Manhunt 2 stall
LTDCrissOK Nov 7, 2026
f85c421
Log guest VSync wait requests for Android diagnostics
LTDCrissOK Nov 7, 2026
47e7869
Trace VSync wakeups and VBlank progression
LTDCrissOK Nov 7, 2026
f00505b
Fix VBlank switch case scope for Android build
LTDCrissOK Nov 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: build

on: [push, pull_request]
on: [push, pull_request, workflow_dispatch]

jobs:
linux-gcc-clang:
Expand Down Expand Up @@ -30,13 +30,15 @@ jobs:
export CC=clang
export CXX=clang++
fi
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-msse4.1 -DCMAKE_CXX_FLAGS=-msse4.1
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-msse4.1 -DCMAKE_CXX_FLAGS=-msse4.1 -DPS2X_IOP_BUILD_TESTS=ON

- name: Build
run: cmake --build build --config Release

- name: Tests
run: ./build/ps2xTest/ps2x_tests
run: |
./build/ps2xTest/ps2x_tests
./build/ps2xIOP/ps2_iop_import_tests

windows-msvc:
strategy:
Expand All @@ -63,13 +65,14 @@ jobs:
- name: Build
shell: cmd
run: |
cmake -B build
cmake -B build -DPS2X_IOP_BUILD_TESTS=ON
cmake --build build --config Release

- name: Tests
shell: cmd
run: |
.\build\ps2xTest\Release\ps2x_tests.exe
.\build\ps2xIOP\Release\ps2_iop_import_tests.exe
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion ps2xIOP/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ add_library(ps2_iop STATIC
src/emulator/imports/iop_sysmem.cpp
src/emulator/imports/iop_timrman.cpp
src/emulator/imports/iop_vblank.cpp
src/modules/cdvdman.cpp
src/modules/dbcman.cpp
src/modules/libsd.cpp
src/modules/mcserv.cpp
Expand Down Expand Up @@ -61,7 +62,6 @@ if(PS2X_IOP_BUILD_TESTS)
target_link_libraries(ps2_iop_import_version_tests PRIVATE ps2_iop)
target_include_directories(ps2_iop_import_version_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_test(NAME ps2_iop_import_version_tests COMMAND ps2_iop_import_version_tests)

endif()

install(TARGETS ps2_iop
Expand Down
3 changes: 3 additions & 0 deletions ps2xIOP/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ ctest --test-dir out/build/iop-tests --output-on-failure

The suites cover IRX execution, RPC, imports, version resolution and generic
HLE compatibility. `ps2x_tests` also covers runtime SIF RPC/DMA integration.


The IOMAN compatibility layer currently supports read-only `open`, `close`, `read`, and `lseek` operations through the host VFS adapter. Write and directory operations are not implemented by this layer yet.
16 changes: 16 additions & 0 deletions ps2xIOP/include/ps2x/iop/iop_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,23 @@ namespace ps2x::iop

virtual void audioCommand(uint32_t sid, uint32_t function, GuestBuffer send, GuestBuffer receive) = 0;

struct CdFileInfo
{
uint32_t lsn = 0u;
uint32_t size = 0u;
bool directory = false;
std::string name;
};

virtual std::string hostPath(HostPathKind kind) const = 0;
// Resolve a CD-ROM path against the mounted disc image and return its real
// ISO9660 extent. Hosts without a mounted image may keep the default.
virtual bool findCdFile(std::string_view path, CdFileInfo &result) const
{
(void)path;
(void)result;
return false;
}
virtual std::string translateGuestPath(std::string_view path) const = 0;
virtual uint64_t openHostFile(std::string_view path) = 0;
virtual bool hostFileSize(uint64_t handle, uint64_t &size) const = 0;
Expand Down
23 changes: 22 additions & 1 deletion ps2xIOP/src/emulator/imports/iop_cdvd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -600,11 +600,32 @@ namespace ps2x::iop::detail
return false;

const std::string guestPath = memory.readString(nameAddress, 1024u);

// When a real ISO is mounted, return the file's actual LSN and size
// from its ISO9660 directory record. The old virtual-directory index
// describes a host folder and its synthetic LSNs do not match sectors
// in the mounted disc image.
ps2x::iop::IopHost::CdFileInfo discEntry;
const bool hasMountedDisc = !host.hostPath(HostPathKind::CdImage).empty();
if (hasMountedDisc)
{
if (!host.findCdFile(guestPath, discEntry))
return false;

std::array<uint8_t, 32u> result{};
writeLe32(result.data(), discEntry.lsn);
writeLe32(result.data() + 4u, discEntry.size);
const std::string leaf = normalizedIsoComponent(discEntry.name);
std::memcpy(result.data() + 8u, leaf.data(), std::min<size_t>(16u, leaf.size()));
result[24u] = discEntry.directory ? 2u : 0u;
return memory.writeRam(resultAddress, result.data(), result.size());
}

// Keep the synthetic host-directory disc for non-ISO runner setups.
IsoNode *node = findVirtualIsoNode(guestPath);
if (!node)
return false;

// sceCdlFILE: lsn, size, name[16], date/flags[8].
std::array<uint8_t, 32u> result{};
writeLe32(result.data(), node->lsn);
writeLe32(result.data() + 4u, node->size);
Expand Down
160 changes: 158 additions & 2 deletions ps2xIOP/src/emulator/imports/iop_ioman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@

namespace ps2x::iop::detail
{
IopIoman::IopIoman(IopMemory &memory) noexcept
: m_memory(memory)
IopIoman::IopIoman(IopHost &host, IopMemory &memory) noexcept
: m_host(host), m_memory(memory)
{
}

void IopIoman::reset()
{
for (const auto &[fd, file] : m_openFiles)
{
(void)fd;
m_host.closeHostFile(file.hostHandle);
}
m_openFiles.clear();
m_devices.clear();
}

Expand All @@ -29,6 +35,156 @@ namespace ps2x::iop::detail

switch (ordinal)
{
case 4: // open
{
constexpr uint32_t kAccessModeMask = 0x0003u;
constexpr uint32_t kReadOnly = 0x0001u;
constexpr uint32_t kCreate = 0x0200u;
constexpr uint32_t kTruncate = 0x0400u;
constexpr uint32_t kExclusive = 0x0800u;

const std::string guestPath = m_memory.readString(a0, 1024u);
const uint32_t mode = cpu.gpr[5];
if (guestPath.empty() ||
(mode & kAccessModeMask) != kReadOnly ||
(mode & (kCreate | kTruncate | kExclusive)) != 0u)
{
setV0(0xFFFFFFFFu);
return true;
}

const std::string hostPath = m_host.translateGuestPath(guestPath);
if (hostPath.empty())
{
setV0(0xFFFFFFFFu);
return true;
}

const uint64_t handle = m_host.openHostFile(hostPath);
if (handle == 0u)
{
setV0(0xFFFFFFFFu);
return true;
}

uint64_t size = 0u;
if (!m_host.hostFileSize(handle, size))
{
m_host.closeHostFile(handle);
setV0(0xFFFFFFFFu);
return true;
}

int32_t fd = -1;
for (int32_t candidate = 3; candidate < 256; ++candidate)
{
if (m_openFiles.find(candidate) == m_openFiles.end())
{
fd = candidate;
break;
}
}
if (fd < 0)
{
m_host.closeHostFile(handle);
setV0(0xFFFFFFFFu);
return true;
}

m_openFiles.emplace(fd, OpenFile{handle, 0u, size});
setV0(static_cast<uint32_t>(fd));
return true;
}
case 5: // close
{
const int32_t fd = static_cast<int32_t>(a0);
const auto file = m_openFiles.find(fd);
if (file == m_openFiles.end())
{
setV0(0xFFFFFFFFu);
return true;
}

m_host.closeHostFile(file->second.hostHandle);
m_openFiles.erase(file);
setV0(0u);
return true;
}
case 6: // read
{
const int32_t fd = static_cast<int32_t>(a0);
const auto file = m_openFiles.find(fd);
const uint32_t destination = cpu.gpr[5];
const uint32_t requested = cpu.gpr[6];
if (file == m_openFiles.end() || requested > IopMemory::RamSize)
{
setV0(0xFFFFFFFFu);
return true;
}
if (requested == 0u)
{
setV0(0u);
return true;
}

std::vector<uint8_t> buffer(requested);
size_t bytesRead = 0u;
if (!m_host.readHostFile(file->second.hostHandle,
file->second.offset,
buffer.data(),
buffer.size(),
bytesRead) ||
bytesRead > buffer.size() ||
!m_memory.writeRam(destination, buffer.data(), bytesRead))
{
setV0(0xFFFFFFFFu);
return true;
}

file->second.offset += bytesRead;
setV0(static_cast<uint32_t>(bytesRead));
return true;
}
case 8: // lseek
{
const int32_t fd = static_cast<int32_t>(a0);
const auto file = m_openFiles.find(fd);
if (file == m_openFiles.end())
{
setV0(0xFFFFFFFFu);
return true;
}

const int32_t offset = static_cast<int32_t>(cpu.gpr[5]);
const uint32_t whence = cpu.gpr[6];
int64_t base = 0;
switch (whence)
{
case 0u: // SEEK_SET
base = 0;
break;
case 1u: // SEEK_CUR
base = static_cast<int64_t>(file->second.offset);
break;
case 2u: // SEEK_END
base = static_cast<int64_t>(file->second.size);
break;
default:
setV0(0xFFFFFFFFu);
return true;
}

const int64_t newOffset = base + static_cast<int64_t>(offset);
if (newOffset < 0 || newOffset > 0x7FFFFFFFLL)
{
setV0(0xFFFFFFFFu);
return true;
}

file->second.offset = static_cast<uint64_t>(newOffset);
setV0(static_cast<uint32_t>(newOffset));
return true;
}
case 20: // AddDrv
{
if (a0 == 0u || m_devices.size() >= kMaxDevices)
Expand Down
14 changes: 13 additions & 1 deletion ps2xIOP/src/emulator/imports/iop_ioman.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#pragma once

#include "ps2x/iop/iop_host.h"

#include <cstdint>
#include <map>
#include <string>
#include <vector>

Expand All @@ -13,7 +16,7 @@ namespace ps2x::iop::detail
class IopIoman
{
public:
explicit IopIoman(IopMemory &memory) noexcept;
IopIoman(IopHost &host, IopMemory &memory) noexcept;

void reset();
[[nodiscard]] bool dispatchImport(uint16_t ordinal, IopCpuState &cpu, IopGuestExecutor &executor);
Expand All @@ -26,7 +29,16 @@ namespace ps2x::iop::detail
std::string name;
};

struct OpenFile
{
uint64_t hostHandle = 0u;
uint64_t offset = 0u;
uint64_t size = 0u;
};

IopHost &m_host;
IopMemory &m_memory;
std::vector<Device> m_devices;
std::map<int32_t, OpenFile> m_openFiles;
};
}
2 changes: 1 addition & 1 deletion ps2xIOP/src/emulator/iop_emulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ namespace ps2x::iop::detail
heaplib(memory),
intrman(memory),
timrman(),
ioman(memory),
ioman(host, memory),
cpuCore(memory),
imports(memory),
loadcore(memory, imports)
Expand Down
Loading