From 8c5bae544a5db95983bec24de119307eaf82646906a268510fd97beb14f505e3 Mon Sep 17 00:00:00 2001 From: ACh Sulfate Date: Fri, 17 May 2024 21:40:15 +0800 Subject: [PATCH] initial commit --- .gitignore | 26 ++++ CMakeLists.txt | 26 ++++ nand_part_table_cvt.cc | 328 +++++++++++++++++++++++++++++++++++++++++ os_utils.cc | 149 +++++++++++++++++++ os_utils.h | 21 +++ utf_convert.cc | 133 +++++++++++++++++ utf_convert.h | 37 +++++ 7 files changed, 720 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 nand_part_table_cvt.cc create mode 100644 os_utils.cc create mode 100644 os_utils.h create mode 100644 utf_convert.cc create mode 100644 utf_convert.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd72186 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +.vscode/ +.vs/ + + +build/ +cmake-build-*/ +.cmake/ +CMakeFiles/ +CMakeScripts/ +CMakeCache.txt +cmake_install.cmake +Testing/ +.ninja* +build.ninja + +Makefile + +.idea/ + +*.o +*.a +*.so +*.dylib +*.dll +*.exe +*.out diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..261cbeb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.28) +project(NandPartitionTableConvert) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_C_STANDARD 11) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Werror=delete-non-virtual-dtor -Werror=return-type -Werror=non-virtual-dtor -Wno-invalid-offsetof") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -Werror=return-type") + +add_executable(NandPartitionTableConvert + nand_part_table_cvt.cc + os_utils.cc + utf_convert.cc +) + +add_subdirectory(fmt fmt) +target_compile_definitions(fmt-header-only INTERFACE FMT_STATIC_THOUSANDS_SEPARATOR=1) + +set_target_properties(NandPartitionTableConvert PROPERTIES + CXX_EXTENSIONS OFF + POSITION_INDEPENDENT_CODE ON +) + +target_link_options(NandPartitionTableConvert PRIVATE "-static" "-static-pie" "-static-libgcc" "-static-libstdc++") + +target_link_libraries(NandPartitionTableConvert fmt::fmt-header-only) diff --git a/nand_part_table_cvt.cc b/nand_part_table_cvt.cc new file mode 100644 index 0000000..8cd3913 --- /dev/null +++ b/nand_part_table_cvt.cc @@ -0,0 +1,328 @@ +#include +#include + +#include + +#include "os_utils.h" +#include "utf_convert.h" + +constexpr uint32_t kInvalidPartition = 0xFFFFFFFFu; + +struct LeapPartitionItem { + uint32_t fixed = 1; + uint32_t start = 0; + uint32_t end = 0; + uint32_t size = 0; +}; + +struct MinatoPartitionItem { + uint32_t start = 0; + uint32_t end = 0; + uint32_t size = 0; + uint32_t flags = 0; +}; + +struct XeltekPartitionItem { + uint32_t start = 0; + uint32_t end = 0; + uint32_t size = 0; + uint32_t unknown = kInvalidPartition; +}; + + +enum class PartitionType { + kNone = 0, + kLeap, + kMinato, + kXeltek +}; + +static_assert(sizeof(MinatoPartitionItem) == 16 && + sizeof(XeltekPartitionItem) == 16 && + sizeof(LeapPartitionItem) == 16); + +struct SimplePartitionItem { + uint32_t start = 0; + uint32_t end = 0; + uint32_t size = 0; +}; + +PartitionType ReadPartitionTable(const std::vector& data, std::vector& out) { + if (data.size() % 16 != 0) { + fmt::print("Invalid partition table size\n"); + return PartitionType::kNone; + } + if (data.empty()) { + fmt::print("Empty partition table\n"); + return PartitionType::kNone; + } + out.clear(); + out.reserve(data.size() / 16); + bool isLeap = false; + const std::array kLeapHeader{"GROUP DEFINE2\0\0"}; + if (memcmp(data.data(), kLeapHeader.data(), 16) == 0) { + isLeap = true; + } + if (isLeap) { + const auto* items = reinterpret_cast(data.data()); + // start from 1 + for (size_t i = 1; i < data.size() / 16; i++) { + const auto& item = items[i]; + if (item.start == kInvalidPartition) { + break; + } + if (item.fixed != 1) { + fmt::print("Invalid partition table, got fixed value {}\n", item.fixed); + return PartitionType::kNone; + } + SimplePartitionItem simpleItem; + simpleItem.start = item.start; + simpleItem.end = item.end; + simpleItem.size = item.size; + out.push_back(simpleItem); + } + return PartitionType::kLeap; + } else { + struct UnknownPartitionItem { + uint32_t start = 0; + uint32_t end = 0; + uint32_t size = 0; + uint32_t unknown = 0; + }; + PartitionType type = PartitionType::kNone; + const auto* items = reinterpret_cast(data.data()); + if (items[0].unknown == 0 || items[0].unknown == 1) { + type = PartitionType::kMinato; + } else if (items[0].unknown == kInvalidPartition) { + type = PartitionType::kXeltek; + } else { + fmt::print("Invalid partition table, unknown value {}\n", items[0].unknown); + return PartitionType::kNone; + } + for (size_t i = 0; i < data.size() / 16; i++) { + const auto& item = items[i]; + if (item.start == kInvalidPartition) { + break; + } + // check unknown + if (item.unknown != kInvalidPartition && item.unknown != 0 && item.unknown != 1) { + fmt::print("Invalid partition table, got unknown value {}\n", item.unknown); + return PartitionType::kNone; + } + SimplePartitionItem simpleItem; + simpleItem.start = item.start; + simpleItem.end = item.end; + simpleItem.size = item.size; + out.push_back(simpleItem); + } + return type; + } +} + +std::vector GenerateLeapPartitionTable(const std::vector& items) { + std::vector data; + data.resize((items.size() + 2) * 16); + std::array header{"GROUP DEFINE2\0\0"}; + memcpy(data.data(), header.data(), 16); + auto* outItems = reinterpret_cast(data.data()); + for (size_t i = 0; i < items.size(); i++) { + const auto& item = items[i]; + outItems[i + 1].fixed = 1; + outItems[i + 1].start = item.start; + outItems[i + 1].end = item.end; + outItems[i + 1].size = item.size; + } + // add end + outItems[items.size() + 1].fixed = kInvalidPartition; + outItems[items.size() + 1].start = kInvalidPartition; + outItems[items.size() + 1].end = kInvalidPartition; + outItems[items.size() + 1].size = kInvalidPartition; + return data; +} + +std::vector GenerateXeltekMinatoPartitionTable(const std::vector& items, + PartitionType type) { + std::vector data; + data.resize(items.size() * 16); + auto* outItems = reinterpret_cast(data.data()); + for (size_t i = 0; i < items.size(); i++) { + const auto& item = items[i]; + outItems[i].start = item.start; + outItems[i].end = item.end; + outItems[i].size = item.size; + if (type == PartitionType::kMinato) { + outItems[i].flags = 0; + } else if (type == PartitionType::kXeltek) { + outItems[i].flags = kInvalidPartition; + } + } + return data; +} + +std::vector GeneratePartitionTable(const std::vector& items, PartitionType type) { + if (type == PartitionType::kLeap) { + return GenerateLeapPartitionTable(items); + } else { + return GenerateXeltekMinatoPartitionTable(items, type); + } +} + +const char* GetPartionTypeLongName(PartitionType type) { + switch (type) { + case PartitionType::kLeap: + return "Leap Electronic NAND Flash programmer"; + case PartitionType::kMinato: + return "Minato NAND Flash programmer, Data I/O NAND Flash programmer"; + case PartitionType::kXeltek: + return "Xeltek Universal IC Chip Programmer"; + default: + return "Unknown"; + } +} + +int MainEntry(int argc, char** argv) { + PartitionType inputType = PartitionType::kNone; + PartitionType outputType = PartitionType::kNone; + std::string inputFile; + std::string outputFile; + bool quiet = false; + if (argc == 1 || (argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0))) { + fmt::print("Usage: {} -i [-q] [-L/M/X output]\n", argv[0]); + fmt::print(" -i : input file\n"); + fmt::print(" -q : quiet mode\n"); + fmt::print(" -L : output file for Leap\n"); + fmt::print(" -M : output file for Minato\n"); + fmt::print(" -X : output file for Xeltek\n"); + return 1; + } + // parse arguments + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-i") == 0) { + if (i + 1 < argc) { + inputFile = argv[i + 1]; + i++; + } + } else if (strcmp(argv[i], "-q") == 0) { + quiet = true; + } else if (strcmp(argv[i], "-L") == 0) { + if (i + 1 < argc) { + outputFile = argv[i + 1]; + outputType = PartitionType::kLeap; + i++; + } + } else if (strcmp(argv[i], "-M") == 0) { + if (i + 1 < argc) { + outputFile = argv[i + 1]; + outputType = PartitionType::kMinato; + i++; + } + } else if (strcmp(argv[i], "-X") == 0) { + if (i + 1 < argc) { + outputFile = argv[i + 1]; + outputType = PartitionType::kXeltek; + i++; + } + } + } + if (inputFile.empty()) { + fmt::print("Input file is required\n"); + return 1; + } + std::vector inputData; + if (int rc; !ReadFile(inputFile, inputData, 512, rc)) { + fmt::print("Unable to open '{}', rc={}, {}", inputFile, rc, GetErrorMsg(-rc)); + return 1; + } + std::vector items; + inputType = ReadPartitionTable(inputData, items); + if (inputType == PartitionType::kNone) { + fmt::print("Invalid or unknown partition table\n"); + return 1; + } + if (items.empty()) { + fmt::print("Empty partition table\n"); + return 1; + } + bool hasError = false; + { + // check if output is valid + // 1. range check + for (const auto& item: items) { + if (item.start >= item.end || item.start + item.size > item.end) { + fmt::print("Invalid partition table, start={}, end={}, size={}\n", item.start, item.end, item.size); + hasError = true; + } + } + // 2. items should be sorted and not overlap + uint32_t last = 0; + for (const auto& item: items) { + if (item.start < last) { + fmt::print("Invalid partition table, items are not sorted or overlap\n"); + hasError = true; + break; + } + last = item.end; + } + } + if (hasError) { + fmt::print("The partition table has errors, aborting\n"); + return 1; + } + // if not quiet, print the partition table + if (!quiet) { + std::string buf; + buf += fmt::format("Input: {} partition(s), {}\n", items.size(), GetPartionTypeLongName(inputType)); + /** + * Index Start End Size + * 31 00000000 00000000 00000000 + */ + buf += "Index Start End Size\n"; + for (size_t i = 0; i < items.size(); i++) { + const auto& item = items[i]; + buf += fmt::format("{:5} {:08x} {:08x} {:08x}\n", i + 1, item.start, item.end, item.size); + } + fmt::print("{}", buf); + } + if (outputType != PartitionType::kNone && !outputFile.empty()) { + std::vector outputData = GeneratePartitionTable(items, outputType); + if (int rc; !WriteFile(outputFile, outputData, rc)) { + fmt::print("Unable to write '{}', rc={}, {}\n", outputFile, rc, GetErrorMsg(-rc)); + return 1; + } + if (!quiet) { + fmt::print("Written to '{}', format: {}\n", outputFile, GetPartionTypeLongName(outputType)); + } + } + return 0; +} + +// The real main entry +#if defined(_WIN32) || defined(_WIN64) || 1 + +extern "C" void __wgetmainargs(int*, wchar_t***, wchar_t***, int, int*); + +int main(int argc, char** argv) { + int argcW; + wchar_t** argvW; + wchar_t** envW; + __wgetmainargs(&argcW, &argvW, &envW, 0, nullptr); + std::vector args; + args.reserve(argcW); + for (int i = 0; i < argcW; i++) { + args.push_back(utfcvt::WideToUTF8(argvW[i])); + } + std::vector argvC; + argvC.reserve(args.size()); + for (auto& arg: args) { + argvC.push_back(arg.data()); + } + return MainEntry((int) argvC.size(), argvC.data()); +} + +#else + +int main(int argc, char** argv) { + return MainEntry(argc, argv); +} + +#endif diff --git a/os_utils.cc b/os_utils.cc new file mode 100644 index 0000000..1661572 --- /dev/null +++ b/os_utils.cc @@ -0,0 +1,149 @@ +// +// Created on 2024-05-17. +// + +#include "os_utils.h" + +#include "utf_convert.h" + +#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) + +#include +#include +#include +#include +#include + +bool ReadFile(std::string_view path, std::vector& out, size_t maxSize, int& errorCode) { + if (maxSize == 0) { + errorCode = -EINVAL; + return false; + } + int fd = open(std::string(path).c_str(), O_RDONLY); + if (fd < 0) { + errorCode = -errno; + return false; + } + struct stat64 st{}; + if (fstat64(fd, &st) < 0) { + close(fd); + errorCode = -errno; + return false; + } + if (st.st_size > maxSize) { + close(fd); + errorCode = -EOVERFLOW; + return false; + } + out.resize(st.st_size); + ssize_t remaining = st.st_size; + ssize_t read = 0; + uint8_t* ptr = out.data(); + while (remaining > 0) { + read = ::read(fd, ptr, remaining); + if (read < 0) { + close(fd); + errorCode = -errno; + return false; + } + remaining -= read; + ptr += read; + } + close(fd); + return true; + +} + +bool WriteFile(std::string_view path, std::span data, int& errorCode) { + int fd = open(std::string(path).c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + errorCode = -errno; + return false; + } + ssize_t remaining = data.size_bytes(); + ssize_t written = 0; + const uint8_t* ptr = data.data(); + while (remaining > 0) { + written = ::write(fd, ptr, remaining); + if (written < 0) { + close(fd); + errorCode = -errno; + return false; + } + remaining -= written; + ptr += written; + } + close(fd); + return true; +} + +#include + + +std::string GetErrorMsg(int errorCode) { + return strerror(std::abs(errorCode)); +} + +#elif defined(_WIN32) + +#include + +bool ReadFile(std::string_view path, std::vector& out, size_t maxSize, int& errorCode) { + std::wstring wpath = utfcvt::UTF8ToWide(path); + HANDLE hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) { + errorCode = -(int) GetLastError(); + return false; + } + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(hFile, &fileSize)) { + CloseHandle(hFile); + errorCode = -(int) GetLastError(); + return false; + } + if (fileSize.QuadPart > maxSize) { + CloseHandle(hFile); + errorCode = -ERROR_FILE_TOO_LARGE; + return false; + } + out.resize(fileSize.QuadPart); + DWORD bytesRead = 0; + if (!ReadFile(hFile, out.data(), fileSize.LowPart, &bytesRead, nullptr)) { + CloseHandle(hFile); + errorCode = -(int) GetLastError(); + return false; + } + CloseHandle(hFile); + return true; +} + +bool WriteFile(std::string_view path, std::span data, int& errorCode) { + std::wstring wpath = utfcvt::UTF8ToWide(path); + HANDLE hFile = CreateFileW(wpath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) { + errorCode = -(int) GetLastError(); + return false; + } + DWORD bytesWritten = 0; + if (!WriteFile(hFile, data.data(), (DWORD) data.size_bytes(), &bytesWritten, nullptr)) { + CloseHandle(hFile); + errorCode = -(int) GetLastError(); + return false; + } + CloseHandle(hFile); + return true; +} + +std::string GetErrorMsg(int errorCode) { + LPWSTR msgBuf = nullptr; + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR) &msgBuf, 0, nullptr); + std::wstring msg(msgBuf); + LocalFree(msgBuf); + return utfcvt::WideToUTF8(msg); +} + +#else +#error "Unsupported platform" +#endif diff --git a/os_utils.h b/os_utils.h new file mode 100644 index 0000000..6b7bfea --- /dev/null +++ b/os_utils.h @@ -0,0 +1,21 @@ +// +// Created on 2024-05-17. +// + +#ifndef NANDPARTITIONTABLECONVERT_OS_UTILS_H +#define NANDPARTITIONTABLECONVERT_OS_UTILS_H + +#include +#include +#include +#include +#include +#include + +bool ReadFile(std::string_view path, std::vector& out, size_t maxSize, int& errorCode); + +bool WriteFile(std::string_view path, std::span data, int& errorCode); + +std::string GetErrorMsg(int errorCode); + +#endif //NANDPARTITIONTABLECONVERT_OS_UTILS_H diff --git a/utf_convert.cc b/utf_convert.cc new file mode 100644 index 0000000..f5bdc42 --- /dev/null +++ b/utf_convert.cc @@ -0,0 +1,133 @@ +// +// Created on 2024-05-17. +// + +#include "utf_convert.h" + +#include + +namespace utfcvt { + +std::u32string UTF8ToUTF32(std::string_view utf8) { + if (utf8.empty()) { + return {}; + } + std::vector codePoints; + // source may have multiple code points, treat as UTF-8 + size_t i = 0; + while (i < utf8.size()) { + if ((utf8[i] & 0b10000000) == 0) { + // 1 byte code point, ASCII + int c = (utf8[i] & 0b01111111); + codePoints.push_back(c); + i++; + } else if ((utf8[i] & 0b11100000) == 0b11000000) { + // 2 byte code point + int c = (utf8[i] & 0b00011111) << 6 | (utf8[i + 1] & 0b00111111); + codePoints.push_back(c); + i += 2; + } else if ((utf8[i] & 0b11110000) == 0b11100000) { + // 3 byte code point + int c = (utf8[i] & 0b00001111) << 12 | (utf8[i + 1] & 0b00111111) << 6 | (utf8[i + 2] & 0b00111111); + codePoints.push_back(c); + i += 3; + } else { + // 4 byte code point + int c = (utf8[i] & 0b00000111) << 18 | (utf8[i + 1] & 0b00111111) << 12 + | (utf8[i + 2] & 0b00111111) << 6 | (utf8[i + 3] & 0b00111111); + codePoints.push_back(c); + i += 4; + } + } + return {codePoints.begin(), codePoints.end()}; +} + +std::u32string UTF16ToUTF32(std::u16string_view utf16) { + if (utf16.empty()) { + return {}; + } + std::vector codePoints; + for (size_t i = 0; i < utf16.size(); ++i) { + if (utf16[i] < 0xD800 || utf16[i] > 0xDFFF) { + codePoints.push_back(utf16[i]); + } else if (i + 1 < utf16.size()) { + codePoints.push_back(0x10000 + ((utf16[i] - 0xD800) << 10) + (utf16[i + 1] - 0xDC00)); + ++i; + } else { + codePoints.push_back(0xFFFD); + } + } + return {codePoints.begin(), codePoints.end()}; +} + +std::u16string UTF32ToUTF16(std::u32string_view utf32){ + std::vector utf16; + for (auto c: utf32) { + if (c <= 0xFFFF) { + utf16.push_back(static_cast(c)); + } else if (c <= 0x10FFFF) { + utf16.push_back(static_cast(0xD800 + ((c - 0x10000) >> 10))); + utf16.push_back(static_cast(0xDC00 + ((c - 0x10000) & 0x3FF))); + } else { + utf16.push_back(0xFFFD); + } + } + return {utf16.begin(), utf16.end()}; +} + +std::string UTF32ToUTF8(std::u32string_view utf32) { + std::string utf8; + for (auto c: utf32) { + if (c <= 0x7F) { + utf8.push_back(static_cast(c)); + } else if (c <= 0x7FF) { + utf8.push_back(static_cast(0xC0 | (c >> 6))); + utf8.push_back(static_cast(0x80 | (c & 0x3F))); + } else if (c <= 0xFFFF) { + utf8.push_back(static_cast(0xE0 | (c >> 12))); + utf8.push_back(static_cast(0x80 | ((c >> 6) & 0x3F))); + utf8.push_back(static_cast(0x80 | (c & 0x3F))); + } else { + utf8.push_back(static_cast(0xF0 | (c >> 18))); + utf8.push_back(static_cast(0x80 | ((c >> 12) & 0x3F))); + utf8.push_back(static_cast(0x80 | ((c >> 6) & 0x3F))); + utf8.push_back(static_cast(0x80 | (c & 0x3F))); + } + } + return utf8; +} + +std::string UTF16ToUTF8(std::u16string_view utf16) { + return UTF32ToUTF8(UTF16ToUTF32(utf16)); +} + +std::u16string UTF8ToUTF16(std::string_view utf8) { + return UTF32ToUTF16(UTF8ToUTF32(utf8)); +} + +#if defined(_WIN32) || defined(_WIN64) + +// for windows, they are not implemented on linux + +static_assert(sizeof(wchar_t) == sizeof(char16_t)); + +std::wstring UTF16ToWide(std::u16string_view utf16) { + return {reinterpret_cast(utf16.data()), utf16.size()}; +} + +std::u16string WideToUTF16(std::wstring_view wide) { + return {reinterpret_cast(wide.data()), wide.size()}; +} + +std::wstring UTF8ToWide(std::string_view utf8) { + auto utf16 = UTF8ToUTF16(utf8); + return UTF16ToWide(utf16); +} + +std::string WideToUTF8(std::wstring_view wide) { + return UTF16ToUTF8(std::u16string_view{reinterpret_cast(wide.data()), wide.size()}); +} + +#endif + +} diff --git a/utf_convert.h b/utf_convert.h new file mode 100644 index 0000000..dae8bca --- /dev/null +++ b/utf_convert.h @@ -0,0 +1,37 @@ +// +// Created on 2024-05-17. +// + +#ifndef UTF_CONVERT_H +#define UTF_CONVERT_H + +#include +#include + +namespace utfcvt { + +std::string UTF16ToUTF8(std::u16string_view utf16); + +std::u16string UTF8ToUTF16(std::string_view utf8); + +std::u32string UTF8ToUTF32(std::string_view utf8); + +std::string UTF32ToUTF8(std::u32string_view utf32); + +std::u32string UTF16ToUTF32(std::u16string_view utf16); + +std::u16string UTF32ToUTF16(std::u32string_view utf32); + +// for windows, they are not implemented on linux + +std::wstring UTF8ToWide(std::string_view utf8); + +std::string WideToUTF8(std::wstring_view wide); + +std::wstring UTF16ToWide(std::u16string_view utf16); + +std::u16string WideToUTF16(std::wstring_view wide); + +} + +#endif //UTF_CONVERT_H