Files
PS5_RetroArch/tests/ps5_directory_test.cpp
T
Mihawk d2a91b395b Port native mGBA and fix core/menu lifecycle rendering
Build pinned mGBA with this PS5 SDK, official metadata and both GB/GBA engines.
Support validated C initializer arrays, native directory/time imports and an
explicit config path. Map large title/core allocations so GBA archives load.
Correct mGBA callback colours and cached frame ownership, crop padded core
textures during presentation, and use the native shell exit after cleanup.

Verified: bash tools/verify.sh (format, unit, build, integration, evidence;
58 tests), then bash tools/run-title.sh --no-build --core-test=mgba --watch 180.
Owner confirms GB/GBC/GBA loading, correct colours/native Quit, and clean menu
and next game. Final capture has zero Vulkan refusals, GPU failure records or
kernel fatal-signal reports; normal frontend exit occurred before window end.

Evidence: evidence/mgba-native/{capture,expectation,iterations,build,abi}.json,
loader/recovery reports and exact linked-driver hashes. Replay with
bash tools/verify.sh evidence. Final build identity:
82024b89e74bbad4ad2ce2e2916d1a49baf26ac325fef20fc71a2c168b862337.

Patch count is deliberately 139: archive diagnostics, menu/cache ownership and
padded-source sampling edits. PS5_Vulkan was not modified. Saves/states,
long-run timing and a fresh FCEUmm/XMB transition matrix remain unverified.
2026-09-19 16:00:01 -04:00

116 lines
3.6 KiB
C++

/* Clock-free SDK getdents fixture: actual adapter, including malformed records. */
#include <cassert>
#include <string>
#include <vector>
#include "../src/ps5_directory.cpp"
namespace
{
std::vector<std::vector<char>> batches;
size_t batch = 0;
int closed = 0;
bool seek_fails = false;
std::vector<char> record(uint32_t inode, unsigned type, const std::string &name)
{
uint16_t length = uint16_t((8 + name.size() + 1 + 3) & ~3u);
std::vector<char> bytes(length);
std::memcpy(bytes.data(), &inode, 4);
std::memcpy(bytes.data() + 4, &length, 2);
bytes[6] = char(type);
bytes[7] = char(name.size());
std::memcpy(bytes.data() + 8, name.c_str(), name.size() + 1);
return bytes;
}
} // namespace
extern "C" int __wrap_open(const char *path, int flags, ...)
{
assert(flags == (O_RDONLY | O_DIRECTORY));
if (std::strcmp(path, "/denied") == 0)
{
errno = EACCES;
return -1;
}
return 0; // fd zero is valid and must be closed too.
}
extern "C" int __wrap_close(int fd)
{
assert(fd == 0);
++closed;
return 0;
}
extern "C" off_t lseek(int fd, off_t offset, int whence)
{
assert(fd == 0 && offset == 0 && whence == SEEK_SET);
if (seek_fails)
{
errno = EINVAL;
return -1;
}
batch = 0;
return 0;
}
extern "C" int getdents(int fd, char *buffer, int size)
{
assert(fd == 0);
// Model the mounted filesystem that rejects small directory reads.
if (size < 64 * 1024)
{
errno = EINVAL;
return -1;
}
if (batch == batches.size())
return 0;
const auto &data = batches[batch++];
assert(data.size() <= size_t(size));
std::memcpy(buffer, data.data(), data.size());
return int(data.size());
}
int main()
{
assert(ps5_opendir("/denied") == nullptr && errno == EACCES);
assert(ps5_opendir(nullptr) == nullptr && errno == ENOENT);
auto first = record(0, DT_REG, "deleted");
auto folder = record(2, DT_DIR, "cores");
first.insert(first.end(), folder.begin(), folder.end());
batches = {first, record(3, DT_REG, "Example ROM.bin")};
DIR *dir = ps5_opendir("/app0");
assert(dir);
auto *entry = ps5_readdir(dir);
assert(entry && entry->d_type == DT_DIR && std::string(entry->d_name) == "cores");
entry = ps5_readdir(dir);
assert(entry && entry->d_type == DT_REG && std::string(entry->d_name) == "Example ROM.bin");
errno = 0;
assert(!ps5_readdir(dir) && errno == 0 && !ps5_readdir(dir));
seek_fails = true;
ps5_rewinddir(dir);
assert(errno == EINVAL && !ps5_readdir(dir));
seek_fails = false;
ps5_rewinddir(dir);
entry = ps5_readdir(dir);
assert(entry && std::string(entry->d_name) == "cores");
ps5_rewinddir(dir); // Reset a partially consumed batch too.
entry = ps5_readdir(dir);
assert(entry && std::string(entry->d_name) == "cores");
assert(ps5_closedir(dir) == 0 && closed == 1);
// Truncated headers, oversized/zero records and missing name terminators fail closed.
auto oversized = record(1, DT_DIR, "x");
oversized[4] = char(255);
auto zero = record(1, DT_DIR, "x");
zero[4] = 0;
auto unterminated = record(1, DT_DIR, "x");
unterminated[9] = 'x';
for (const auto &bad : {std::vector<char>(7), oversized, zero, unterminated})
{
batches = {bad};
batch = 0;
dir = ps5_opendir("/app0");
errno = 0;
assert(!ps5_readdir(dir) && errno == EIO);
assert(!ps5_readdir(dir));
ps5_closedir(dir);
}
assert(closed == 5);
std::puts(
"ps5_directory: SDK record parsing, multiple batches, EOF, denial and corruption PASS");
}