From ae82fa5d39a00464b51b8755fe222b7af43c8ed3 Mon Sep 17 00:00:00 2001 From: SternXD Date: Sun, 10 May 2026 04:56:53 -0400 Subject: [PATCH 1/2] MSBuild: Fix compiling --- src/ui/ui.vcxproj | 2 ++ src/ui/ui.vcxproj.filters | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/ui/ui.vcxproj b/src/ui/ui.vcxproj index 451f845..35385fe 100644 --- a/src/ui/ui.vcxproj +++ b/src/ui/ui.vcxproj @@ -201,6 +201,7 @@ + @@ -220,6 +221,7 @@ + diff --git a/src/ui/ui.vcxproj.filters b/src/ui/ui.vcxproj.filters index 899f993..518a7f7 100644 --- a/src/ui/ui.vcxproj.filters +++ b/src/ui/ui.vcxproj.filters @@ -29,6 +29,9 @@ + + src + src @@ -82,6 +85,9 @@ + + headers + headers From 4687112a4bd47f6be4c838f15716d3877cc6decc Mon Sep 17 00:00:00 2001 From: SternXD Date: Sun, 10 May 2026 06:58:53 -0400 Subject: [PATCH 2/2] Formats: Fix superblock, FAT/ECC, and PSU metadata for saves --- src/core/formats/ps2mc.cpp | 196 ++++++++++++++++++++++++++------- src/core/formats/ps2mc_ecc.cpp | 28 ++--- src/core/formats/ps2save.cpp | 28 ++++- 3 files changed, 192 insertions(+), 60 deletions(-) diff --git a/src/core/formats/ps2mc.cpp b/src/core/formats/ps2mc.cpp index f0eed66..6dc5655 100644 --- a/src/core/formats/ps2mc.cpp +++ b/src/core/formats/ps2mc.cpp @@ -94,6 +94,7 @@ class PS2MemoryCard::Impl PS2McDirEntry find_entry(const std::string& path, uint32_t& parent_cluster); std::vector read_dirents(uint32_t dir_cluster); void write_dirents(uint32_t dir_cluster, const std::vector& entries); + void syncParentDirectoryEntryLength(uint32_t child_dir_cluster); uint32_t allocate_cluster(); std::vector allocate_clusters(uint32_t count); void free_cluster_chain(uint32_t start_cluster); @@ -274,7 +275,7 @@ std::vector PS2MemoryCard::Impl::read_fat_cluster(uint32_t fat_cluster void PS2MemoryCard::Impl::read_fat_from_card() { fat.clear(); - fat.resize(allocatable_cluster_end, PS2MC_FAT_CHAIN_END); + fat.resize(allocatable_cluster_end, PS2MC_FAT_CHAIN_END_UNALLOC); uint32_t fat_entry = 0; @@ -349,7 +350,7 @@ void PS2MemoryCard::Impl::read_superblock() for (int i = 0; i < 32; ++i) { - indirect_fat_cluster_list[i] = readLE(sb_page, 76 + i * 4); + indirect_fat_cluster_list[i] = readLE(sb_page, 80 + i * 4); } if (sb_page.size() > 0x44 + 4) @@ -591,7 +592,7 @@ void PS2MemoryCard::Impl::write_dirents(uint32_t dir_cluster, const std::vector< if (next >= fat.size() || next == PS2MC_FAT_CHAIN_END_UNALLOC || next == PS2MC_FAT_CHAIN_END) { uint32_t new_cluster = allocate_cluster(); - fat[current_cluster] = (fat[current_cluster] & ~PS2MC_FAT_CLUSTER_MASK) | new_cluster; + fat[current_cluster] = (fat[current_cluster] & ~PS2MC_FAT_CLUSTER_MASK) | (new_cluster | PS2MC_FAT_ALLOCATED_BIT); fat[new_cluster] = PS2MC_FAT_CHAIN_END; current_cluster = new_cluster; } @@ -607,6 +608,42 @@ void PS2MemoryCard::Impl::write_dirents(uint32_t dir_cluster, const std::vector< } } +void PS2MemoryCard::Impl::syncParentDirectoryEntryLength(uint32_t child_dir_cluster) +{ + auto child = read_dirents(child_dir_cluster); + if (child.empty() || child[0].name != ".") + { + return; + } + const uint32_t newLen = child[0].length; + const uint32_t ancestor = child[0].cluster; + const uint32_t slot = child[0].dirEntry; + + auto ancestor_entries = read_dirents(ancestor); + bool updated = false; + if (slot < ancestor_entries.size() && (ancestor_entries[slot].mode & DF_DIR) && ancestor_entries[slot].cluster == child_dir_cluster) + { + ancestor_entries[slot].length = newLen; + updated = true; + } + else + { + for (auto& e : ancestor_entries) + { + if ((e.mode & DF_DIR) && (e.mode & DF_EXISTS) && e.cluster == child_dir_cluster) + { + e.length = newLen; + updated = true; + break; + } + } + } + if (updated) + { + write_dirents(ancestor, ancestor_entries); + } +} + uint32_t PS2MemoryCard::Impl::allocate_cluster() { for (uint32_t i = 0; i < fat.size(); ++i) @@ -634,7 +671,7 @@ std::vector PS2MemoryCard::Impl::allocate_clusters(uint32_t count) if (i > 0) { - fat[clusters[i - 1]] = cluster; + fat[clusters[i - 1]] = cluster | PS2MC_FAT_ALLOCATED_BIT; } } @@ -650,11 +687,19 @@ void PS2MemoryCard::Impl::free_cluster_chain(uint32_t start_cluster) { uint32_t current = start_cluster; - while (current != PS2MC_FAT_CHAIN_END && current < fat.size()) + while (current < fat.size()) { - uint32_t next = fat[current] & PS2MC_FAT_CLUSTER_MASK; + const uint32_t raw = fat[current]; + const uint32_t masked = raw & PS2MC_FAT_CLUSTER_MASK; + fat[current] = PS2MC_FAT_CHAIN_END_UNALLOC; - current = next; + + if (raw == PS2MC_FAT_CHAIN_END || masked >= fat.size() || masked == PS2MC_FAT_CHAIN_END_UNALLOC) + { + break; + } + + current = masked; } modified = true; @@ -770,9 +815,10 @@ void PS2MemoryCard::Impl::calculate_fat_layout(uint32_t& first_ifc, uint32_t& in uint32_t fat_clusters = (allocatable_clusters_est + epc - 1) / epc; uint32_t indirect_fat_clusters = (fat_clusters + epc - 1) / epc; if (indirect_fat_clusters > 32) + { indirect_fat_clusters = 32; - - fat_clusters = indirect_fat_clusters * epc; + fat_clusters = indirect_fat_clusters * epc; + } allocatable_cluster_offset = first_ifc + indirect_fat_clusters + fat_clusters; @@ -839,6 +885,19 @@ void PS2MemoryCard::Impl::write_superblock(uint32_t first_ifc, uint32_t indirect sb[337] = 0x2B; write_page(0, sb); + + const uint64_t backup2_offset = static_cast(good_block2) * pages_per_erase_block * raw_page_size; + std::vector erased_page(raw_page_size, 0xFF); + file.seekp(static_cast(backup2_offset)); + for (uint32_t i = 0; i < pages_per_erase_block; ++i) + { + file.write(reinterpret_cast(erased_page.data()), + static_cast(erased_page.size())); + if (!file) + { + throw PS2McIOError("Failed to initialize backup erase block"); + } + } } void PS2MemoryCard::Impl::init_indirect_fat_clusters(uint32_t first_ifc, uint32_t indirect_fat_clusters, uint32_t epc) @@ -875,7 +934,7 @@ void PS2MemoryCard::Impl::init_root_directory() std::vector rootEntries; - PS2McDirEntry dot; + PS2McDirEntry dot{}; dot.mode = DF_DIR | DF_EXISTS | DF_RWX | DF_0400; dot.name = "."; dot.cluster = 0; @@ -884,7 +943,7 @@ void PS2MemoryCard::Impl::init_root_directory() dot.modified = dot.created; rootEntries.push_back(dot); - PS2McDirEntry dotdot; + PS2McDirEntry dotdot{}; dotdot.mode = DF_DIR | DF_EXISTS | DF_WRITE | DF_EXECUTE | DF_0400 | DF_HIDDEN; dotdot.name = ".."; dotdot.cluster = 0; @@ -1272,7 +1331,8 @@ void PS2MemoryCard::makeDir(const std::string& path) { try { - auto entry = pImpl->find_entry(path, pImpl->rootdir_fat_cluster); + uint32_t unusedParent = 0; + auto entry = pImpl->find_entry(path, unusedParent); if (entry.mode & DF_EXISTS) { return; @@ -1299,39 +1359,48 @@ void PS2MemoryCard::makeDir(const std::string& path) uint32_t dir_cluster = pImpl->allocate_cluster(); + auto parent_entries = pImpl->read_dirents(parent_cluster); + const uint32_t slot_for_new_dir = static_cast(parent_entries.size()); + + const auto now = timeToTod(time(nullptr)); + PS2McDirEntry new_dir_entry = {}; - new_dir_entry.mode = DF_DIR | DF_EXISTS | DF_RWX; + new_dir_entry.mode = DF_DIR | DF_EXISTS | DF_RWX | DF_0400; new_dir_entry.name = dir_name; new_dir_entry.cluster = dir_cluster; - new_dir_entry.length = 0; - new_dir_entry.created = timeToTod(time(nullptr)); - new_dir_entry.modified = new_dir_entry.created; + new_dir_entry.length = 2; + new_dir_entry.created = now; + new_dir_entry.modified = now; std::vector new_dir_entries; PS2McDirEntry dot_entry = {}; - dot_entry.mode = DF_DIR | DF_EXISTS | DF_RWX; + dot_entry.mode = DF_DIR | DF_EXISTS | DF_RWX | DF_0400; dot_entry.name = "."; - dot_entry.cluster = dir_cluster; - dot_entry.length = 0; - dot_entry.created = new_dir_entry.created; - dot_entry.modified = new_dir_entry.modified; - dot_entry.dirEntry = 0; + dot_entry.cluster = parent_cluster; + dot_entry.length = 2; + dot_entry.created = now; + dot_entry.modified = now; + dot_entry.dirEntry = slot_for_new_dir; new_dir_entries.push_back(dot_entry); PS2McDirEntry dotdot_entry = {}; - dotdot_entry.mode = DF_DIR | DF_EXISTS | DF_RWX; + dotdot_entry.mode = DF_DIR | DF_EXISTS | DF_RWX | DF_0400; dotdot_entry.name = ".."; - dotdot_entry.cluster = parent_cluster; + dotdot_entry.cluster = 0; dotdot_entry.length = 0; - dotdot_entry.created = new_dir_entry.created; - dotdot_entry.modified = new_dir_entry.modified; + dotdot_entry.created = now; + dotdot_entry.modified = now; dotdot_entry.dirEntry = 0; new_dir_entries.push_back(dotdot_entry); pImpl->write_dirents(dir_cluster, new_dir_entries); - auto parent_entries = pImpl->read_dirents(parent_cluster); + parent_entries.push_back(new_dir_entry); + if (!parent_entries.empty() && (parent_entries[0].mode & DF_DIR)) + { + parent_entries[0].length = static_cast(parent_entries.size()); + } pImpl->write_dirents(parent_cluster, parent_entries); @@ -1360,7 +1429,8 @@ void PS2MemoryCard::writeFile(const std::string& path, const std::vectorfind_entry(path, pImpl->rootdir_fat_cluster); + uint32_t unusedParent = 0; + auto entry = pImpl->find_entry(path, unusedParent); if (entry.mode & DF_EXISTS) { throw PS2McIOError("File already exists: " + path); @@ -1387,7 +1457,11 @@ void PS2MemoryCard::writeFile(const std::string& path, const std::vector(data.size()) + pImpl->cluster_size - 1) / pImpl->cluster_size; - auto file_clusters = pImpl->allocate_clusters(clusters_needed); + std::vector file_clusters; + if (clusters_needed > 0) + { + file_clusters = pImpl->allocate_clusters(clusters_needed); + } uint32_t offset = 0; for (uint32_t cluster : file_clusters) @@ -1408,16 +1482,21 @@ void PS2MemoryCard::writeFile(const std::string& path, const std::vector(data.size()); file_entry.created = timeToTod(time(nullptr)); file_entry.modified = file_entry.created; auto parent_entries = pImpl->read_dirents(parent_cluster); parent_entries.push_back(file_entry); + if (!parent_entries.empty() && (parent_entries[0].mode & DF_DIR) && parent_entries[0].name == ".") + { + parent_entries[0].length = static_cast(parent_entries.size()); + } pImpl->write_dirents(parent_cluster, parent_entries); + pImpl->syncParentDirectoryEntryLength(parent_cluster); pImpl->write_fat_to_card(); @@ -1540,7 +1619,7 @@ bool PS2MemoryCard::importSaveFile(PS2SaveFile& save, bool ignoreExisting, const try { - uint32_t dummy; + uint32_t dummy = 0; pImpl->find_entry(savePath, dummy); if (!ignoreExisting) @@ -1563,27 +1642,70 @@ bool PS2MemoryCard::importSaveFile(PS2SaveFile& save, bool ignoreExisting, const writeFile(filePath, entry.data); - uint32_t parent_cluster = 0; - auto currentEntry = pImpl->find_entry(filePath, parent_cluster); - auto parentEntries = pImpl->read_dirents(parent_cluster); + uint32_t parent_cluster_inner = 0; + auto currentEntry = pImpl->find_entry(filePath, parent_cluster_inner); + auto parentEntries = pImpl->read_dirents(parent_cluster_inner); bool updated = false; for (auto& pe : parentEntries) { if (pe.name == currentEntry.name) { + const uint16_t psuMode = entry.dirEntry.mode; + constexpr uint16_t typeMask = DF_FILE | DF_DIR | DF_EXISTS; + pe.mode = static_cast((psuMode & ~typeMask) | (pe.mode & typeMask)); + if ((pe.mode & DF_EXISTS) && (pe.mode & (DF_FILE | DF_DIR)) && !(pe.mode & DF_PSX)) + { + pe.mode |= DF_0400; + } + pe.unused = entry.dirEntry.unused; pe.created = entry.dirEntry.created; pe.modified = entry.dirEntry.modified; - pe.mode = (entry.dirEntry.mode & ~DF_DIR) | DF_EXISTS | DF_FILE; + pe.attr = entry.dirEntry.attr; updated = true; break; } } if (updated) { - pImpl->write_dirents(parent_cluster, parentEntries); + pImpl->write_dirents(parent_cluster_inner, parentEntries); + pImpl->syncParentDirectoryEntryLength(parent_cluster_inner); } } + { + uint32_t saveParentCluster = 0; + (void)pImpl->find_entry(savePath, saveParentCluster); + + auto parentRows = pImpl->read_dirents(saveParentCluster); + const auto& hdr = entries[0].dirEntry; + constexpr uint16_t typeMask = DF_FILE | DF_DIR | DF_EXISTS; + for (auto& row : parentRows) + { + if (row.name == saveDirName && (row.mode & DF_DIR)) + { + row.mode = static_cast((hdr.mode & ~typeMask) | (row.mode & typeMask)); + if ((row.mode & DF_EXISTS) && (row.mode & DF_DIR) && !(row.mode & DF_PSX)) + { + row.mode |= DF_0400; + } + row.unused = hdr.unused; + row.created = hdr.created; + row.modified = hdr.modified; + row.attr = hdr.attr; + break; + } + } + pImpl->write_dirents(saveParentCluster, parentRows); + } + + uint32_t rootParent = pImpl->rootdir_fat_cluster; + auto finalRoot = pImpl->read_dirents(rootParent); + if (!finalRoot.empty() && (finalRoot[0].mode & DF_DIR)) + { + finalRoot[0].length = static_cast(finalRoot.size()); + pImpl->write_dirents(rootParent, finalRoot); + } + pImpl->write_fat_to_card(); pImpl->file.flush(); diff --git a/src/core/formats/ps2mc_ecc.cpp b/src/core/formats/ps2mc_ecc.cpp index 4d15607..72e7262 100644 --- a/src/core/formats/ps2mc_ecc.cpp +++ b/src/core/formats/ps2mc_ecc.cpp @@ -94,31 +94,25 @@ int eccCheck(std::vector& data, std::array& ecc) uint8_t lp0_diff = (computed[1] ^ ecc[1]) & 0x7F; uint8_t lp1_diff = (computed[2] ^ ecc[2]) & 0x7F; - if (cp_diff == 0 && lp0_diff == 0 && lp1_diff == 0) - { - return ECC_CHECK_OK; - } + uint8_t lp_comp = lp0_diff ^ lp1_diff; + uint8_t cp_comp = static_cast((cp_diff >> 4) ^ (cp_diff & 0x07)); - if (popcount(cp_diff) == 1 && popcount(lp0_diff) == 1 && popcount(lp1_diff) == 1) + if (lp_comp == 0x7F && cp_comp == 0x07) { - uint8_t byte_pos = lp0_diff ^ lp1_diff; - if (byte_pos < data.size()) + if (lp1_diff < data.size()) { - uint8_t bit_pos = 0; - for (int i = 0; i < 7; i++) - { - if (cp_diff & (1 << i)) - { - bit_pos |= (1 << (i < 3 ? i : i - 4)); - } - } - data[byte_pos] ^= (1 << bit_pos); - + data[lp1_diff] = static_cast(data[lp1_diff] ^ (1u << (cp_diff >> 4))); ecc = eccCalculate(data); return ECC_CHECK_CORRECTED; } } + if ((cp_diff == 0 && lp0_diff == 0 && lp1_diff == 0) || (popcount(lp_comp) + popcount(cp_comp) == 1)) + { + ecc = computed; + return ECC_CHECK_CORRECTED; + } + return ECC_CHECK_FAILED; } diff --git a/src/core/formats/ps2save.cpp b/src/core/formats/ps2save.cpp index a7f77f0..94d77f3 100644 --- a/src/core/formats/ps2save.cpp +++ b/src/core/formats/ps2save.cpp @@ -772,7 +772,11 @@ void PS2SaveFile::Impl::loadEms(std::ifstream& file) } uint32_t fileCount = dirent.length - 2; entries.clear(); - entries.reserve(fileCount); + entries.reserve(static_cast(fileCount) + 1); + PS2SaveEntry dirSlot; + dirSlot.dirEntry = dirent; + dirSlot.data.clear(); + entries.push_back(std::move(dirSlot)); for (uint32_t i = 0; i < fileCount; ++i) { auto entRaw = readFixed(file, PS2MC_DIRENT_LENGTH); @@ -795,12 +799,23 @@ void PS2SaveFile::Impl::loadEms(std::ifstream& file) void PS2SaveFile::Impl::saveEms(std::ofstream& file) { + const bool hasDirHeader = !entries.empty() && (entries[0].dirEntry.mode & DF_DIR); + const size_t numFiles = hasDirHeader ? entries.size() - 1 : entries.size(); + PS2McDirEntry root{}; + if (hasDirHeader) + { + root = entries[0].dirEntry; + } + else + { + root.name = entries.empty() ? "SAVE" : entries[0].dirEntry.name; + root.created = timeToTod(time(nullptr)); + root.modified = root.created; + } root.mode = DF_RWX | DF_DIR | DF_0400 | DF_EXISTS; - root.length = static_cast(entries.size() + 2); - root.name = entries.empty() ? "SAVE" : entries[0].dirEntry.name; - root.created = timeToTod(time(nullptr)); - root.modified = root.created; + root.length = static_cast(numFiles + 2); + file.write(reinterpret_cast(packDirEntry(root).data()), PS2MC_DIRENT_LENGTH); PS2McDirEntry dot = root; @@ -810,8 +825,9 @@ void PS2SaveFile::Impl::saveEms(std::ofstream& file) dotdot.name = ".."; file.write(reinterpret_cast(packDirEntry(dotdot).data()), PS2MC_DIRENT_LENGTH); - for (const auto& e : entries) + for (size_t idx = hasDirHeader ? 1 : 0; idx < entries.size(); ++idx) { + const auto& e = entries[idx]; auto ent = e.dirEntry; ent.mode = DF_RWX | DF_FILE | DF_0400 | DF_EXISTS; auto packed = packDirEntry(ent);