From b5a1ea6089ef4825985d6c71909aeb9c810c0fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=81=E6=A0=91=E4=BA=BA?= Date: Fri, 20 Dec 2024 11:03:16 +0900 Subject: [PATCH] refactor: work without infra.dll, native 64 bit build. --- .gitattributes | 0 CMakeLists.txt | 6 +- CMakePresets.json | 2 +- Jenkinsfile | 8 + README.MD | 13 +- src/common/endian_helper.h | 81 +++++- src/infra/infra.cpp | 245 ++++++++++-------- src/infra/infra.h | 40 +-- src/infra/sqlite_base.h | 9 - src/infra/sqlite_error.h | 99 ------- src/infra/sqlite_fn.h | 71 ----- src/main.cpp | 21 +- src/tc_tea/tc_tea.cpp | 6 +- src/tc_tea/tc_tea.h | 4 +- src/utils/cli.cpp | 5 - third-party/aes/CMakeLists.txt | 14 + third-party/aes/aes.cpp | 357 ++++++++++++++++++++++++++ third-party/aes/aes.h | 25 ++ third-party/md5/CMakeLists.txt | 15 ++ third-party/md5/md5.cpp | 284 ++++++++++++++++++++ third-party/md5/md5.h | 46 ++++ third-party/sqlite3/.gitignore | 2 + third-party/sqlite3/CMakeLists.txt | 11 + third-party/sqlite3/fetch_sqlite3.sh | 11 + third-party/sqlite3/sqlite3.sha256sum | 1 + 25 files changed, 1002 insertions(+), 374 deletions(-) create mode 100644 .gitattributes delete mode 100644 src/infra/sqlite_base.h delete mode 100644 src/infra/sqlite_error.h delete mode 100644 src/infra/sqlite_fn.h create mode 100644 third-party/aes/CMakeLists.txt create mode 100644 third-party/aes/aes.cpp create mode 100644 third-party/aes/aes.h create mode 100644 third-party/md5/CMakeLists.txt create mode 100644 third-party/md5/md5.cpp create mode 100644 third-party/md5/md5.h create mode 100644 third-party/sqlite3/.gitignore create mode 100644 third-party/sqlite3/CMakeLists.txt create mode 100644 third-party/sqlite3/fetch_sqlite3.sh create mode 100644 third-party/sqlite3/sqlite3.sha256sum diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e69de29 diff --git a/CMakeLists.txt b/CMakeLists.txt index e2feee3..abaca50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.10) project(kgg-dec VERSION 0.5.1 LANGUAGES CXX) +add_subdirectory(third-party/aes) +add_subdirectory(third-party/md5) +add_subdirectory(third-party/sqlite3) + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -23,6 +27,6 @@ target_include_directories(kgg-dec src/tc_tea ) -target_link_libraries(kgg-dec PRIVATE shell32 ole32) +target_link_libraries(kgg-dec PRIVATE shell32 ole32 libaes libmd5 sqlite3) target_compile_definitions(kgg-dec PRIVATE NOMINMAX) target_compile_definitions(kgg-dec PRIVATE KGGDEC_PROJECT_VERSION="${PROJECT_VERSION}") diff --git a/CMakePresets.json b/CMakePresets.json index bf212c3..0853876 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -35,7 +35,7 @@ "description": "Configure for Visual Studio", "generator": "Visual Studio 17 2022", "binaryDir": "${sourceDir}/build/vs2022", - "architecture": "Win32" + "architecture": "x64" } ], "buildPresets": [ diff --git a/Jenkinsfile b/Jenkinsfile index 8125655..92b8b44 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -27,6 +27,14 @@ pipeline { } } + stage('Prepare') { + steps { + dir('third-party/sqlite3') { + sh './fetch_sqlite3.sh' + } + } + } + stage('Build') { steps { bat ''' diff --git a/README.MD b/README.MD index 557f62f..3a8fba0 100644 --- a/README.MD +++ b/README.MD @@ -2,21 +2,20 @@ 酷狗 `kgg` 文件解密工具。 +请尽量在下载文件的设备上操作,避免密钥丢失。 + ## 使用方法 (快捷) -1. 将 `kgg-dec.exe` 与酷狗安装目录下的 `infra.dll` 拷贝到 `kgg` 文件所在目录。 -2. 双击 `kgg-dec.exe` 开始解密当前目录。 +1. 双击 `kgg-dec.exe` 开始解密当前目录。 ## 使用方法 (命令行) -1. 从酷狗安装目录拷贝 `infra.dll` 文件到 `kgg-dec.exe` 的目录。 -2. 启动 `kgg-dec.exe`,其中第一个参数为含有 `kgg` 文件的目录。 -3. 你也可以使用 `--` 来将参数后的 `-` 开头的参数视为输入文件或目录。 -4. 你可以指定多项输入文件或目录。 +1. 启动 `kgg-dec.exe`,其中第一个参数为含有 `kgg` 文件的目录。 +2. 你也可以使用 `--` 来将参数后的 `-` 开头的参数视为输入文件或目录。 +3. 你可以指定多项输入文件或目录。 ### 其它参数 -* `--infra-dll` (可选): 指定 `infra.dll` 的路径,默认为 `infra.dll`。 * `--scan-all-file-ext` (可选,`0` 或 `1`): 是否扫描所有文件后缀名。默认为 `0`,只扫描 `kgg` 文件。 * `--db` (可选): 指定 `KGMusicV3.db` 的路径。默认为 `%AppData%/Kugou8/KGMusicV3.db`。 * `--suffix` (可选): 指定解密后文件的后缀。默认为 `_kgg-dec`。 diff --git a/src/common/endian_helper.h b/src/common/endian_helper.h index fcd77bf..3630549 100644 --- a/src/common/endian_helper.h +++ b/src/common/endian_helper.h @@ -2,6 +2,7 @@ #include #include +#include #if defined(_MSC_VER) #define bswap_u16 _byteswap_ushort @@ -15,35 +16,87 @@ namespace Endian { -inline uint64_t be_u64_read(const uint8_t* p) { +template +T be_read(const uint8_t* p) + requires(std::is_integral_v) +{ + // ReSharper disable once CppDFAUnreachableCode if constexpr (std::endian::native == std::endian::big) { - return *reinterpret_cast(p); + return *reinterpret_cast(p); + } else if constexpr (sizeof(T) == 2) { + return bswap_u16(*reinterpret_cast(p)); + } else if constexpr (sizeof(T) == 4) { + return bswap_u32(*reinterpret_cast(p)); + } else if constexpr (sizeof(T) == 8) { + return bswap_u64(*reinterpret_cast(p)); } else { - return bswap_u64(*reinterpret_cast(p)); + T result{}; + for (size_t i = 0; i < sizeof(T); i++) { + reinterpret_cast(&result)[i] = p[sizeof(T) - i - 1]; + } + return result; } } -inline void be_u64_write(uint8_t* p, uint64_t value) { +template +void be_write(uint8_t* p, const T value) + requires(std::is_integral_v) +{ + // ReSharper disable once CppDFAUnreachableCode if constexpr (std::endian::native == std::endian::big) { - *reinterpret_cast(p) = value; + *reinterpret_cast(p) = value; + } else if constexpr (sizeof(T) == 2) { + *reinterpret_cast(p) = bswap_u16(value); + } else if constexpr (sizeof(T) == 4) { + *reinterpret_cast(p) = bswap_u32(value); + } else if constexpr (sizeof(T) == 8) { + *reinterpret_cast(p) = bswap_u64(value); } else { - *reinterpret_cast(p) = bswap_u64(value); + for (size_t i = 0; i < sizeof(T); i++) { + p[sizeof(T) - i - 1] = reinterpret_cast(&value)[i]; + } } } -inline uint32_t be_u32_read(const uint8_t* p) { - if constexpr (std::endian::native == std::endian::big) { - return *reinterpret_cast(p); +template +T le_read(const uint8_t* p) + requires(std::is_integral_v) +{ + // ReSharper disable once CppDFAUnreachableCode + if constexpr (std::endian::native == std::endian::little) { + return *reinterpret_cast(p); + } else if constexpr (sizeof(T) == 2) { + return bswap_u16(*reinterpret_cast(p)); + } else if constexpr (sizeof(T) == 4) { + return bswap_u32(*reinterpret_cast(p)); + } else if constexpr (sizeof(T) == 8) { + return bswap_u64(*reinterpret_cast(p)); } else { - return bswap_u32(*reinterpret_cast(p)); + T result{}; + for (size_t i = 0; i < sizeof(T); i++) { + reinterpret_cast(&result)[i] = p[i]; + } + return result; } } -inline void be_u32_write(uint8_t* p, uint32_t value) { - if constexpr (std::endian::native == std::endian::big) { - *reinterpret_cast(p) = value; +template +void le_write(uint8_t* p, const T value) + requires(std::is_integral_v) +{ + // ReSharper disable once CppDFAUnreachableCode + if constexpr (std::endian::native == std::endian::little) { + *reinterpret_cast(p) = value; + } else if constexpr (sizeof(T) == 2) { + *reinterpret_cast(p) = bswap_u16(value); + } else if constexpr (sizeof(T) == 4) { + *reinterpret_cast(p) = bswap_u32(value); + } else if constexpr (sizeof(T) == 8) { + *reinterpret_cast(p) = bswap_u64(value); } else { - *reinterpret_cast(p) = bswap_u32(value); + for (size_t i = 0; i < sizeof(T); i++) { + p[i] = reinterpret_cast(&value)[i]; + } } } diff --git a/src/infra/infra.cpp b/src/infra/infra.cpp index 739ef65..0f9bab3 100644 --- a/src/infra/infra.cpp +++ b/src/infra/infra.cpp @@ -1,137 +1,168 @@ #include "infra.h" -#include "sqlite_error.h" -#include "sqlite_fn.h" -#include +#include +#include +#include +#include +#include namespace Infra { -SqliteDB::SqliteDB(const std::filesystem::path& infra_dll_path) { - ok_ = InitInfraDll(infra_dll_path); +constexpr size_t kPageSize = 0x400; + +inline bool is_valid_page_1_header(const uint8_t* page1) { + const auto o10 = Endian::le_read(&page1[0x10]); + const auto o14 = Endian::le_read(&page1[0x14]); + const uint32_t v6 = (o10 & 0xff) << 8 | (o10 & 0xff00) << 16; + return o14 == 0x20204000 && (v6 - 0x200) <= 0xFE00 && ((v6 - 1) & v6) == 0; } -bool SqliteDB::Open(const std::filesystem::path& db_path, std::string_view key) { - if (infra_ == nullptr) { - return false; +void derive_page_key(uint8_t* aes_key, uint8_t* aes_iv, const uint8_t* p_master_key, const uint32_t page_no) { + uint8_t buffer[0x18]; + + // Setup buffer + memcpy(buffer, p_master_key, 0x10); + Endian::le_write(&buffer[0x10], page_no); + Endian::le_write(&buffer[0x14], 0x546C4173); + + // Derive Key + md5(aes_key, buffer, 24); + + // Derive IV + for (uint32_t ebx{page_no + 1}, i = 0; i < 16; i += 4) { + uint32_t eax = 0x7FFFFF07 * (ebx / 0xce26); + uint32_t ecx = 0x9EF4 * ebx - eax; + if (ecx & 0x8000'0000) { + ecx += 0x7FFF'FF07; + } + ebx = ecx; + Endian::le_write(&buffer[i], ebx); + } + md5(aes_iv, buffer, 16); + + // Cleanup + memset(buffer, 0xCC, sizeof(buffer)); +} + +static const uint8_t kDefaultMasterKey[0x10] = { + 0x1d, 0x61, 0x31, 0x45, 0xb2, 0x47, 0xbf, 0x7f, // + 0x3d, 0x18, 0x96, 0x72, 0x14, 0x4f, 0xe4, 0xbf, // +}; + +static constexpr uint8_t kSQLiteDatabaseHeader[0x10] = {'S', 'Q', 'L', 'i', 't', 'e', ' ', 'f', + 'o', 'r', 'm', 'a', 't', ' ', '3', 0}; + +int load_db(std::vector& db_data, const std::filesystem::path& db_path) { + using namespace AES; + db_data.clear(); + + std::ifstream ifs_db(db_path, std::ios::binary); + if (!ifs_db.is_open()) { + return SQLITE_CANTOPEN; } - auto db_path_u8 = db_path.generic_u8string(); - int rc = sqlite3_open_v2_(reinterpret_cast(db_path_u8.c_str()), &db_, SQLITE_OPEN_READONLY, nullptr); - if (rc != SQLITE_OK) { - return false; + ifs_db.seekg(0, std::ios::end); + const auto db_size = static_cast(ifs_db.tellg()); + const auto last_page = db_size / kPageSize; + if (db_size % kPageSize != 0) { + return SQLITE_CORRUPT; } + ifs_db.seekg(0, std::ios::beg); - if (!key.empty()) { - rc = sqlite3_key_(db_, key.data(), static_cast(key.size())); - if (rc != SQLITE_OK) { - sqlite3_close_v2_(db_); - db_ = nullptr; - return false; + db_data.resize(db_size); + auto p_page = db_data.data(); + + AES_ctx ctx_aes{}; + for (size_t page_no = 1; page_no <= last_page; page_no++, p_page += kPageSize) { + ifs_db.read(reinterpret_cast(p_page), kPageSize); + if (!ifs_db) [[unlikely]] { + return SQLITE_IOERR; + } + + { + uint8_t aes_key[16]; + uint8_t aes_iv[16]; + derive_page_key(aes_key, aes_iv, kDefaultMasterKey, static_cast(page_no)); + AES_init_ctx_iv(&ctx_aes, aes_key, aes_iv); + } + + if (page_no == 1) [[unlikely]] { + if (memcmp(p_page, kSQLiteDatabaseHeader, 0x10) == 0) { + ifs_db.read(reinterpret_cast(p_page + kPageSize), + static_cast(db_size - kPageSize)); + return SQLITE_OK; // no encryption + } + + if (!is_valid_page_1_header(p_page)) [[unlikely]] { + db_data.clear(); + return SQLITE_CORRUPT; // header validation failed + } + uint8_t backup[0x08]; // backup magic numbers + memcpy(&backup, &p_page[0x10], 0x08); + memcpy(&p_page[0x10], &p_page[0x08], 0x08); + AES_CBC_decrypt_buffer(&ctx_aes, p_page + 0x10, kPageSize - 0x10); + if (memcmp(backup, &p_page[0x10], 0x08) != 0) { + db_data.clear(); + return SQLITE_CORRUPT; // header validation failed + } + memcpy(p_page, kSQLiteDatabaseHeader, 0x10); + } else { + AES_CBC_decrypt_buffer(&ctx_aes, p_page, kPageSize); } } - return true; + return SQLITE_OK; } -void SqliteDB::Close() { - if (db_) { - sqlite3_close_v2_(db_); - db_ = nullptr; - } -} -void SqliteDB::FreeInfraDll() { - if (infra_ != nullptr) { - FreeLibrary(reinterpret_cast(infra_)); - infra_ = nullptr; - } +int dump_ekey(kgm_ekey_db_t& result, const std::filesystem::path& db_path) { + result.clear(); - sqlite3_open_v2_ = nullptr; - sqlite3_key_ = nullptr; - sqlite3_prepare_v2_ = nullptr; - sqlite3_step_ = nullptr; - sqlite3_column_text_ = nullptr; - sqlite3_close_v2_ = nullptr; - sqlite3_finalize_ = nullptr; -} - -bool SqliteDB::InitInfraDll(const std::filesystem::path& infra_dll_path) { - auto path_unicode = infra_dll_path.wstring(); - HMODULE hMod = LoadLibraryW(path_unicode.c_str()); - infra_ = hMod; - if (hMod == nullptr) { - return false; - } - - sqlite3_open_v2_ = reinterpret_cast(GetProcAddress(hMod, "sqlite3_open_v2")); - sqlite3_key_ = reinterpret_cast(GetProcAddress(hMod, "sqlite3_key")); - sqlite3_prepare_v2_ = reinterpret_cast(GetProcAddress(hMod, "sqlite3_prepare_v2")); - sqlite3_step_ = reinterpret_cast(GetProcAddress(hMod, "sqlite3_step")); - sqlite3_column_text_ = reinterpret_cast(GetProcAddress(hMod, "sqlite3_column_text")); - sqlite3_close_v2_ = reinterpret_cast(GetProcAddress(hMod, "sqlite3_close_v2")); - sqlite3_finalize_ = reinterpret_cast(GetProcAddress(hMod, "sqlite3_finalize")); - - if (!sqlite3_open_v2_ || !sqlite3_key_ || !sqlite3_prepare_v2_ || !sqlite3_step_ || !sqlite3_column_text_ || - !sqlite3_close_v2_ || !sqlite3_finalize_) { - infra_ = nullptr; - return false; - } - - return true; -} - -KugouDb::KugouDb(const std::filesystem::path& infra_dll_path, const std::filesystem::path& db_path) - : SqliteDB(infra_dll_path) { - int rc{-1}; - if (!IsInfraOk()) { - return; - } - - Open(db_path); -} - -kgm_ekey_db_t KugouDb::dump_ekey(int& error) { - if (!IsOpen()) { - error = SQLITE_ERROR; - return {}; - } - - int rc{-1}; - sqlite3_stmt* stmt{nullptr}; - - rc = sqlite3_prepare_v2_(db_, - "select EncryptionKeyId, EncryptionKey from ShareFileItems" - " where EncryptionKey != ''", - -1, &stmt, nullptr); + std::vector db_data; + int rc = load_db(db_data, db_path); if (rc != SQLITE_OK) { - error = rc; - return {}; + return rc; } - kgm_ekey_db_t result{}; - while ((rc = sqlite3_step_(stmt)) == SQLITE_ROW) { - const auto* ekey_id = reinterpret_cast(sqlite3_column_text_(stmt, 0)); - const auto* ekey = reinterpret_cast(sqlite3_column_text_(stmt, 1)); + // Open an in-memory database + sqlite3* db = nullptr; + rc = sqlite3_open(":memory:", &db); + if (rc != SQLITE_OK) { + return rc; + } + + const auto p_db_bytes = db_data.data(); + const auto len = static_cast(db_data.size()); + rc = sqlite3_deserialize(db, "main", p_db_bytes, len, len, SQLITE_DESERIALIZE_READONLY); + if (rc != SQLITE_OK) { + sqlite3_close(db); + return rc; + } + + sqlite3_stmt* stmt{nullptr}; + rc = sqlite3_prepare_v2(db, + "select EncryptionKeyId, EncryptionKey from ShareFileItems" + " where EncryptionKey != ''", + -1, &stmt, nullptr); + + if (rc != SQLITE_OK) { + sqlite3_close(db); + return rc; + } + + while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) { + const auto* ekey_id = reinterpret_cast(sqlite3_column_text(stmt, 0)); + const auto* ekey = reinterpret_cast(sqlite3_column_text(stmt, 1)); result[ekey_id] = ekey; } if (rc != SQLITE_DONE) { - error = rc; + sqlite3_close(db); + return rc; } - sqlite3_finalize_(stmt); - error = 0; - return result; -} + sqlite3_finalize(stmt); -KugouDb::~KugouDb() { - if (db_ != nullptr) { - sqlite3_close_v2_(db_); - db_ = nullptr; - } -} - -bool KugouDb::Open(const std::filesystem::path& db_path) { - return SqliteDB::Open(db_path, {"7777B48756BA491BB4CEE771A3E2727E"}); + return sqlite3_close(db); } } // namespace Infra diff --git a/src/infra/infra.h b/src/infra/infra.h index 4f87093..519aa77 100644 --- a/src/infra/infra.h +++ b/src/infra/infra.h @@ -1,49 +1,13 @@ #pragma once #include +#include #include -#include "sqlite_base.h" -#include "sqlite_fn.h" - namespace Infra { typedef std::unordered_map kgm_ekey_db_t; -extern bool g_init_sqlite_ok; -class SqliteDB { - public: - explicit SqliteDB(const std::filesystem::path& infra_dll_path); - bool Open(const std::filesystem::path& db_path, std::string_view key); - void Close(); - [[nodiscard]] bool IsInfraOk() const { return ok_; } - [[nodiscard]] bool IsOpen() const { return db_ != nullptr; } - - private: - bool InitInfraDll(const std::filesystem::path& infra_dll_path); - bool ok_{false}; - - protected: - void FreeInfraDll(); - - void* infra_{nullptr}; - sqlite3_open_v2_t sqlite3_open_v2_{nullptr}; - sqlite3_key_t sqlite3_key_{nullptr}; - sqlite3_prepare_v2_t sqlite3_prepare_v2_{nullptr}; - sqlite3_step_t sqlite3_step_{nullptr}; - sqlite3_column_text_t sqlite3_column_text_{nullptr}; - sqlite3_close_v2_t sqlite3_close_v2_{nullptr}; - sqlite3_finalize_t sqlite3_finalize_{nullptr}; - sqlite3* db_{nullptr}; -}; - -class KugouDb : public SqliteDB { - public: - explicit KugouDb(const std::filesystem::path& infra_dll_path, const std::filesystem::path& db_path); - ~KugouDb(); - - bool Open(const std::filesystem::path& db_path); - kgm_ekey_db_t dump_ekey(int& error); -}; +int dump_ekey(kgm_ekey_db_t& result, const std::filesystem::path& db_path); } // namespace Infra diff --git a/src/infra/sqlite_base.h b/src/infra/sqlite_base.h deleted file mode 100644 index 6bf48b6..0000000 --- a/src/infra/sqlite_base.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -// SQLite -#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ - -typedef struct sqlite3 sqlite3; -typedef struct sqlite3_stmt sqlite3_stmt; diff --git a/src/infra/sqlite_error.h b/src/infra/sqlite_error.h deleted file mode 100644 index ec2e495..0000000 --- a/src/infra/sqlite_error.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#define SQLITE_OK (0) - -/* beginning-of-error-codes */ -#define SQLITE_ERROR 1 /* Generic error */ -#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ -#define SQLITE_PERM 3 /* Access permission denied */ -#define SQLITE_ABORT 4 /* Callback routine requested an abort */ -#define SQLITE_BUSY 5 /* The database file is locked */ -#define SQLITE_LOCKED 6 /* A table in the database is locked */ -#define SQLITE_NOMEM 7 /* A malloc() failed */ -#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ -#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ -#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ -#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ -#define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */ -#define SQLITE_FULL 13 /* Insertion failed because database is full */ -#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ -#define SQLITE_PROTOCOL 15 /* Database lock protocol error */ -#define SQLITE_EMPTY 16 /* Internal use only */ -#define SQLITE_SCHEMA 17 /* The database schema changed */ -#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ -#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ -#define SQLITE_MISMATCH 20 /* Data type mismatch */ -#define SQLITE_MISUSE 21 /* Library used incorrectly */ -#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ -#define SQLITE_AUTH 23 /* Authorization denied */ -#define SQLITE_FORMAT 24 /* Not used */ -#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ -#define SQLITE_NOTADB 26 /* File opened that is not a database file */ -#define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */ -#define SQLITE_WARNING 28 /* Warnings from sqlite3_log() */ -#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ -#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ -/* end-of-error-codes */ - -inline const char* sqlite_get_error(int rc) { - switch (rc) { - case SQLITE_ERROR: - return "SQLITE_ERROR: Generic error"; - case SQLITE_INTERNAL: - return "SQLITE_INTERNAL: Internal logic error in SQLite"; - case SQLITE_PERM: - return "SQLITE_PERM: Access permission denied"; - case SQLITE_ABORT: - return "SQLITE_ABORT: Callback routine requested an abort"; - case SQLITE_BUSY: - return "SQLITE_BUSY: The database file is locked"; - case SQLITE_LOCKED: - return "SQLITE_LOCKED: A table in the database is locked"; - case SQLITE_NOMEM: - return "SQLITE_NOMEM: A malloc() failed"; - case SQLITE_READONLY: - return "SQLITE_READONLY: Attempt to write a readonly database"; - case SQLITE_INTERRUPT: - return "SQLITE_INTERRUPT: Operation terminated by sqlite3_interrupt()"; - case SQLITE_IOERR: - return "SQLITE_IOERR: Some kind of disk I/O error occurred"; - case SQLITE_CORRUPT: - return "SQLITE_CORRUPT: The database disk image is malformed"; - case SQLITE_NOTFOUND: - return "SQLITE_NOTFOUND: Unknown opcode in sqlite3_file_control()"; - case SQLITE_FULL: - return "SQLITE_FULL: Insertion failed because database is full"; - case SQLITE_CANTOPEN: - return "SQLITE_CANTOPEN: Unable to open the database file"; - case SQLITE_PROTOCOL: - return "SQLITE_PROTOCOL: Database lock protocol error"; - case SQLITE_EMPTY: - return "SQLITE_EMPTY: Internal use only"; - case SQLITE_SCHEMA: - return "SQLITE_SCHEMA: The database schema changed"; - case SQLITE_TOOBIG: - return "SQLITE_TOOBIG: String or BLOB exceeds size limit"; - case SQLITE_CONSTRAINT: - return "SQLITE_CONSTRAINT: Abort due to constraint violation"; - case SQLITE_MISMATCH: - return "SQLITE_MISMATCH: Data type mismatch"; - case SQLITE_MISUSE: - return "SQLITE_MISUSE: Library used incorrectly"; - case SQLITE_NOLFS: - return "SQLITE_NOLFS: Uses OS features not supported on host"; - case SQLITE_AUTH: - return "SQLITE_AUTH: Authorization denied"; - case SQLITE_FORMAT: - return "SQLITE_FORMAT: Not used"; - case SQLITE_RANGE: - return "SQLITE_RANGE: 2nd parameter to sqlite3_bind out of range"; - case SQLITE_NOTADB: - return "SQLITE_NOTADB: File opened that is not a database file"; - case SQLITE_NOTICE: - return "SQLITE_NOTICE: Notifications from sqlite3_log()"; - case SQLITE_WARNING: - return "SQLITE_WARNING: Warnings from sqlite3_log()"; - default: - return ""; - } -} diff --git a/src/infra/sqlite_fn.h b/src/infra/sqlite_fn.h deleted file mode 100644 index 45fed18..0000000 --- a/src/infra/sqlite_fn.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include "sqlite_base.h" - -/** - * @brief Opens a SQLite database file with extended options. - * - * @param filename The name of the database file to be opened (UTF-8 encoded). - * @param ppDb A pointer to a pointer that will receive the SQLite database - * handle upon successful opening. - * @param flags Flags that control the behavior of the database connection. - * @param zVfs The name of the VFS (Virtual File System) module to use. - * If NULL, the default VFS is used. - * @return Returns SQLITE_OK on success or an error code on failure. - */ -typedef int (*sqlite3_open_v2_t)(const char* filename, sqlite3** ppDb, int flags, const char* zVfs); - -/** - * @brief Compiles an SQL statement into a prepared statement. - * - * @param db Database handle. - * @param zSql SQL statement, UTF-8 encoded. - * @param n Maximum length of zSql in bytes. - * @param ppStmt OUT: Statement handle. - * @param pzTail OUT: Pointer to unused portion of zSql. - * @return Returns SQLITE_OK on success or an error code on failure. - */ -typedef int (*sqlite3_prepare_v2_t)(sqlite3* db, const char* zSql, int n, sqlite3_stmt** ppStmt, const char** pzTail); - -/** - * @brief Evaluates a prepared statement. - * - * @param stmt Prepared statement. - * @return Returns SQLITE_ROW, SQLITE_DONE, or an error code. - */ -typedef int (*sqlite3_step_t)(sqlite3_stmt* stmt); - -/** - * @brief Returns the text value of a column in the current row of a result set. - * - * @param stmt Prepared statement. - * @param iCol Column index. - * @return Text value of the column. - */ -typedef const unsigned char* (*sqlite3_column_text_t)(sqlite3_stmt* stmt, int iCol); - -/** - * @brief Destroys a prepared statement object. - * - * @param stmt Prepared statement. - * @return Returns SQLITE_OK on success or an error code on failure. - */ -typedef int (*sqlite3_finalize_t)(sqlite3_stmt* stmt); - -/** - * @brief Closes a database connection and invalidates all prepared statements. - * - * @param db Database handle. - * @return Returns SQLITE_OK on success or an error code on failure. - */ -typedef int (*sqlite3_close_v2_t)(sqlite3* db); - -/** - * @brief Sets the encryption key for a database. - * - * @param db Database to be keyed. - * @param pKey The key. - * @param nKey The length of the key in bytes. - * @return Returns SQLITE_OK on success or an error code on failure. - */ -typedef int (*sqlite3_key_t)(sqlite3* db, const void* pKey, int nKey); diff --git a/src/main.cpp b/src/main.cpp index c8f2bca..6a1d4d8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ +#include #include "infra/infra.h" -#include "infra/sqlite_error.h" #include "jobs.hpp" #include "utils/cli.h" @@ -41,7 +41,6 @@ void print_license() { void print_usage() { fputs( "Usage: kgg-dec " - "[--infra-dll infra.dll] " "[--scan-all-file-ext 0] " "[--db /path/to/KGMusicV3.db] " "[--suffix _kgg-dec] " @@ -64,15 +63,10 @@ int main() { bool scan_all_exts = cli_args.get_scan_all_file_ext(); - auto infra_dll_path = cli_args.get_infra_dll(); auto kgm_db_path = cli_args.get_db_path(); auto file_suffix = cli_args.get_file_suffix(); { bool cli_arg_error{false}; - if (!exists(infra_dll_path)) { - fputs("[ERR ] infra.dll not found\n", stderr); - cli_arg_error = true; - } if (!exists(kgm_db_path)) { fputs("[ERR ] KGMusicV3.db not found\n", stderr); @@ -83,18 +77,11 @@ int main() { } } - int error{-1}; - Infra::KugouDb db{infra_dll_path, kgm_db_path}; - if (!db.IsOpen()) { - fprintf(stderr, "[ERR ] db init error: is infra.dll ok?\n"); + kgm_ekey_db_t ekey_db; + if (const auto rc = Infra::dump_ekey(ekey_db, kgm_db_path); rc != 0) { + fprintf(stderr, "[ERR ] dump ekey failed %d (%s)", rc, sqlite3_errstr(rc)); return 1; } - auto ekey_db = db.dump_ekey(error); - if (error != 0) { - fprintf(stderr, "[ERR ] dump ekey failed %d (%s)", error, sqlite_get_error(error)); - return 1; - } - db.Close(); #ifndef NDEBUG fprintf(stderr, "ekey_db:\n"); diff --git a/src/tc_tea/tc_tea.cpp b/src/tc_tea/tc_tea.cpp index 5967177..66e5c9a 100644 --- a/src/tc_tea/tc_tea.cpp +++ b/src/tc_tea/tc_tea.cpp @@ -15,12 +15,12 @@ inline void decrypt_round(uint8_t* p_plain, uint64_t* iv1, uint64_t* iv2, const uint32_t* key) { - uint64_t iv1_next = Endian::be_u64_read(p_cipher); + uint64_t iv1_next = Endian::be_read(p_cipher); uint64_t iv2_next = tc_tea_ecb_decrypt(iv1_next ^ *iv2, key); uint64_t plain = iv2_next ^ *iv1; *iv1 = iv1_next; *iv2 = iv2_next; - Endian::be_u64_write(p_plain, plain); + Endian::be_write(p_plain, plain); } std::vector tc_tea_cbc_decrypt(std::span cipher, const uint32_t* key) { @@ -62,7 +62,7 @@ std::vector tc_tea_cbc_decrypt(std::span cipher, const uint32_ p_output[0] = header[kTeaBlockSize]; } // Validate zero padding - auto verify = Endian::be_u64_read(header + kTeaBlockSize) << 8; + auto verify = Endian::be_read(header + kTeaBlockSize) << 8; if (verify != 0) { result.resize(0); } diff --git a/src/tc_tea/tc_tea.h b/src/tc_tea/tc_tea.h index f02a2ca..93813b1 100644 --- a/src/tc_tea/tc_tea.h +++ b/src/tc_tea/tc_tea.h @@ -10,8 +10,8 @@ std::vector tc_tea_cbc_decrypt(std::span cipher, const uint32_ inline std::vector tc_tea_cbc_decrypt(std::span cipher, const uint8_t* key) { uint32_t key_u32[4]; - for (int i = 0; i < 4; i++) { - key_u32[i] = Endian::be_u32_read(key + i * 4); + for (int i = 0; i < 4; i++, key += 4) { + key_u32[i] = Endian::be_read(key); } return tc_tea_cbc_decrypt(cipher, key_u32); } diff --git a/src/utils/cli.cpp b/src/utils/cli.cpp index 73a4fc1..ffdff8e 100644 --- a/src/utils/cli.cpp +++ b/src/utils/cli.cpp @@ -47,11 +47,6 @@ void CliParser::parse_from_cli() { named_args_ = named_args; } -std::filesystem::path CliParser::get_infra_dll() const { - std::filesystem::path infra_dll_path{get_with_default(L"infra-dll", L"infra.dll")}; - return absolute(infra_dll_path); -} - std::filesystem::path CliParser::get_db_path() const { std::filesystem::path kugou_db{}; if (const auto& it = named_args_.find(L"db"); it != named_args_.end()) { diff --git a/third-party/aes/CMakeLists.txt b/third-party/aes/CMakeLists.txt new file mode 100644 index 0000000..9f024e9 --- /dev/null +++ b/third-party/aes/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.10) + +project(libaes VERSION 0.0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Tiny AES in C (https://github.com/kokke/tiny-AES-c/) is licensed under the Unlicense license. +add_library(libaes STATIC aes.cpp) +target_include_directories(libaes + PUBLIC + "$" + "$" +) diff --git a/third-party/aes/aes.cpp b/third-party/aes/aes.cpp new file mode 100644 index 0000000..adf1db9 --- /dev/null +++ b/third-party/aes/aes.cpp @@ -0,0 +1,357 @@ +#include "aes.h" + +#include + +#define Nb 4 +#define Nk 4 // The number of 32 bit words in a key. +#define Nr 10 // The number of rounds in AES Cipher. + +namespace AES { + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// state - array holding the intermediate results during decryption. +typedef uint8_t state_t[4][4]; + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const uint8_t sbox[256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, + 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, + 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, + 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, + 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, + 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, + 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, + 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, + 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, + 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, + 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, + 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, + 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, + 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16}; + +static const uint8_t rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, + 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, + 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, + 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, + 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, + 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, + 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, + 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, + 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, + 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, + 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, + 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, + 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, + 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d}; + +// The round constant word array, Rcon[i], contains the values given by +// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +static const uint8_t Rcon[11] = {0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36}; + +/* + * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), + * that you can remove most of the elements in the Rcon array, because they are unused. + * + * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon + * + * "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed), + * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." + */ + +inline uint8_t getSBoxValue(const uint8_t num) { + return sbox[num]; +} + +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) { + unsigned i, k; + uint8_t temp_arr[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (i = 0; i < Nk; ++i) { + RoundKey[i * 4 + 0] = Key[i * 4 + 0]; + RoundKey[i * 4 + 1] = Key[i * 4 + 1]; + RoundKey[i * 4 + 2] = Key[i * 4 + 2]; + RoundKey[i * 4 + 3] = Key[i * 4 + 3]; + } + + // All other "round keys" are found from the previous round keys. + for (i = Nk; i < Nb * (Nr + 1); ++i) { + { + k = (i - 1) * 4; + temp_arr[0] = RoundKey[k + 0]; + temp_arr[1] = RoundKey[k + 1]; + temp_arr[2] = RoundKey[k + 2]; + temp_arr[3] = RoundKey[k + 3]; + } + + if (i % Nk == 0) { + // This function shifts the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + const uint8_t u8tmp = temp_arr[0]; + temp_arr[0] = temp_arr[1]; + temp_arr[1] = temp_arr[2]; + temp_arr[2] = temp_arr[3]; + temp_arr[3] = u8tmp; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function SubWord() + { + temp_arr[0] = getSBoxValue(temp_arr[0]); + temp_arr[1] = getSBoxValue(temp_arr[1]); + temp_arr[2] = getSBoxValue(temp_arr[2]); + temp_arr[3] = getSBoxValue(temp_arr[3]); + } + + temp_arr[0] = temp_arr[0] ^ Rcon[i / Nk]; + } + + // AES256 code was here. + + const unsigned j = i * 4; + k = (i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ temp_arr[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ temp_arr[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ temp_arr[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ temp_arr[3]; + } +} + +void AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) { + KeyExpansion(ctx->RoundKey, key); + memcpy(ctx->Iv, iv, kBlockLen); +} + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) { + for (uint8_t i = 0; i < 4; ++i) { + for (uint8_t j = 0; j < 4; ++j) { + (*state)[i][j] ^= RoundKey[round * Nb * 4 + i * Nb + j]; + } + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +void SubBytes(state_t* state) { + for (uint8_t i = 0; i < 4; ++i) { + for (uint8_t j = 0; j < 4; ++j) { + (*state)[j][i] = getSBoxValue((*state)[j][i]); + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +void ShiftRows(state_t* state) { + // Rotate first row 1 column to left + uint8_t temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + // Rotate second row 2 columns to left + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to left + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +inline uint8_t xtime(uint8_t x) { + return x << 1 ^ (x >> 7 & 1) * 0x1b; +} + +// MixColumns function mixes the columns of the state matrix +void MixColumns(state_t* state) { + for (uint8_t i = 0; i < 4; ++i) { + uint8_t t = (*state)[i][0]; + uint8_t Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3]; + uint8_t Tm = (*state)[i][0] ^ (*state)[i][1]; + Tm = xtime(Tm); + (*state)[i][0] ^= Tm ^ Tmp; + Tm = (*state)[i][1] ^ (*state)[i][2]; + Tm = xtime(Tm); + (*state)[i][1] ^= Tm ^ Tmp; + Tm = (*state)[i][2] ^ (*state)[i][3]; + Tm = xtime(Tm); + (*state)[i][2] ^= Tm ^ Tmp; + Tm = (*state)[i][3] ^ t; + Tm = xtime(Tm); + (*state)[i][3] ^= Tm ^ Tmp; + } +} + +// Multiply is used to multiply numbers in the field GF(2^8) +// Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary +// The compiler seems to be able to vectorize the operation better this way. +// See https://github.com/kokke/tiny-AES-c/pull/34 +#if MULTIPLY_AS_A_FUNCTION +static uint8_t Multiply(uint8_t x, uint8_t y) { + return (((y & 1) * x) ^ ((y >> 1 & 1) * xtime(x)) ^ ((y >> 2 & 1) * xtime(xtime(x))) ^ + ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ + ((y >> 4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */ +} +#else +#define Multiply(x, y) \ + (((y & 1) * x) ^ ((y >> 1 & 1) * xtime(x)) ^ ((y >> 2 & 1) * xtime(xtime(x))) ^ \ + ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ ((y >> 4 & 1) * xtime(xtime(xtime(xtime(x)))))) + +#endif + +inline uint8_t getSBoxInvert(uint8_t num) { + return rsbox[num]; +} + +// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +void InvMixColumns(state_t* state) { + for (int i = 0; i < 4; ++i) { + uint8_t a = (*state)[i][0]; + uint8_t b = (*state)[i][1]; + uint8_t c = (*state)[i][2]; + uint8_t d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +void InvSubBytes(state_t* state) { + for (uint8_t i = 0; i < 4; ++i) { + for (uint8_t j = 0; j < 4; ++j) { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } +} + +void InvShiftRows(state_t* state) { + // Rotate first row 1 column to right + uint8_t temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + // Rotate second row 2 columns to right + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to right + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} + +// Cipher is the main function that encrypts the PlainText. +void Cipher(state_t* state, const uint8_t* RoundKey) { + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(0, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without MixColumns() + for (round = 1;; ++round) { + SubBytes(state); + ShiftRows(state); + if (round == Nr) { + break; + } + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + // Add round key to last round + AddRoundKey(Nr, state, RoundKey); +} + +void InvCipher(state_t* state, const uint8_t* RoundKey) { + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(Nr, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without InvMixColumn() + for (round = Nr - 1;; --round) { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + if (round == 0) { + break; + } + InvMixColumns(state); + } +} + +inline void XorWithIv(uint8_t* buf, const uint8_t* Iv) { + for (uint8_t i = 0; i < kBlockLen; ++i) // The block in AES is always 128bit no matter the key size + { + buf[i] ^= Iv[i]; + } +} + +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { + uint8_t* Iv = ctx->Iv; + for (size_t i = 0; i < length; i += kBlockLen) { + XorWithIv(buf, Iv); + Cipher(reinterpret_cast(buf), ctx->RoundKey); + Iv = buf; + buf += kBlockLen; + } + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, kBlockLen); +} + +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { + for (size_t i = 0; i < length; i += kBlockLen) { + uint8_t storeNextIv[kBlockLen]; + memcpy(storeNextIv, buf, kBlockLen); + InvCipher(reinterpret_cast(buf), ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, kBlockLen); + buf += kBlockLen; + } +} + +} // namespace AES diff --git a/third-party/aes/aes.h b/third-party/aes/aes.h new file mode 100644 index 0000000..c09e151 --- /dev/null +++ b/third-party/aes/aes.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace AES { + +constexpr size_t kKeyLen = 16; // Key length in bytes +constexpr size_t kKeyExpansionSize = 176; +constexpr size_t kBlockLen = 16; // Block length in bytes - AES is 128b block only + +struct AES_ctx { + uint8_t RoundKey[kKeyExpansionSize]; + uint8_t Iv[16]; +}; + +void AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); + +// buffer size MUST be mutile of AES_BLOCKLEN; +// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +} // namespace AES diff --git a/third-party/md5/CMakeLists.txt b/third-party/md5/CMakeLists.txt new file mode 100644 index 0000000..eb87679 --- /dev/null +++ b/third-party/md5/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.10) + +project(md5 VERSION 0.0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Derived from the "RSA Data Security, Inc. MD5 Message-Digest Algorithm": +# https://github.com/freebsd/freebsd-src/blob/release/14.2.0/sys/kern/md5c.c +add_library(libmd5 STATIC md5.cpp) +target_include_directories(libmd5 + PUBLIC + "$" + "$" +) diff --git a/third-party/md5/md5.cpp b/third-party/md5/md5.cpp new file mode 100644 index 0000000..e27c484 --- /dev/null +++ b/third-party/md5/md5.cpp @@ -0,0 +1,284 @@ +// Derived from the "RSA Data Security, Inc. MD5 Message-Digest Algorithm": +// src: https://github.com/freebsd/freebsd-src/blob/release/14.2.0/sys/kern/md5c.c + +#include +#include +#include + +#include "md5.h" + +#if defined(_MSC_VER) +#define bswap_u16 _byteswap_ushort +#define bswap_u32 _byteswap_ulong +#define bswap_u64 _byteswap_uint64 +#else +#define bswap_u16 __builtin_bswap16 +#define bswap_u32 __builtin_bswap32 +#define bswap_u64 __builtin_bswap64 +#endif + +template +void Encode(uint8_t* output, const T input) + requires(std::is_integral_v && (sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8)) +{ + if constexpr (std::endian::native == std::endian::little || sizeof(T) == 1) { + memcpy(output, &input, sizeof(T)); + // ReSharper disable once CppDFAUnreachableCode + } else if constexpr (sizeof(T) == 2) { + *reinterpret_cast(output) = bswap_u16(input); + } else if constexpr (sizeof(T) == 4) { + *reinterpret_cast(output) = bswap_u32(input); + } else if constexpr (sizeof(T) == 8) { + *reinterpret_cast(output) = bswap_u64(input); + } +} + +template +void Decode(T* output, const uint8_t* input) + requires(std::is_integral_v && (sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8)) +{ + if constexpr (std::endian::native == std::endian::little) { + memcpy(output, input, sizeof(T)); + // ReSharper disable once CppDFAUnreachableCode + } else if constexpr (sizeof(T) == 2) { + *output = bswap_u16(*reinterpret_cast(input)); + } else if constexpr (sizeof(T) == 4) { + *output = bswap_u32(*reinterpret_cast(input)); + } else if constexpr (sizeof(T) == 8) { + *output = bswap_u64(*reinterpret_cast(input)); + } +} + +inline void Encode(unsigned char* output, const uint32_t* input, const unsigned int len) { + if constexpr (std::endian::native == std::endian::little) { + memcpy(output, input, len); + } else { + // ReSharper disable once CppDFAUnreachableCode + for (unsigned int i = 0; i < len; i += 4, output += 4) { + Encode(output, input[i]); + } + } +} + +inline void Decode(uint32_t* output, const unsigned char* input, const unsigned int len) { + // ReSharper disable once CppDFAUnreachableCode + if constexpr (std::endian::native == std::endian::little) { + memcpy(output, input, len); + } else { + for (unsigned int i = 0; i < len; i += 4, ++output) { + Decode(output, &input[i]); + } + } +} + +void MD5Transform(uint32_t state[4], const unsigned char block[64]); + +static unsigned char PADDING[64] = {0x80}; + +/* F, G, H and I are basic MD5 functions. */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +/* + * FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. + * Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) \ + { \ + (a) += F((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) \ + { \ + (a) += G((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) \ + { \ + (a) += H((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) \ + { \ + (a) += I((b), (c), (d)) + (x) + (uint32_t)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } + +/* + * MD5 block update operation. Continues an MD5 message-digest + * operation, processing another message block, and updating the + * context. + */ + +void md5_update(MD5_CTX* ctx, const uint8_t* in, const size_t len) { + unsigned int i{0}; + const unsigned char* input = in; + + /* Compute number of bytes mod 64 */ + unsigned int index = ctx->count % 64; + ctx->count += len; + + // ReSharper disable once CppTooWideScopeInitStatement + const unsigned int partLen = 64 - index; + + /* Transform as many times as possible. */ + if (len >= partLen) { + memcpy(&ctx->buffer[index], input, partLen); + MD5Transform(ctx->state, ctx->buffer); + + for (i = partLen; i + 63 < len; i += 64) { + MD5Transform(ctx->state, &input[i]); + } + + index = 0; + } + + /* Buffer remaining input */ + memcpy(&ctx->buffer[index], &input[i], len - i); +} + +/* + * MD5 padding. Adds padding followed by original length. + */ + +static void MD5Pad(MD5_CTX* context) { + unsigned char bits[8]; + + /* Save number of bits */ + Encode(bits, context->count << 3); + + /* Pad out to 56 mod 64. */ + const unsigned int index = context->count % 64; + const unsigned int padLen = index < 56 ? 56 - index : 120 - index; + md5_update(context, PADDING, padLen); + + /* Append length (before padding) */ + md5_update(context, bits, 8); +} + +/* + * MD5 finalization. Ends an MD5 message-digest operation, writing + * the message digest and zeroizing the context. + */ +void md5_final(MD5_CTX* ctx, uint8_t* digest) { + /* Do padding. */ + MD5Pad(ctx); + + /* Store state in digest */ + Encode(digest, ctx->state, MD5_DIGEST_LENGTH); + + /* Zeroize sensitive information. */ + memset(ctx, 0, sizeof(*ctx)); +} + +/* MD5 basic transformation. Transforms state based on block. */ +void MD5Transform(uint32_t state[4], const unsigned char block[64]) { + uint32_t a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode(x, block, 64); + + /* Round 1 */ + constexpr int S11 = 7; + constexpr int S12 = 12; + constexpr int S13 = 17; + constexpr int S14 = 22; + FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */ + FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */ + FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */ + FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */ + FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */ + FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */ + FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */ + FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */ + FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */ + FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */ + FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + constexpr int S21 = 5; + constexpr int S22 = 9; + constexpr int S23 = 14; + constexpr int S24 = 20; + GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */ + GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */ + GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */ + GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */ + GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */ + GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */ + GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */ + GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */ + GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */ + GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */ + GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + constexpr int S31 = 4; + constexpr int S32 = 11; + constexpr int S33 = 16; + constexpr int S34 = 23; + HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */ + HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */ + HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */ + HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */ + HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */ + HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */ + HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */ + HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */ + HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */ + HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + constexpr int S41 = 6; + constexpr int S42 = 10; + constexpr int S43 = 15; + constexpr int S44 = 21; + II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */ + II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */ + II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */ + II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */ + II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */ + II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */ + II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */ + II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */ + II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */ + II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. */ + memset(x, 0, sizeof(x)); +} diff --git a/third-party/md5/md5.h b/third-party/md5/md5.h new file mode 100644 index 0000000..0ebf3bc --- /dev/null +++ b/third-party/md5/md5.h @@ -0,0 +1,46 @@ +#pragma once + +// Derived from the "RSA Data Security, Inc. MD5 Message-Digest Algorithm": +// src: https://github.com/freebsd/freebsd-src/blob/release/14.2.0/sys/kern/md5c.c + +#include + +#define MD5_BLOCK_LENGTH 64 +#define MD5_DIGEST_LENGTH 16 +#define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH * 2 + 1) + +/* MD5 context. */ +struct MD5_CTX { + uint64_t count; /* number of bits, modulo 2^64 (lsb first) */ + uint32_t state[4]; /* state (ABCD) */ + unsigned char buffer[64]; /* input buffer */ +}; + +/* MD5 initialization. Begins an MD5 operation, writing a new context. */ +inline void md5_init(MD5_CTX* context) { + context->count = 0; + + /* Load magic initialization constants. */ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +void md5_update(MD5_CTX* ctx, const uint8_t* in, size_t len); +void md5_final(MD5_CTX* ctx, uint8_t* digest); + +inline void md5(uint8_t* digest, const uint8_t* in, const size_t len) { + MD5_CTX ctx; + md5_init(&ctx); + md5_update(&ctx, in, len); + md5_final(&ctx, digest); +} + +inline void md5(uint8_t* digest, const uint8_t* in, const size_t len, const uint8_t* in2, size_t len2) { + MD5_CTX ctx; + md5_init(&ctx); + md5_update(&ctx, in, len); + md5_update(&ctx, in2, len2); + md5_final(&ctx, digest); +} diff --git a/third-party/sqlite3/.gitignore b/third-party/sqlite3/.gitignore new file mode 100644 index 0000000..811708e --- /dev/null +++ b/third-party/sqlite3/.gitignore @@ -0,0 +1,2 @@ +sqlite-*/ +sqlite-*.zip diff --git a/third-party/sqlite3/CMakeLists.txt b/third-party/sqlite3/CMakeLists.txt new file mode 100644 index 0000000..f77d58d --- /dev/null +++ b/third-party/sqlite3/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.10) + +project(sqlite VERSION 0.0.1 LANGUAGES C) + +# SQLite3 is in the public domain, see https://www.sqlite.org/copyright.html +add_library(sqlite3 STATIC sqlite-amalgamation-3470200/sqlite3.c) +target_include_directories(sqlite3 + PUBLIC + "$" + "$" +) diff --git a/third-party/sqlite3/fetch_sqlite3.sh b/third-party/sqlite3/fetch_sqlite3.sh new file mode 100644 index 0000000..61f005b --- /dev/null +++ b/third-party/sqlite3/fetch_sqlite3.sh @@ -0,0 +1,11 @@ +#!/bin/sh -ex + +NAME="sqlite-amalgamation-3470200" + +if ! sha256sum -c sqlite3.sha256sum; then + rm -f sqlite3-*.zip + curl -fsLO "https://www.sqlite.org/2024/$NAME.zip" + sha256sum -c sqlite3.sha256sum || exit 1 +fi + +unzip -n "$NAME.zip" diff --git a/third-party/sqlite3/sqlite3.sha256sum b/third-party/sqlite3/sqlite3.sha256sum new file mode 100644 index 0000000..48d9bbc --- /dev/null +++ b/third-party/sqlite3/sqlite3.sha256sum @@ -0,0 +1 @@ +aa73d8748095808471deaa8e6f34aa700e37f2f787f4425744f53fdd15a89c40 sqlite-amalgamation-3470200.zip