mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-20 03:18:04 +02:00
sceIo: generate and resolve FAT 8.3 short names
sceIoDread hands back a dirent whose d_private holds the 8.3 short name ahead of the long name, and we never wrote the short name at all - the game got whatever was on the stack there. Crazy Taxi: Fare Wars reads it rather than d_name, so it rejected every file in ms0:/MUSIC, ended up with an empty playlist and never even reserved an mp3 handle: custom soundtracks were silently dead, with the game spinning on InitResource/SetLoopNum forever. Generate the names from the directory listing, and resolve them back in DirectoryFileSystem so a game can open a file by the short name it was given. Both sides come from the same function, so they agree. We can't lean on the host for any of this. Linux, macOS and Android have no 8.3 names at all, and while Windows does keep aliases it generates them by a different rule - it counts to ~4 and then switches to a hash - so resolution runs before the literal path is tried rather than as a fallback, or on Windows we'd quietly open a different file than the one we handed the game. The exact names a real PSP produces are still unverified - no pspautotest covers d_private - so this implements the ordinary FAT rule and the new FatShortNames unit test pins that down until hardware can settle it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
284472ae13
commit
fb8c99ad49
@@ -612,14 +612,16 @@ int DirectoryFileSystem::RenameFile(const std::string &from, const std::string &
|
||||
}
|
||||
|
||||
bool DirectoryFileSystem::RemoveFile(const std::string &filename) {
|
||||
Path localPath = GetLocalPath(filename);
|
||||
std::string resolved = filename;
|
||||
ResolveShortNames(resolved);
|
||||
Path localPath = GetLocalPath(resolved);
|
||||
|
||||
bool retValue = File::Delete(localPath);
|
||||
|
||||
if (flags & FileSystemFlags::CASE_SENSITIVE) {
|
||||
if (!retValue) {
|
||||
// May have failed due to case sensitivity, so try again. Try even if it fails?
|
||||
std::string fullNamePath = filename;
|
||||
std::string fullNamePath = resolved;
|
||||
if (!FixPathCase(basePath, fullNamePath, FPC_FILE_MUST_EXIST))
|
||||
return (bool)ReplayApplyDisk(ReplayAction::FILE_REMOVE, false, CoreTiming::GetGlobalTimeUs());
|
||||
localPath = GetLocalPath(fullNamePath);
|
||||
@@ -632,7 +634,53 @@ bool DirectoryFileSystem::RemoveFile(const std::string &filename) {
|
||||
return ReplayApplyDisk(ReplayAction::FILE_REMOVE, retValue, CoreTiming::GetGlobalTimeUs()) != 0;
|
||||
}
|
||||
|
||||
// Note that this runs *before* the literal path is tried, not as a fallback after it fails. That's
|
||||
// deliberate: on Windows the host resolves its own 8.3 aliases, which are generated by a different
|
||||
// rule than ours, so opening the literal name can quietly land on a different file than the one we
|
||||
// handed the game in sceIoDread. Everywhere else the literal name simply wouldn't exist.
|
||||
void DirectoryFileSystem::ResolveShortNames(std::string &path) {
|
||||
// Only the memory stick is FAT, and only a name with a counter in it can be a short name, so
|
||||
// ordinary paths cost one character scan and nothing more.
|
||||
if (!(flags & FileSystemFlags::SIMULATE_FAT32) || path.find('~') == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
if (resolvingShortNames_) {
|
||||
return;
|
||||
}
|
||||
resolvingShortNames_ = true;
|
||||
|
||||
std::vector<std::string_view> parts;
|
||||
SplitString(path, '/', parts);
|
||||
|
||||
std::string resolved;
|
||||
for (std::string_view rawPart : parts) {
|
||||
std::string part(rawPart);
|
||||
if (part.find('~') != std::string::npos) {
|
||||
bool exists = false;
|
||||
std::vector<PSPFileInfo> listing = GetDirListing(resolved, &exists);
|
||||
if (exists) {
|
||||
std::vector<std::string> shortNames;
|
||||
GenerateFatShortNames(listing, &shortNames);
|
||||
for (size_t i = 0; i < shortNames.size(); i++) {
|
||||
if (equalsNoCase(shortNames[i], part)) {
|
||||
part = listing[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!resolved.empty()) {
|
||||
resolved += "/";
|
||||
}
|
||||
resolved += part;
|
||||
}
|
||||
|
||||
resolvingShortNames_ = false;
|
||||
path = resolved;
|
||||
}
|
||||
|
||||
int DirectoryFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) {
|
||||
ResolveShortNames(filename);
|
||||
OpenFileEntry entry;
|
||||
entry.hFile.fileSystemFlags_ = flags;
|
||||
u32 err = 0;
|
||||
@@ -761,6 +809,8 @@ static u32 PspAccessBits(bool isDirectory, bool isWritable) {
|
||||
}
|
||||
|
||||
PSPFileInfo DirectoryFileSystem::GetFileInfo(std::string filename) {
|
||||
ResolveShortNames(filename);
|
||||
|
||||
PSPFileInfo x;
|
||||
x.name = filename;
|
||||
|
||||
@@ -894,6 +944,11 @@ bool DirectoryFileSystem::ComputeRecursiveDirSizeIfFast(const std::string &path,
|
||||
std::vector<PSPFileInfo> DirectoryFileSystem::GetDirListing(std::string_view path, bool *exists) {
|
||||
std::vector<PSPFileInfo> myVector;
|
||||
|
||||
// A game can open a subdirectory by its short name too.
|
||||
std::string resolvedPath(path);
|
||||
ResolveShortNames(resolvedPath);
|
||||
path = resolvedPath;
|
||||
|
||||
std::vector<File::FileInfo> files;
|
||||
Path localPath = GetLocalPath(path);
|
||||
const int flags = File::GETFILES_GETHIDDEN | File::GETFILES_GET_NAVIGATION_ENTRIES;
|
||||
|
||||
Reference in New Issue
Block a user