Nugget
Loading...
Searching...
No Matches
memory-card-filesystem.hh
Go to the documentation of this file.
1/*
2
3MIT License
4
5Copyright (c) 2026 PCSX-Redux authors
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26
27#pragma once
28
29#include <EASTL/functional.h>
30#include <stdint.h>
31
32#include "psyqo/memory-card.hh"
33
34namespace psyqo {
35
36class GPU;
37
73 public:
82 struct Icon {
83 uint8_t frameCount; // 1..3
84 uint16_t clut[16]; // 16-colour palette, BGR555
85 uint8_t pixels[3][128]; // up to 3 frames of 16x16 4bpp
86 };
87
91 struct FileEntry {
92 char name[21]; // null-terminated Sony filename
93 uint16_t sizeInBlocks; // 1..15
94 uint8_t firstBlock; // 1..15
95 };
96
108 struct FileInfo {
109 uint8_t title[65]; // 64-byte Shift-JIS title field + NUL
110 Icon icon; // palette + up to 3 animation frames
111 };
112
113 explicit MemoryCardFileSystem(MemoryCard &card) : m_card(card) {}
114
118 [[nodiscard]] bool isIdle() const { return !m_busy; }
119
122
123 // -- Asynchronous operations (the callback basis) -----------------------
124 // Each starts a transaction and returns immediately; `callback` fires from
125 // the main loop, during GPU pumping, once the transaction completes. Only
126 // one may be in flight at a time (asserts idle).
127
133 void getCardState(Port port, eastl::function<void(Error)> &&callback);
134
142 void format(Port port, eastl::function<void(Error)> &&callback);
143
147 void getFreeBlockCount(Port port, uint32_t *outFreeBlocks, eastl::function<void(Error)> &&callback);
148
156 void listFiles(Port port, FileEntry *out, uint32_t maxEntries, uint32_t *outCount,
157 eastl::function<void(Error)> &&callback);
158
162 void fileExists(Port port, const char *name, bool *outExists, eastl::function<void(Error)> &&callback);
163
176 void readFile(Port port, const char *name, void *buffer, uint32_t maxLen, uint32_t *outLen,
177 eastl::function<void(Error)> &&callback);
178
193 void readFileInfo(Port port, const char *name, FileInfo *out, eastl::function<void(Error)> &&callback);
194
213 void writeFile(Port port, const char *name, const char *title, const Icon &icon, const void *data,
214 uint32_t dataLen, eastl::function<void(Error)> &&callback);
215
219 void deleteFile(Port port, const char *name, eastl::function<void(Error)> &&callback);
220
221 // -- Blocking variants --------------------------------------------------
222 // These run the same transaction but pump the GPU until it finishes and
223 // return the error directly. They still take a few hundred milliseconds for
224 // a non-trivial payload, so they are best used at a deliberate save point.
225
227 Error formatBlocking(GPU &gpu, Port port);
228 Error getFreeBlockCountBlocking(GPU &gpu, Port port, uint32_t *outFreeBlocks);
229 Error listFilesBlocking(GPU &gpu, Port port, FileEntry *out, uint32_t maxEntries, uint32_t *outCount);
230 Error fileExistsBlocking(GPU &gpu, Port port, const char *name, bool *outExists);
231 Error readFileBlocking(GPU &gpu, Port port, const char *name, void *buffer, uint32_t maxLen, uint32_t *outLen);
232 Error readFileInfoBlocking(GPU &gpu, Port port, const char *name, FileInfo *out);
233 Error writeFileBlocking(GPU &gpu, Port port, const char *name, const char *title, const Icon &icon,
234 const void *data, uint32_t dataLen);
235 Error deleteFileBlocking(GPU &gpu, Port port, const char *name);
236
237 private:
238 // A 4-byte aligned 128-byte frame buffer.
239 struct Frame {
240 alignas(4) uint8_t bytes[128];
241 };
242
243 // Directory allocation states (low byte of the 32-bit state word). The high
244 // nibble separates in-use (0x5x) from available (0xax); the low nibble is
245 // the block's position in its chain, preserved across deletion so a deleted
246 // chain stays recoverable.
247 static constexpr uint8_t c_stateFirst = 0x51; // in use: first or only block
248 static constexpr uint8_t c_stateMiddle = 0x52; // in use: middle block
249 static constexpr uint8_t c_stateLast = 0x53; // in use: last block
250 static constexpr uint8_t c_stateFree = 0xa0; // available: never allocated
251 static constexpr uint8_t c_stateDeletedFirst = 0xa1; // available: a deleted first block
252 static constexpr uint8_t c_stateDeletedMiddle = 0xa2; // available: a deleted middle block
253 static constexpr uint8_t c_stateDeletedLast = 0xa3; // available: a deleted last block
254
255 // Directory entry field offsets.
256 static constexpr uint32_t c_offAlloc = 0x00;
257 static constexpr uint32_t c_offSize = 0x04;
258 static constexpr uint32_t c_offNext = 0x08;
259 static constexpr uint32_t c_offName = 0x0a;
260 static constexpr uint32_t c_offChecksum = 0x7f;
261 static constexpr uint32_t c_maxNameLength = 20;
262
263 static uint8_t frameChecksum(const uint8_t *frame);
264 static void finishDirEntry(uint8_t *entry);
265 // Marks an in-use block (0x5x) deleted, keeping its chain position (0x5x ->
266 // 0xax) and so its links, so the deleted chain stays recoverable.
267 static uint8_t deletedState(uint8_t inUse) { return static_cast<uint8_t>((inUse & 0x0f) | 0xa0); }
268 static bool nameMatches(const uint8_t *entry, const char *name);
269 static bool findFirstBlock(const Frame *dir15, const char *name, int *outBlock);
270 // The set of blocks (bit (slot + 1)) reachable from a valid first-block
271 // head. Anything else - free, deleted, or an orphaned middle/last block with
272 // no head - is available for allocation.
273 static uint16_t reachableBlocks(const Frame *dir15);
274
275 // -- Asynchronous transaction engine -----------------------------------
276 // The operation in flight, and where in it we are. Each device sector
277 // completion advances the machine by exactly one sector transfer.
278 enum class Op : uint8_t {
279 None,
280 GetCardState,
281 Format,
282 GetFreeBlockCount,
283 ListFiles,
284 FileExists,
285 ReadFile,
286 ReadInfo,
287 WriteFile,
288 DeleteFile,
289 };
290 enum class Phase : uint8_t {
291 Fail, // m_result is set; finish on the next tick
292 Header, // read sector 0, validate "MC"
293 ReadDir, // read the 15 directory frames (m_idx = 0..14)
294 ReadTitle, // read a file's first frame to size its header (readFile)
295 ReadData, // read a file's payload frames (readFile)
296 ReadInfo, // read a file's title + icon frames (readFileInfo)
297 WriteData, // write title/icon/payload frames (writeFile)
298 WriteDir, // commit the dirty directory frames (write/delete)
299 Format, // write the format frames (m_idx = 0..63)
300 };
301 enum class StepResult : uint8_t { Continue, Done };
302
303 void begin(Op op, Port port, eastl::function<void(Error)> &&callback);
304 Error runBlocking(GPU &gpu);
305 // Issues exactly one asynchronous device sector transfer for the current
306 // phase, or finishes the transaction if the current phase has no transfer
307 // left to do.
308 void issueOrFinish();
309 // The device's per-sector completion callback: consumes the transfer's
310 // result, advances the per-operation state machine, then chains the next
311 // transfer (or finishes).
312 void onSectorDone(Error error);
313 StepResult afterReadDir();
314 void finish(Error error);
315
316 MemoryCard &m_card;
317
318 // engine state
319 bool m_busy = false;
320 bool m_lockHeld = false;
321 eastl::function<void(Error)> m_callback;
322 Error m_result = Error::OK;
323 Op m_op = Op::None;
324 Phase m_phase = Phase::Fail;
325 Port m_port = Port::Port0;
326 uint32_t m_idx = 0; // step within the current phase
327
328 // working buffers / per-operation state
329 Frame m_dir[15];
330 Frame m_scratch;
331 uint8_t m_chain[16];
332 uint32_t m_chainLen = 0;
333 uint32_t m_headerFrames = 0;
334 uint32_t m_blocksNeeded = 0;
335 uint32_t m_iconFrames = 0;
336 bool m_dirty[15] = {};
337 // The directory slots to commit, in the order they must be written: for a
338 // create the file's head (the 0x51 first block) is written last; for a
339 // delete its head (cut to 0xa1) is written first. So a power loss mid-commit
340 // can only orphan tail blocks, never leave a referenced file pointing at a
341 // half-written chain.
342 uint8_t m_writeOrder[15] = {};
343 uint32_t m_writeOrderLen = 0;
344 uint32_t m_dataOffset = 0;
345 uint32_t m_written = 0;
346 uint32_t m_blockIdx = 0; // current block within the chain
347 uint32_t m_frameInBlock = 0; // current frame within the current block
348
349 // saved operation arguments
350 const char *m_name = nullptr;
351 const char *m_title = nullptr;
352 void *m_readBuffer = nullptr;
353 const void *m_writeData = nullptr;
354 uint32_t m_dataLen = 0;
355 uint32_t m_maxLen = 0;
356 uint32_t *m_outLen = nullptr;
357 uint32_t *m_outFreeBlocks = nullptr;
358 uint32_t *m_outCount = nullptr;
359 bool *m_outExists = nullptr;
360 FileEntry *m_outEntries = nullptr;
361 FileInfo *m_outInfo = nullptr;
362 uint32_t m_maxEntries = 0;
363 Icon m_icon = {};
364};
365
366} // namespace psyqo
The singleton GPU class.
Definition gpu.hh:88
A Sony-compatible memory card filesystem.
Definition memory-card-filesystem.hh:72
Error fileExistsBlocking(GPU &gpu, Port port, const char *name, bool *outExists)
Definition memory-card-filesystem.cpp:744
void format(Port port, eastl::function< void(Error)> &&callback)
Writes a fresh, empty Sony filesystem to the card.
Definition memory-card-filesystem.cpp:645
void readFileInfo(Port port, const char *name, FileInfo *out, eastl::function< void(Error)> &&callback)
Reads a file's title and icon.
Definition memory-card-filesystem.cpp:688
Error deleteFileBlocking(GPU &gpu, Port port, const char *name)
Definition memory-card-filesystem.cpp:781
Error formatBlocking(GPU &gpu, Port port)
Definition memory-card-filesystem.cpp:722
void deleteFile(Port port, const char *name, eastl::function< void(Error)> &&callback)
Deletes a file, freeing all of its blocks.
Definition memory-card-filesystem.cpp:708
void getCardState(Port port, eastl::function< void(Error)> &&callback)
Determines whether a usable, formatted card is present.
Definition memory-card-filesystem.cpp:640
bool isIdle() const
Whether the filesystem is ready to accept a new operation.
Definition memory-card-filesystem.hh:118
void listFiles(Port port, FileEntry *out, uint32_t maxEntries, uint32_t *outCount, eastl::function< void(Error)> &&callback)
Lists the files on the card.
Definition memory-card-filesystem.cpp:658
Error getFreeBlockCountBlocking(GPU &gpu, Port port, uint32_t *outFreeBlocks)
Definition memory-card-filesystem.cpp:727
Error readFileInfoBlocking(GPU &gpu, Port port, const char *name, FileInfo *out)
Definition memory-card-filesystem.cpp:763
void writeFile(Port port, const char *name, const char *title, const Icon &icon, const void *data, uint32_t dataLen, eastl::function< void(Error)> &&callback)
Creates or overwrites a file.
Definition memory-card-filesystem.cpp:696
Error writeFileBlocking(GPU &gpu, Port port, const char *name, const char *title, const Icon &icon, const void *data, uint32_t dataLen)
Definition memory-card-filesystem.cpp:770
void readFile(Port port, const char *name, void *buffer, uint32_t maxLen, uint32_t *outLen, eastl::function< void(Error)> &&callback)
Reads the payload of a file.
Definition memory-card-filesystem.cpp:677
void fileExists(Port port, const char *name, bool *outExists, eastl::function< void(Error)> &&callback)
Reports whether a named file exists, into *outExists.
Definition memory-card-filesystem.cpp:668
Error readFileBlocking(GPU &gpu, Port port, const char *name, void *buffer, uint32_t maxLen, uint32_t *outLen)
Definition memory-card-filesystem.cpp:752
void getFreeBlockCount(Port port, uint32_t *outFreeBlocks, eastl::function< void(Error)> &&callback)
Counts the free 8KiB blocks (0..15) into *outFreeBlocks.
Definition memory-card-filesystem.cpp:650
Error getCardStateBlocking(GPU &gpu, Port port)
Definition memory-card-filesystem.cpp:717
MemoryCardFileSystem(MemoryCard &card)
Definition memory-card-filesystem.hh:113
Error listFilesBlocking(GPU &gpu, Port port, FileEntry *out, uint32_t maxEntries, uint32_t *outCount)
Definition memory-card-filesystem.cpp:734
A low level driver for the PlayStation memory cards.
Definition memory-card.hh:67
Error
The error codes returned by every memory card operation.
Definition memory-card.hh:75
Port
The memory card port to talk to.
Definition memory-card.hh:98
uint32_t out
Definition cpu.c:62
Definition lua.hh:38
A directory listing entry.
Definition memory-card-filesystem.hh:91
uint16_t sizeInBlocks
Definition memory-card-filesystem.hh:93
char name[21]
Definition memory-card-filesystem.hh:92
uint8_t firstBlock
Definition memory-card-filesystem.hh:94
A file's on-card metadata: its title and icon.
Definition memory-card-filesystem.hh:108
uint8_t title[65]
Definition memory-card-filesystem.hh:109
Icon icon
Definition memory-card-filesystem.hh:110
A save icon, in the native PlayStation format.
Definition memory-card-filesystem.hh:82
uint16_t clut[16]
Definition memory-card-filesystem.hh:84
uint8_t pixels[3][128]
Definition memory-card-filesystem.hh:85
uint8_t frameCount
Definition memory-card-filesystem.hh:83
static void * buffer
Definition syscalls.h:231
void void(ptr, size)
void uint32_t(classId, spec)