From fee1b985c0d5cd5ff6e5da7fa9ad4739174ae697 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Wed, 25 Jan 2023 10:38:04 +0100 Subject: [PATCH] feat: Added better error messages for generating and importing ips patches --- lib/libimhex/include/hex/helpers/patches.hpp | 17 ++++-- lib/libimhex/source/helpers/patches.cpp | 40 +++++++------ plugins/builtin/romfs/lang/en_US.json | 7 ++- .../source/content/main_menu_items.cpp | 59 +++++++++++++++---- 4 files changed, 90 insertions(+), 33 deletions(-) diff --git a/lib/libimhex/include/hex/helpers/patches.hpp b/lib/libimhex/include/hex/helpers/patches.hpp index fbeb6880a..a42f75b76 100644 --- a/lib/libimhex/include/hex/helpers/patches.hpp +++ b/lib/libimhex/include/hex/helpers/patches.hpp @@ -4,14 +4,23 @@ #include #include +#include namespace hex { using Patches = std::map; - std::vector generateIPSPatch(const Patches &patches); - std::vector generateIPS32Patch(const Patches &patches); + enum class IPSError { + AddressOutOfRange, + PatchTooLarge, + InvalidPatchHeader, + InvalidPatchFormat, + MissingEOF + }; - Patches loadIPSPatch(const std::vector &ipsPatch); - Patches loadIPS32Patch(const std::vector &ipsPatch); + std::expected, IPSError> generateIPSPatch(const Patches &patches); + std::expected, IPSError> generateIPS32Patch(const Patches &patches); + + std::expected loadIPSPatch(const std::vector &ipsPatch); + std::expected loadIPS32Patch(const std::vector &ipsPatch); } \ No newline at end of file diff --git a/lib/libimhex/source/helpers/patches.cpp b/lib/libimhex/source/helpers/patches.cpp index 4323bf453..af89169bf 100644 --- a/lib/libimhex/source/helpers/patches.cpp +++ b/lib/libimhex/source/helpers/patches.cpp @@ -18,7 +18,7 @@ namespace hex { std::memcpy((&buffer.back() - sizeof(T)) + 1, &bytes, sizeof(T)); } - std::vector generateIPSPatch(const Patches &patches) { + std::expected, IPSError> generateIPSPatch(const Patches &patches) { std::vector result; pushStringBack(result, "PATCH"); @@ -42,8 +42,10 @@ namespace hex { } else { bytes.push_back(values[i]); - if (bytes.size() > 0xFFFF || startAddress > 0xFF'FFFF) - return {}; + if (bytes.size() > 0xFFFF) + return std::unexpected(IPSError::PatchTooLarge); + if (startAddress > 0xFFFF'FFFF) + return std::unexpected(IPSError::AddressOutOfRange); u32 address = startAddress.value(); auto addressBytes = reinterpret_cast(&address); @@ -66,7 +68,7 @@ namespace hex { return result; } - std::vector generateIPS32Patch(const Patches &patches) { + std::expected, IPSError> generateIPS32Patch(const Patches &patches) { std::vector result; pushStringBack(result, "IPS32"); @@ -90,8 +92,10 @@ namespace hex { } else { bytes.push_back(values[i]); - if (bytes.size() > 0xFFFF || startAddress > 0xFFFF'FFFF) - return {}; + if (bytes.size() > 0xFFFF) + return std::unexpected(IPSError::PatchTooLarge); + if (startAddress > 0xFFFF'FFFF) + return std::unexpected(IPSError::AddressOutOfRange); u32 address = startAddress.value(); auto addressBytes = reinterpret_cast(&address); @@ -115,12 +119,12 @@ namespace hex { return result; } - Patches loadIPSPatch(const std::vector &ipsPatch) { + std::expected loadIPSPatch(const std::vector &ipsPatch) { if (ipsPatch.size() < (5 + 3)) - return {}; + return std::unexpected(IPSError::InvalidPatchHeader); if (std::memcmp(ipsPatch.data(), "PATCH", 5) != 0) - return {}; + return std::unexpected(IPSError::InvalidPatchHeader); Patches result; bool foundEOF = false; @@ -135,7 +139,7 @@ namespace hex { // Handle normal record if (size > 0x0000) { if (ipsOffset + size > ipsPatch.size() - 3) - return {}; + return std::unexpected(IPSError::InvalidPatchFormat); for (u16 i = 0; i < size; i++) result[offset + i] = ipsPatch[ipsOffset + i]; @@ -144,7 +148,7 @@ namespace hex { // Handle RLE record else { if (ipsOffset + 3 > ipsPatch.size() - 3) - return {}; + return std::unexpected(IPSError::InvalidPatchFormat); u16 rleSize = ipsPatch[ipsOffset + 0] | (ipsPatch[ipsOffset + 1] << 8); @@ -163,15 +167,15 @@ namespace hex { if (foundEOF) return result; else - return {}; + return std::unexpected(IPSError::MissingEOF); } - Patches loadIPS32Patch(const std::vector &ipsPatch) { + std::expected loadIPS32Patch(const std::vector &ipsPatch) { if (ipsPatch.size() < (5 + 4)) - return {}; + return std::unexpected(IPSError::InvalidPatchHeader); if (std::memcmp(ipsPatch.data(), "IPS32", 5) != 0) - return {}; + return std::unexpected(IPSError::InvalidPatchHeader); Patches result; bool foundEEOF = false; @@ -186,7 +190,7 @@ namespace hex { // Handle normal record if (size > 0x0000) { if (ipsOffset + size > ipsPatch.size() - 3) - return {}; + return std::unexpected(IPSError::InvalidPatchFormat); for (u16 i = 0; i < size; i++) result[offset + i] = ipsPatch[ipsOffset + i]; @@ -195,7 +199,7 @@ namespace hex { // Handle RLE record else { if (ipsOffset + 3 > ipsPatch.size() - 3) - return {}; + return std::unexpected(IPSError::InvalidPatchFormat); u16 rleSize = ipsPatch[ipsOffset + 0] | (ipsPatch[ipsOffset + 1] << 8); @@ -214,7 +218,7 @@ namespace hex { if (foundEEOF) return result; else - return {}; + return std::unexpected(IPSError::MissingEOF); } } \ No newline at end of file diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 6336d8b98..7848e6cd7 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -136,7 +136,12 @@ "hex.builtin.menu.file.close": "Close", "hex.builtin.menu.file.create_file": "New File...", "hex.builtin.menu.file.export": "Export...", - "hex.builtin.menu.file.export.base64.popup.export_error": "File is not in a valid Base64 format!", + "hex.builtin.menu.file.export.ips.popup.export_error": "Failed to create new IPS file!", + "hex.builtin.menu.file.export.ips.popup.invalid_patch_header_error": "Invalid IPS patch header!", + "hex.builtin.menu.file.export.ips.popup.address_out_of_range_error": "A patch tried to patch an address that is out of range!", + "hex.builtin.menu.file.export.ips.popup.patch_too_large_error": "A patch was larger than the maximally allowed size!", + "hex.builtin.menu.file.export.ips.popup.invalid_patch_format_error": "Invalid IPS patch format!", + "hex.builtin.menu.file.export.ips.popup.missing_eof_error": "Missing IPS EOF record!", "hex.builtin.menu.file.export.ips": "IPS Patch", "hex.builtin.menu.file.export.ips32": "IPS32 Patch", "hex.builtin.menu.file.export.popup.create": "Cannot export data. Failed to create file!", diff --git a/plugins/builtin/source/content/main_menu_items.cpp b/plugins/builtin/source/content/main_menu_items.cpp index 6354538d5..bbd063083 100644 --- a/plugins/builtin/source/content/main_menu_items.cpp +++ b/plugins/builtin/source/content/main_menu_items.cpp @@ -17,6 +17,26 @@ namespace hex::plugin::builtin { static bool g_demoWindowOpen = false; + void handleIPSError(IPSError error) { + switch (error) { + case IPSError::InvalidPatchHeader: + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.invalid_patch_header_error"_lang); + break; + case IPSError::AddressOutOfRange: + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.address_out_of_range_error"_lang); + break; + case IPSError::PatchTooLarge: + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.patch_too_large_error"_lang); + break; + case IPSError::InvalidPatchFormat: + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.invalid_patch_format_error"_lang); + break; + case IPSError::MissingEOF: + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.missing_eof_error"_lang); + break; + } + } + static void createFileMenu() { ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.file", 1000); @@ -137,13 +157,17 @@ namespace hex::plugin::builtin { TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) { auto patchData = fs::File(path, fs::File::Mode::Read).readBytes(); auto patch = hex::loadIPSPatch(patchData); + if (!patch.has_value()) { + handleIPSError(patch.error()); + return; + } - task.setMaxValue(patch.size()); + task.setMaxValue(patch->size()); auto provider = ImHexApi::Provider::get(); u64 progress = 0; - for (auto &[address, value] : patch) { + for (auto &[address, value] : *patch) { provider->addPatch(address, &value, 1); progress++; task.update(progress); @@ -158,14 +182,18 @@ namespace hex::plugin::builtin { fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) { TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) { auto patchData = fs::File(path, fs::File::Mode::Read).readBytes(); - auto patch = hex::loadIPS32Patch(patchData); + auto patch = hex::loadIPS32Patch(patchData); + if (!patch.has_value()) { + handleIPSError(patch.error()); + return; + } - task.setMaxValue(patch.size()); + task.setMaxValue(patch->size()); auto provider = ImHexApi::Provider::get(); u64 progress = 0; - for (auto &[address, value] : patch) { + for (auto &[address, value] : *patch) { provider->addPatch(address, &value, 1); progress++; task.update(progress); @@ -184,6 +212,8 @@ namespace hex::plugin::builtin { if (ImGui::BeginMenu("hex.builtin.menu.file.export"_lang, providerValid && provider->isWritable())) { if (ImGui::MenuItem("hex.builtin.menu.file.export.ips"_lang, nullptr, false)) { Patches patches = provider->getPatches(); + + // Make sure there's no patch at address 0x00454F46 because that would cause the patch to contain the sequence "EOF" which signals the end of the patch if (!patches.contains(0x00454F45) && patches.contains(0x00454F46)) { u8 value = 0; provider->read(0x00454F45, &value, sizeof(u8)); @@ -197,11 +227,15 @@ namespace hex::plugin::builtin { fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) { auto file = fs::File(path, fs::File::Mode::Create); if (!file.isValid()) { - View::showErrorPopup("hex.builtin.menu.file.export.base64.popup.export_error"_lang); + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.export_error"_lang); return; } - file.write(data); + if (data.has_value()) + file.write(data.value()); + else { + handleIPSError(data.error()); + } }); }); }); @@ -209,7 +243,9 @@ namespace hex::plugin::builtin { if (ImGui::MenuItem("hex.builtin.menu.file.export.ips32"_lang, nullptr, false)) { Patches patches = provider->getPatches(); - if (!patches.contains(0x00454F45) && patches.contains(0x45454F46)) { + + // Make sure there's no patch at address 0x45454F46 because that would cause the patch to contain the sequence "*EOF" which signals the end of the patch + if (!patches.contains(0x45454F45) && patches.contains(0x45454F46)) { u8 value = 0; provider->read(0x45454F45, &value, sizeof(u8)); patches[0x45454F45] = value; @@ -222,11 +258,14 @@ namespace hex::plugin::builtin { fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) { auto file = fs::File(path, fs::File::Mode::Create); if (!file.isValid()) { - View::showErrorPopup("hex.builtin.menu.file.export.base64.popup.export_error"_lang); + View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.export_error"_lang); return; } - file.write(data); + if (data.has_value()) + file.write(data.value()); + else + handleIPSError(data.error()); }); }); });