mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Merge pull request #22277 from hrydgard/kernel-mode-partitions
Kernel mode partitions and sceIo behavior
This commit is contained in:
15 files changed
+235
-35
No files matched your search
@@ -1080,6 +1080,41 @@ bool IsDirectoryWritable(const Path &path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetFileWritable(const Path &filename, bool writable) {
|
||||
switch (filename.Type()) {
|
||||
case PathType::NATIVE:
|
||||
break;
|
||||
default:
|
||||
// Content URIs and the other virtual path types have no notion of this, so say so rather
|
||||
// than pretending it worked.
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
const DWORD attrs = GetFileAttributesW(filename.ToWString().c_str());
|
||||
if (attrs == INVALID_FILE_ATTRIBUTES) {
|
||||
return false;
|
||||
}
|
||||
const DWORD updated = writable ? (attrs & ~FILE_ATTRIBUTE_READONLY) : (attrs | FILE_ATTRIBUTE_READONLY);
|
||||
if (updated == attrs) {
|
||||
return true;
|
||||
}
|
||||
return SetFileAttributesW(filename.ToWString().c_str(), updated) != 0;
|
||||
#else
|
||||
struct stat info;
|
||||
if (stat(filename.c_str(), &info) != 0) {
|
||||
return false;
|
||||
}
|
||||
// Mirror the write bits onto whoever already has read access, which is what clearing the FAT
|
||||
// read-only attribute amounts to.
|
||||
mode_t mode = info.st_mode & ~(mode_t)0222;
|
||||
if (writable) {
|
||||
mode |= (info.st_mode & 0444) >> 1;
|
||||
}
|
||||
return chmod(filename.c_str(), mode) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Deletes an empty directory, returns true on success
|
||||
// WARNING: On Android with content URIs, it will delete recursively!
|
||||
bool DeleteDir(const Path &path) {
|
||||
|
||||
@@ -127,6 +127,10 @@ bool CreateEmptyFile(const Path &filename);
|
||||
// don't tell the whole story (Windows ACLs, read-only mounts, ...).
|
||||
bool IsDirectoryWritable(const Path &path);
|
||||
|
||||
// Set or clear a file's read-only-ness, which is what a FAT read-only attribute maps onto.
|
||||
// Returns false where the platform can't express it - notably Android content URIs.
|
||||
bool SetFileWritable(const Path &filename, bool writable);
|
||||
|
||||
// Opens ini file (cheats, texture replacements etc.)
|
||||
// TODO: Belongs in System or something.
|
||||
bool OpenFileInEditor(const Path &fileName);
|
||||
|
||||
@@ -634,6 +634,11 @@ bool DirectoryFileSystem::RemoveFile(const std::string &filename) {
|
||||
return ReplayApplyDisk(ReplayAction::FILE_REMOVE, retValue, CoreTiming::GetGlobalTimeUs()) != 0;
|
||||
}
|
||||
|
||||
bool DirectoryFileSystem::SetFileWritable(const std::string &filename, bool writable) {
|
||||
Path fullName = GetLocalPath(filename);
|
||||
return File::SetFileWritable(fullName, writable);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -85,6 +85,7 @@ public:
|
||||
bool RmDir(const std::string &dirname) override;
|
||||
int RenameFile(const std::string &from, const std::string &to) override;
|
||||
bool RemoveFile(const std::string &filename) override;
|
||||
bool SetFileWritable(const std::string &filename, bool writable) override;
|
||||
FileSystemFlags Flags() const override { return flags; }
|
||||
u64 FreeDiskSpace(const std::string &path) override;
|
||||
|
||||
|
||||
@@ -103,6 +103,30 @@ static std::string CleanShortNamePart(std::string_view part, size_t maxLen, bool
|
||||
return out;
|
||||
}
|
||||
|
||||
// Whether the name's capitalisation survives without a long-name entry. FAT keeps one flag for
|
||||
// the base and one for the extension, but the PSP only honours the base one - so a lowercase
|
||||
// extension forces a long name entry, and with it a ~1 suffix, while a lowercase base alone
|
||||
// doesn't. That's why hardware gives "shrt" -> SHRT but "readme.txt" -> README~1.TXT.
|
||||
static bool ShortNameCaseSurvives(std::string_view base, std::string_view ext) {
|
||||
bool lower = false, upper = false;
|
||||
for (char c : base) {
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
lower = true;
|
||||
} else if (c >= 'A' && c <= 'Z') {
|
||||
upper = true;
|
||||
}
|
||||
}
|
||||
if (lower && upper) {
|
||||
return false;
|
||||
}
|
||||
for (char c : ext) {
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GenerateFatShortNames(const std::vector<PSPFileInfo> &listing, std::vector<std::string> *shortNames) {
|
||||
shortNames->clear();
|
||||
shortNames->reserve(listing.size());
|
||||
@@ -128,6 +152,10 @@ void GenerateFatShortNames(const std::vector<PSPFileInfo> &listing, std::vector<
|
||||
lossy = true;
|
||||
}
|
||||
|
||||
if (!ShortNameCaseSurvives(baseIn, extIn)) {
|
||||
lossy = true;
|
||||
}
|
||||
|
||||
std::string base = CleanShortNamePart(baseIn, 8, &lossy);
|
||||
std::string ext = CleanShortNamePart(extIn, 3, &lossy);
|
||||
if (base.empty()) {
|
||||
|
||||
@@ -157,6 +157,9 @@ public:
|
||||
virtual bool RmDir(const std::string &dirname) = 0;
|
||||
virtual int RenameFile(const std::string &from, const std::string &to) = 0;
|
||||
virtual bool RemoveFile(const std::string &filename) = 0;
|
||||
// Sets or clears the FAT read-only attribute, as sceIoChstat does. Defaults to "can't", which
|
||||
// is right for read-only filesystems and for hosts that can't express it.
|
||||
virtual bool SetFileWritable(const std::string &filename, bool writable) { return false; }
|
||||
virtual int Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) = 0;
|
||||
virtual PSPDevType DevType(u32 handle) = 0;
|
||||
virtual FileSystemFlags Flags() const = 0;
|
||||
|
||||
@@ -507,6 +507,19 @@ int MetaFileSystem::RenameFile(const std::string &from, const std::string &to)
|
||||
}
|
||||
}
|
||||
|
||||
bool MetaFileSystem::SetFileWritable(const std::string &filename, bool writable)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(lock);
|
||||
std::string of;
|
||||
IFileSystem *system;
|
||||
int error = MapFilePath(filename, &of, &system);
|
||||
if (error == 0) {
|
||||
return system->SetFileWritable(of, writable);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool MetaFileSystem::RemoveFile(const std::string &filename)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> guard(lock);
|
||||
|
||||
@@ -137,6 +137,7 @@ public:
|
||||
bool RmDir(const std::string &dirname) override;
|
||||
int RenameFile(const std::string &from, const std::string &to) override;
|
||||
bool RemoveFile(const std::string &filename) override;
|
||||
bool SetFileWritable(const std::string &filename, bool writable) override;
|
||||
int Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) override;
|
||||
PSPDevType DevType(u32 handle) override;
|
||||
FileSystemFlags Flags() const override { return FileSystemFlags::NONE; }
|
||||
|
||||
+56
-14
@@ -871,9 +871,12 @@ void ConvertTmToPspDateTime(ScePspDateTime& date_out, const tm& date_in, int mic
|
||||
date_out.microsecond = microSeconds;
|
||||
}
|
||||
|
||||
static void __IoGetStat(SceIoStat *stat, const PSPFileInfo &info) {
|
||||
memset(stat, 0xfe, sizeof(SceIoStat));
|
||||
|
||||
// isFAT is whether the file lives on a FAT volume (the memory stick), which changes both the
|
||||
// permissions reported and whether st_private means anything.
|
||||
static void __IoGetStat(SceIoStat *stat, const PSPFileInfo &info, bool isFAT) {
|
||||
// Deliberately no memset: pspautotests io/stat poisons the struct and shows a real PSP writes
|
||||
// only as far as the timestamps - the six st_private words come back exactly as the caller
|
||||
// left them. Clearing the whole struct would destroy 24 bytes the kernel never touches.
|
||||
int type, attr;
|
||||
if (info.type & FILETYPE_DIRECTORY) {
|
||||
type = SCE_STM_FDIR;
|
||||
@@ -883,14 +886,27 @@ static void __IoGetStat(SceIoStat *stat, const PSPFileInfo &info) {
|
||||
attr = TYPE_FILE;
|
||||
}
|
||||
|
||||
if (isFAT) {
|
||||
// FAT has no permissions of its own, so everything reads back as 0777 - including the
|
||||
// execute bits, which is what Beats needed (issue #14812). Clearing the write bits is
|
||||
// the read-only attribute, and that shows up in st_attr too.
|
||||
const bool readOnly = (info.access & 0222) == 0;
|
||||
stat->st_mode = type | (readOnly ? 0555 : 0777);
|
||||
stat->st_attr = attr | (readOnly ? 0x01 : 0x00);
|
||||
} else {
|
||||
stat->st_mode = type | info.access;
|
||||
stat->st_attr = attr;
|
||||
}
|
||||
stat->st_size = info.size;
|
||||
ConvertTmToPspDateTime(stat->st_a_time, info.atime, info.atimeUs);
|
||||
ConvertTmToPspDateTime(stat->st_c_time, info.ctime, info.ctimeUs);
|
||||
ConvertTmToPspDateTime(stat->st_m_time, info.mtime, info.mtimeUs);
|
||||
// st_private[0] carries the LBN on a UMD, which games read to build disc0:/sce_lbn paths -
|
||||
// see umd/raw_access. On the memory stick a real PSP leaves it alone entirely.
|
||||
if (!isFAT) {
|
||||
stat->st_private[0] = info.startSector;
|
||||
}
|
||||
}
|
||||
|
||||
static void __IoSchedAsync(FileNode *f, int fd, int usec) {
|
||||
CoreTiming::ScheduleEvent(usToCycles(usec), asyncNotifyEvent, fd);
|
||||
@@ -911,11 +927,25 @@ static u32 sceIoGetstat(const char *filename, u32 addr) {
|
||||
// TODO: Improve timing (although this seems normally slow..)
|
||||
int usec = 1000;
|
||||
|
||||
// A real PSP refuses to stat the root of a volume - io/stat records sceIoGetstat("ms0:/")
|
||||
// coming back as an invalid argument rather than describing the directory.
|
||||
const char *colon = strchr(filename, ':');
|
||||
if (colon != nullptr) {
|
||||
const char *rest = colon + 1;
|
||||
while (*rest == '/') {
|
||||
++rest;
|
||||
}
|
||||
if (*rest == '\0') {
|
||||
return hleDelayResult(hleLogWarning(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT, "volume root"), "io getstat", usec);
|
||||
}
|
||||
}
|
||||
|
||||
const bool isFAT = pspFileSystem.FlagsFromFilename(filename) & FileSystemFlags::SIMULATE_FAT32;
|
||||
auto stat = PSPPointer<SceIoStat>::Create(addr);
|
||||
PSPFileInfo info = pspFileSystem.GetFileInfo(filename);
|
||||
if (info.exists) {
|
||||
if (stat.IsValid()) {
|
||||
__IoGetStat(stat, info);
|
||||
__IoGetStat(stat, info, isFAT);
|
||||
stat.NotifyWrite("IoGetstat");
|
||||
return hleDelayResult(hleLogDebug(Log::sceIo, 0, "sector = %08x", info.startSector), "io getstat", usec);
|
||||
} else {
|
||||
@@ -931,12 +961,26 @@ static u32 sceIoChstat(const char *filename, u32 iostatptr, u32 changebits) {
|
||||
if (!iostat.IsValid())
|
||||
return hleReportError(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT, "bad address");
|
||||
|
||||
ERROR_LOG(Log::sceIo, "UNIMPL sceIoChstat(%s, %08x, %08x)", filename, iostatptr, changebits);
|
||||
if (changebits & SCE_CST_MODE)
|
||||
ERROR_LOG_REPORT(Log::sceIo, "sceIoChstat: change mode to %03o requested", iostat->st_mode);
|
||||
// On a FAT volume the write bits in st_mode and the 0x01 bit in st_attr are two views of the
|
||||
// same read-only flag: io/stat/readonly records that setting either one produces both, and
|
||||
// that it's reversible. Anything else in the struct is still ignored.
|
||||
bool haveWritable = false;
|
||||
bool writable = false;
|
||||
if (changebits & SCE_CST_MODE) {
|
||||
writable = (iostat->st_mode & 0222) != 0;
|
||||
haveWritable = true;
|
||||
}
|
||||
if (changebits & SCE_CST_ATTR) {
|
||||
// These are pretty much all of the reported calls: https://report.ppsspp.org/logs/kind/1115
|
||||
ERROR_LOG_REPORT(Log::sceIo, "sceIoChstat: change attr to %04x requested", iostat->st_attr);
|
||||
// The attribute wins if both were asked for, since it names the flag directly.
|
||||
writable = (iostat->st_attr & 0x01) == 0;
|
||||
haveWritable = true;
|
||||
}
|
||||
if (haveWritable) {
|
||||
if (!pspFileSystem.SetFileWritable(filename, writable)) {
|
||||
// Nothing to be done on a host that can't express it - Android content URIs, or a
|
||||
// read-only filesystem. Hardware would have succeeded, so don't fail the call.
|
||||
WARN_LOG(Log::sceIo, "sceIoChstat: could not make %s %s", filename, writable ? "writable" : "read-only");
|
||||
}
|
||||
}
|
||||
if (changebits & SCE_CST_SIZE)
|
||||
ERROR_LOG(Log::sceIo, "sceIoChstat: change size requested");
|
||||
@@ -2533,17 +2577,15 @@ static u32 sceIoDread(int id, u32 dirent_addr) {
|
||||
}
|
||||
|
||||
PSPFileInfo &info = dir->listing[dir->index];
|
||||
__IoGetStat(&entry->d_stat, info);
|
||||
const bool isFATDir = pspFileSystem.FlagsFromFilename(dir->name) & FileSystemFlags::SIMULATE_FAT32;
|
||||
__IoGetStat(&entry->d_stat, info, isFATDir);
|
||||
|
||||
strncpy(entry->d_name, info.name.c_str(), 256);
|
||||
entry->d_name[255] = '\0';
|
||||
|
||||
bool isFAT = pspFileSystem.FlagsFromFilename(dir->name) & FileSystemFlags::SIMULATE_FAT32;
|
||||
// Only write d_private for memory stick
|
||||
if (isFAT) {
|
||||
if (isFATDir) {
|
||||
const std::string &shortName = dir->ShortName(dir->index);
|
||||
// All files look like they're executable on FAT. This is required for Beats, see issue #14812
|
||||
entry->d_stat.st_mode |= 0111;
|
||||
// write d_private for supporting Custom BGM
|
||||
// ref JPCSP https://code.google.com/p/jpcsp/source/detail?r=3468
|
||||
if (Memory::IsValidAddress(entry->d_private)){
|
||||
|
||||
+31
-12
@@ -46,18 +46,33 @@ struct KernelHeap : public KernelObject {
|
||||
};
|
||||
|
||||
static int sceKernelCreateHeap(int partitionId, int size, int flags, const char *Name) {
|
||||
u32 allocSize = (size + 3) & ~3;
|
||||
// Everything below is recorded by pspautotests sysmem/kernel/heap, which is the first test
|
||||
// this API has ever had - these used to be guesses.
|
||||
//
|
||||
// Only partitions 1-6 exist, and anything else is ILLEGAL_PARTITION rather than the
|
||||
// ILLEGAL_ARGUMENT this used to return. Note the test can't cover partition 5: creating a
|
||||
// heap in the volatile partition takes a real PSP down hard enough to need a reboot.
|
||||
if (partitionId < 1 || partitionId > 6)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_PARTITION, "invalid partition %d", partitionId);
|
||||
|
||||
BlockAllocator *allocator = BlockAllocatorFromID(partitionId);
|
||||
// TODO: Validate error code.
|
||||
if (!allocator)
|
||||
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition");
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_PARTITION, "invalid partition %d", partitionId);
|
||||
|
||||
// TODO: This should probably actually use flags? Name?
|
||||
// A zero or negative size is refused outright, before anything is allocated.
|
||||
if (size <= 0)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_HEAPBLOCK_ALLOC_FAILED, "invalid size %d", size);
|
||||
// A name is required, unlike most of the kernel object constructors.
|
||||
if (!Name)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ERROR, "invalid name");
|
||||
|
||||
u32 allocSize = (size + 3) & ~3;
|
||||
|
||||
// flags really is ignored - the test sweeps -1, 0, 1, 2, 3, 4, 0x100 and 0x1000 and every
|
||||
// one of them creates a heap.
|
||||
u32 addr = allocator->Alloc(allocSize, g_fromBottom, StringFromFormat("KernelHeap/%s", Name).c_str());
|
||||
if (addr == (u32)-1) {
|
||||
// TODO: Validate error code.
|
||||
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_NO_MEMORY, "fFailed to allocate %d bytes of memory", size);
|
||||
return hleLogError(Log::sceKernel, SCE_KERNEL_ERROR_NO_MEMORY, "failed to allocate %d bytes of memory", size);
|
||||
}
|
||||
|
||||
KernelHeap *heap = new KernelHeap();
|
||||
@@ -65,7 +80,7 @@ static int sceKernelCreateHeap(int partitionId, int size, int flags, const char
|
||||
|
||||
heap->partitionId = partitionId;
|
||||
heap->flags = flags;
|
||||
heap->name = Name ? Name : ""; // Not sure if this needs validation.
|
||||
heap->name = Name;
|
||||
heap->size = allocSize;
|
||||
heap->address = addr;
|
||||
heap->alloc.Init(heap->address + 128, heap->size - 128, true);
|
||||
@@ -152,12 +167,16 @@ static int sceKernelAllocHeapMemoryWithOption(int heapId, u32 memSize, u32 param
|
||||
if (paramsPtr != 0) {
|
||||
if (!Memory::IsValid4AlignedRange(paramsPtr, 8))
|
||||
return hleLogError(Log::sceKernel, 0, "invalid paramsPtr");
|
||||
u32 size = Memory::ReadUnchecked_U32(paramsPtr); // size of the params struct
|
||||
if (size < 8)
|
||||
return hleLogError(Log::sceKernel, 0, "invalid param size");
|
||||
if (size > 8)
|
||||
WARN_LOG(Log::HLE, "sceKernelAllocHeapMemoryWithOption(): unexpected param size %d", size);
|
||||
// The size field is not validated at all - sysmem/kernel/heap sweeps 0, 4, 8, 12 and
|
||||
// 0x100 through here and every one of them allocates. Only the alignment matters.
|
||||
grain = Memory::ReadUnchecked_U32(paramsPtr + 4);
|
||||
// And it has to be a power of two from 4 to 0x80. 0 means "no preference", 1 and 2 are
|
||||
// refused just as firmly as 0x100 and up.
|
||||
if (grain == 0) {
|
||||
grain = 4;
|
||||
} else if (grain < 4 || grain > 0x80 || (grain & (grain - 1)) != 0) {
|
||||
return hleLogWarning(Log::sceKernel, 0, "invalid alignment %d", grain);
|
||||
}
|
||||
}
|
||||
// There's 8 bytes at the end of every block, reserved.
|
||||
memSize += 8;
|
||||
|
||||
@@ -525,7 +525,10 @@ static void __KernelSortFplThreads(FPL *fpl)
|
||||
int sceKernelCreateFpl(const char *name, u32 mpid, u32 attr, u32 blockSize, u32 numBlocks, u32 optPtr) {
|
||||
if (!name)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_NO_MEMORY, "invalid name");
|
||||
if (mpid < 1 || mpid > 9 || mpid == 7)
|
||||
// Only partitions 1-6 exist. sysmem/partitions and its kernel-mode twin record 7 and up
|
||||
// coming back ILLEGAL_ARGUMENT from both privilege levels; what privilege changes is the
|
||||
// permission check below, not the range.
|
||||
if (mpid < 1 || mpid > 6)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", mpid);
|
||||
|
||||
BlockAllocator *allocator = BlockAllocatorFromID(mpid);
|
||||
@@ -882,8 +885,14 @@ int sceKernelAllocPartitionMemory(int partition, const char *name, int type, u32
|
||||
if ((addr & (addr - 1)) != 0 || addr == 0)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ALIGNMENT_SIZE, "invalid alignment %x", addr);
|
||||
}
|
||||
if (partition < 1 || partition > 9 || partition == 7)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %x", partition);
|
||||
// SysMemUserForUser and SysMemForKernel both land here, and sysmem/partitions shows they
|
||||
// report an out-of-range partition differently - ILLEGAL_ARGUMENT from the user entry point,
|
||||
// ILLEGAL_PARTITION from the kernel one. hleIsKernelMode() is exactly "came in through the
|
||||
// kernel NID", which is the distinction being made.
|
||||
if (partition < 1 || partition > 6) {
|
||||
const u32 error = hleIsKernelMode() ? SCE_KERNEL_ERROR_ILLEGAL_PARTITION : SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
|
||||
return hleLogWarning(Log::sceKernel, error, "invalid partition %x", partition);
|
||||
}
|
||||
|
||||
BlockAllocator *allocator = BlockAllocatorFromID(partition);
|
||||
if (allocator == nullptr)
|
||||
@@ -1311,7 +1320,10 @@ static void __KernelSortVplThreads(VPL *vpl)
|
||||
SceUID sceKernelCreateVpl(const char *name, int partition, u32 attr, u32 vplSize, u32 optPtr) {
|
||||
if (!name)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ERROR, "invalid name");
|
||||
if (partition < 1 || partition > 9 || partition == 7)
|
||||
// Only partitions 1-6 exist. sysmem/partitions and its kernel-mode twin record 7 and up
|
||||
// coming back ILLEGAL_ARGUMENT from both privilege levels; what privilege changes is the
|
||||
// permission check below, not the range.
|
||||
if (partition < 1 || partition > 6)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", partition);
|
||||
|
||||
BlockAllocator *allocator = BlockAllocatorFromID(partition);
|
||||
|
||||
@@ -622,7 +622,10 @@ void __KernelMsgPipeDoState(PointerWrap &p)
|
||||
int sceKernelCreateMsgPipe(const char *name, int partition, u32 attr, u32 size, u32 optionsPtr) {
|
||||
if (!name)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_NO_MEMORY, "invalid name");
|
||||
if (partition < 1 || partition > 9 || partition == 7)
|
||||
// Only partitions 1-6 exist. sysmem/partitions and its kernel-mode twin record 7 and up
|
||||
// coming back ILLEGAL_ARGUMENT from both privilege levels; what privilege changes is the
|
||||
// permission check below, not the range.
|
||||
if (partition < 1 || partition > 6)
|
||||
return hleLogWarning(Log::sceKernel, SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "invalid partition %d", partition);
|
||||
|
||||
BlockAllocator *allocator = BlockAllocatorFromID(partition);
|
||||
|
||||
+1
-1
Submodule pspautotests updated: 7c5d150d40...1dda6a3709.
@@ -221,6 +221,8 @@ tests_good = [
|
||||
"io/cwd/cwd",
|
||||
"io/file/rename",
|
||||
"io/directory/directory",
|
||||
"io/stat/stat",
|
||||
"io/stat/readonly",
|
||||
"io/open/badparent",
|
||||
"jpeg/create",
|
||||
"jpeg/delete",
|
||||
@@ -249,6 +251,9 @@ tests_good = [
|
||||
"sysmem/freesize",
|
||||
"sysmem/memblock",
|
||||
"sysmem/sysmem",
|
||||
"sysmem/partitions",
|
||||
"sysmem/kernel/partitions",
|
||||
"sysmem/kernel/heap",
|
||||
"sysmem/volatile",
|
||||
"threads/alarm/alarm",
|
||||
"threads/alarm/cancel/cancel",
|
||||
@@ -458,11 +463,11 @@ tests_next = [
|
||||
"intr/registersub",
|
||||
"intr/releasesub",
|
||||
"intr/waits",
|
||||
"sysmem/kernel/heapgrow",
|
||||
"io/file/file",
|
||||
"io/io/io",
|
||||
"io/iodrv/iodrv",
|
||||
"io/shortname/shortname",
|
||||
"io/stat/stat",
|
||||
"io/open/tty0",
|
||||
"jpeg/csc",
|
||||
"jpeg/decode",
|
||||
|
||||
+31
-2
@@ -2914,14 +2914,29 @@ bool TestFatShortNames() {
|
||||
return shortNames;
|
||||
};
|
||||
|
||||
// Names that already fit 8.3 are only uppercased, and the navigation entries are left alone.
|
||||
// A name that is already valid uppercase 8.3 is kept as-is, and the navigation entries are
|
||||
// left alone. "readme.md" is not: its extension is lowercase, which a PSP can't record, so it
|
||||
// gets a counter - see the case block below.
|
||||
std::vector<std::string> plain = shortNamesFor({".", "..", "TEST.TXT", "readme.md", "WIPEOUT"});
|
||||
EXPECT_EQ_STR(plain[0], std::string("."));
|
||||
EXPECT_EQ_STR(plain[1], std::string(".."));
|
||||
EXPECT_EQ_STR(plain[2], std::string("TEST.TXT"));
|
||||
EXPECT_EQ_STR(plain[3], std::string("README.MD"));
|
||||
EXPECT_EQ_STR(plain[3], std::string("README~1.MD"));
|
||||
EXPECT_EQ_STR(plain[4], std::string("WIPEOUT"));
|
||||
|
||||
// Capitalisation, as recorded off a real PSP by pspautotests io/shortname. FAT keeps a
|
||||
// lowercase flag for the base and another for the extension, but the PSP only honours the
|
||||
// base one - so a lowercase base survives on its own and a lowercase extension never does.
|
||||
std::vector<std::string> cased = shortNamesFor({"shrt", "readme.txt", "UPPER.TXT", "MiXeD.txt"});
|
||||
// All lowercase, no extension: representable, so no counter.
|
||||
EXPECT_EQ_STR(cased[0], std::string("SHRT"));
|
||||
// Lowercase extension: not representable.
|
||||
EXPECT_EQ_STR(cased[1], std::string("README~1.TXT"));
|
||||
// Already uppercase throughout.
|
||||
EXPECT_EQ_STR(cased[2], std::string("UPPER.TXT"));
|
||||
// Mixed case in the base.
|
||||
EXPECT_EQ_STR(cased[3], std::string("MIXED~1.TXT"));
|
||||
|
||||
// Long names get truncated to six characters plus a counter, which keeps counting past ~4.
|
||||
std::vector<std::string> many = shortNamesFor({
|
||||
"sample-12s.mp3",
|
||||
@@ -2945,9 +2960,23 @@ bool TestFatShortNames() {
|
||||
std::vector<std::string> odd = shortNamesFor({"my song.mp3", "a+b.mp3", "no_ext", ".hidden"});
|
||||
EXPECT_EQ_STR(odd[0], std::string("MYSONG~1.MP3"));
|
||||
EXPECT_EQ_STR(odd[1], std::string("A_B~1.MP3"));
|
||||
// All lowercase with no extension, so this one keeps its name.
|
||||
EXPECT_EQ_STR(odd[2], std::string("NO_EXT"));
|
||||
EXPECT_EQ_STR(odd[3], std::string("HIDDEN~1"));
|
||||
|
||||
// The rest of what io/shortname records, so the whole recorded set is pinned here and not
|
||||
// only in a test that needs a PSP to re-run.
|
||||
std::vector<std::string> hw = shortNamesFor({
|
||||
"a.b.c.txt", "noextensionhere", "sp ace.txt", "+plus[brack].txt",
|
||||
"toolongextension.mpeg", "LongDirectoryName",
|
||||
});
|
||||
EXPECT_EQ_STR(hw[0], std::string("ABC~1.TXT"));
|
||||
EXPECT_EQ_STR(hw[1], std::string("NOEXTE~1"));
|
||||
EXPECT_EQ_STR(hw[2], std::string("SPACE~1.TXT"));
|
||||
EXPECT_EQ_STR(hw[3], std::string("_PLUS_~1.TXT"));
|
||||
EXPECT_EQ_STR(hw[4], std::string("TOOLON~1.MPE"));
|
||||
EXPECT_EQ_STR(hw[5], std::string("LONGDI~1"));
|
||||
|
||||
// Two long names sharing a six character stem must not collide.
|
||||
std::vector<std::string> collide = shortNamesFor({"longname-one.txt", "longname-two.txt"});
|
||||
EXPECT_EQ_STR(collide[0], std::string("LONGNA~1.TXT"));
|
||||
|
||||
Reference in new issue
Block a user