diff --git a/src/libs/libSaveData.cpp b/src/libs/libSaveData.cpp index b55f3f90..50f280bb 100644 --- a/src/libs/libSaveData.cpp +++ b/src/libs/libSaveData.cpp @@ -27,6 +27,7 @@ namespace SaveData { // TODO(): specify dir at launcher static constexpr char SAVE_DATA_DIR[] = "_SaveData"; +static constexpr uint64_t SAVE_DATA_BLOCKS_MIN = 48; static constexpr uint64_t SAVE_DATA_BLOCKS_MAX = 16384; struct SceSaveDataDirName { @@ -305,9 +306,9 @@ static bool valid_memory_range(const SaveDataMemoryData& data, size_t size) { data.buf_size <= size - static_cast(data.offset); } -// Keep memory.dat as raw guest bytes. Replacing a flushed temporary file protects the +// Replacing a flushed temporary file protects the // previous save if writing fails or the emulator exits partway through an update. -static int write_memory_file(const std::filesystem::path& path, const void* data, uint32_t size) { +static int write_save_file(const std::filesystem::path& path, const void* data, uint32_t size) { const auto temporary = std::filesystem::path(path.string() + ".tmp"); Common::File file; if (!file.Create(temporary)) { @@ -329,6 +330,21 @@ static int write_memory_file(const std::filesystem::path& path, const void* data return SAVE_DATA_ERROR_INTERNAL; } +static int read_save_blocks(const std::filesystem::path& directory, uint64_t* blocks) { + Common::File file(directory / "sce_sys" / "blocks.bin", Common::File::Mode::Read); + if (file.IsInvalid() || file.Size() != sizeof(*blocks)) { + return SAVE_DATA_ERROR_BROKEN; + } + uint64_t value = 0; + uint32_t read = 0; + file.Read(&value, sizeof(value), &read); + if (read != sizeof(value) || value < SAVE_DATA_BLOCKS_MIN || value > SAVE_DATA_BLOCKS_MAX) { + return SAVE_DATA_ERROR_BROKEN; + } + *blocks = value; + return OK; +} + static int save_memory(const std::filesystem::path& directory, const SaveDataMemory& memory) { std::error_code error; std::filesystem::create_directories(directory, error); @@ -336,10 +352,10 @@ static int save_memory(const std::filesystem::path& directory, const SaveDataMem return SAVE_DATA_ERROR_INTERNAL; } // Display metadata is a separate host sidecar, leaving memory.dat as raw guest bytes. - int result = write_memory_file(directory / "param.bin", &memory.param, sizeof(memory.param)); + int result = write_save_file(directory / "param.bin", &memory.param, sizeof(memory.param)); if (result == OK) { result = - write_memory_file(directory / "memory.dat", memory.data.data(), memory.data.size()); + write_save_file(directory / "memory.dat", memory.data.data(), memory.data.size()); } return result; } @@ -561,9 +577,14 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond, result->params[i] = {}; } if (result->infos != nullptr) { - result->infos[i] = {}; - result->infos[i].blocks = SAVE_DATA_BLOCKS_MAX; - result->infos[i].free_blocks = SAVE_DATA_BLOCKS_MAX; + auto& info = result->infos[i]; + info = {}; + const int status = read_save_blocks(root / dir_list[i], &info.blocks); + if (status != OK) { + return status; + } + // Host-directory saves report their allocation as free space. + info.free_blocks = info.blocks; } } @@ -621,10 +642,22 @@ int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResul bool created = false; if ((create || create2) && !Common::File::IsDirectoryExisting(mount_dir)) { - Common::File::CreateDirectories(mount_dir); + if (mount->blocks < SAVE_DATA_BLOCKS_MIN || mount->blocks > SAVE_DATA_BLOCKS_MAX) { + return SAVE_DATA_ERROR_PARAMETER; + } + const auto metadata = std::filesystem::path(mount_dir) / "sce_sys"; + std::error_code error; + std::filesystem::create_directories(metadata, error); + if (error) { + return SAVE_DATA_ERROR_INTERNAL; + } + const int status = write_save_file(metadata / "blocks.bin", &mount->blocks, + sizeof(mount->blocks)); + if (status != OK) { + std::filesystem::remove_all(mount_dir, error); + return status; + } created = true; - - EXIT_NOT_IMPLEMENTED((!Common::File::IsDirectoryExisting(mount_dir))); } return mount_save_data(slot, dir_name, mount_dir, created ? 1u : 0u, mount_result); @@ -1101,13 +1134,23 @@ int KYTY_SYSV_ABI SaveDataGetMountInfo(const SaveDataMountPoint* mount_point, SaveDataMountInfo* info) { PRINT_NAME(); - EXIT_NOT_IMPLEMENTED(mount_point == nullptr); - EXIT_NOT_IMPLEMENTED(info == nullptr); - + if (mount_point == nullptr || info == nullptr || + std::memchr(mount_point->data, 0, sizeof(mount_point->data)) == nullptr) { + return SAVE_DATA_ERROR_PARAMETER; + } + Common::LockGuard lock(g_mount_mutex); + if (g_mount_slots.Find(mount_point->data) == SaveDataMountSlots::FULL) { + return SAVE_DATA_ERROR_NOT_MOUNTED; + } + uint64_t blocks = 0; + const int status = read_save_blocks( + LibKernel::FileSystem::GetRealFilename(mount_point->data), &blocks); + if (status != OK) { + return status; + } *info = {}; - - info->blocks = SAVE_DATA_BLOCKS_MAX; - info->free_blocks = SAVE_DATA_BLOCKS_MAX; + info->blocks = blocks; + info->free_blocks = blocks; return OK; } diff --git a/tests/SaveDataMemoryTests.cpp b/tests/SaveDataMemoryTests.cpp index 57880e1d..048853be 100644 --- a/tests/SaveDataMemoryTests.cpp +++ b/tests/SaveDataMemoryTests.cpp @@ -17,6 +17,7 @@ namespace { std::string g_test_title = "PPSA21564"; std::filesystem::path g_mounted_directory; +std::map g_test_mounts; } // namespace namespace Loader { @@ -33,10 +34,14 @@ Common::Time GetTime() { } // namespace Loader namespace Libs::LibKernel::FileSystem { -void Mount(const std::filesystem::path& directory, const std::string&) { +void Mount(const std::filesystem::path& directory, const std::string& point) { g_mounted_directory = directory; + g_test_mounts[point] = directory; +} +void Umount(const std::string& point) { g_test_mounts.erase(point); } +std::filesystem::path GetRealFilename(const std::string& point) { + return g_test_mounts.at(point); } -void Umount(const std::string&) {} } // namespace Libs::LibKernel::FileSystem namespace { @@ -337,6 +342,7 @@ void TestClassicSavePaths() { mount.user_id = 1; mount.dir_name = &name; mount.mount_mode = text == "save.1" ? 1 : 4; + mount.blocks = 48; SaveDataMountResult result {}; CHECK(SaveDataMount3(&mount, &result) == OK); CHECK(g_mounted_directory == fs::path("_SaveData") / "CLASSIC" / text); @@ -385,6 +391,70 @@ void TestClassicSavePaths() { CHECK(Search(1) == names); } +void TestSaveAllocations() { + Reset("CAPACITY"); + std::array names {DirName("Options"), DirName("Player")}; + const std::array allocations {48, 96}; + for (const auto blocks : {0u, 47u, 16385u}) { + struct SaveDataMount3 invalid {}; + invalid.user_id = 1; + invalid.dir_name = &names[0]; + invalid.mount_mode = 4; + invalid.blocks = blocks; + SaveDataMountResult result {}; + CHECK(SaveDataMount3(&invalid, &result) == SAVE_DATA_ERROR_PARAMETER); + CHECK(!fs::exists("_SaveData/CAPACITY/Options")); + } + for (size_t i = 0; i < names.size(); i++) { + struct SaveDataMount3 mount {}; + mount.user_id = 1; + mount.dir_name = &names[i]; + mount.mount_mode = 4; + mount.blocks = allocations[i]; + SaveDataMountResult result {}; + CHECK(SaveDataMount3(&mount, &result) == OK); + CHECK(result.mount_status == 1); + std::ofstream(g_mounted_directory / "USR-DATA") << "saved payload"; + CHECK(SaveDataUmount2(0, &result.mount_point) == OK); + SaveDataMountInfo info {}; + CHECK(SaveDataGetMountInfo(&result.mount_point, &info) == SAVE_DATA_ERROR_NOT_MOUNTED); + } + Reset("CAPACITY"); + std::array found {}; + std::array infos {}; + SaveDataDirNameSearchCond cond {}; + cond.user_id = 1; + SaveDataDirNameSearchResult search {}; + search.dir_names = found.data(); + search.dir_names_num = found.size(); + search.infos = infos.data(); + CHECK(SaveDataDirNameSearch(&cond, &search) == OK && search.set_num == 2); + for (size_t i = 0; i < names.size(); i++) { + CHECK(std::string(found[i].data) == names[i].data); + CHECK(infos[i].blocks == allocations[i] && infos[i].free_blocks == allocations[i]); + struct SaveDataMount3 mount {}; + mount.user_id = 1; + mount.dir_name = &names[i]; + mount.mount_mode = 34; + mount.blocks = 16384; + SaveDataMountResult result {}; + CHECK(SaveDataMount3(&mount, &result) == OK && result.mount_status == 0); + std::ofstream(g_mounted_directory / "USR-DATA", std::ios::app) << "more data"; + SaveDataMountInfo info {}; + CHECK(SaveDataGetMountInfo(&result.mount_point, &info) == OK); + CHECK(info.blocks == allocations[i] && info.free_blocks == allocations[i]); + CHECK(SaveDataUmount2(0, &result.mount_point) == OK); + } + CHECK(infos[0].blocks + infos[1].blocks == 144); + const fs::path metadata = "_SaveData/CAPACITY/Options/sce_sys/blocks.bin"; + fs::resize_file(metadata, 7); + CHECK(SaveDataDirNameSearch(&cond, &search) == SAVE_DATA_ERROR_BROKEN); + CHECK(fs::file_size(metadata) == 7); + CHECK(fs::remove(metadata)); + CHECK(SaveDataDirNameSearch(&cond, &search) == SAVE_DATA_ERROR_BROKEN); + CHECK(!fs::exists(metadata)); +} + void RunChild(const fs::path& executable, const char* mode) { #ifdef _WIN32 CHECK(_spawnl(_P_WAIT, executable.string().c_str(), executable.string().c_str(), mode, @@ -425,6 +495,7 @@ int main(int argc, char** argv) { TestIsolationAndSync(); TestFailedWritePreservesSave(); TestClassicSavePaths(); + TestSaveAllocations(); CHECK(SaveDataTerminate() == OK); fs::current_path(previous); fs::remove_all(temp); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 21905095..79a0428f 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -19624,7 +19624,7 @@ TestCase VectorMinMaxF16Ops() { O::S_ENDPGM}}; } -TestCase VectorCvtF16U16CapturedByteSource() { +TestCase VectorCvtF16U16ByteSource() { using O = ShaderOpcode; constexpr std::array inputs = {0xff123456u, 0x80123456u, 0x00123456u}; @@ -19633,15 +19633,14 @@ TestCase VectorCvtF16U16CapturedByteSource() { for (u32 i = 0; i < inputs.size(); i++) { AppendVMovU32(&code, 30, i * 4u); AppendBufferLoadDword(&code, 18, 30); - // Captured shader 0x15082e211725205f: unsigned byte3 -> FP16 word0, - // preserving word1 of v16. + // Convert unsigned byte3 to FP16 word0 while preserving word1 of v16. code.insert(code.end(), {0x7e20a0f9u, 0x00031412u}); AppendStoreVgpr(&code, 16, i); } AppendEnd(&code); TestCase test; - test.name = "VectorCvtF16U16CapturedByteSource"; + test.name = "VectorCvtF16U16ByteSource"; test.code = std::move(code); test.initial.assign(inputs.begin(), inputs.end()); test.expected = {0xa5a55bf8u, 0xa5a55800u, 0xa5a50000u}; @@ -27554,7 +27553,7 @@ std::vector MakeCases() { AddCase(CvtPkrtzF16F32SdwaAndOutputModifiers); AddCase(PackedMinMaxF16NanAndSignedZeroEdges); AddCase(VectorMinMaxF16Ops); - AddCase(VectorCvtF16U16CapturedByteSource); + AddCase(VectorCvtF16U16ByteSource); AddCase(VectorCvtU16F16Sdwa); AddCase(NativeAndSdwa16BitDestinationWrites); AddCase(VectorMinMaxMed3F16Ops); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 8ee408f8..cd03b93d 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -1753,7 +1753,7 @@ void TestNggVertexEntryState() { [](const auto &output) { return output.kind == IR::StageOutputKind::Position; }), - "NGG launch counts suppressed the captured vertex export"); + "NGG launch counts suppressed the vertex export"); Check(result.program.wave_size == wave_size, "NGG wave size was lost during translation"); CheckSpirvBinaryValidates(result.spirv);