From 1d7e96f3a4d36af9267ec2d2dc07075295058f5c Mon Sep 17 00:00:00 2001 From: "Scott E. Graves" Date: Tue, 26 Aug 2025 20:08:47 -0500 Subject: [PATCH] updated build system --- CMakeLists.txt | 3 + cmake/functions.cmake | 10 ++ cmake/libraries.cmake | 4 +- cmake/libraries/boost.cmake | 4 + cmake/libraries/icu.cmake | 24 ++++ scripts/make_common.sh | 15 ++- support/3rd_party/icu_configure.sh | 16 +++ support/include/utils/com_init_wrapper.hpp | 2 +- support/include/utils/config.hpp | 4 +- support/include/utils/encrypting_reader.hpp | 2 +- support/include/utils/encryption.hpp | 20 +-- support/include/utils/hash.hpp | 62 +++++++++ support/include/utils/windows.hpp | 12 +- support/src/utils/encryption.cpp | 24 ++-- support/src/utils/hash.cpp | 51 ++++++++ support/src/utils/string.cpp | 101 ++++++++++++-- support/src/utils/windows.cpp | 138 +++++++++++++++++++- support/test/src/utils/hash_test.cpp | 66 ++++++++++ support/test/src/utils/string_test.cpp | 6 + support/test/src/utils/ttl_cache_test.cpp | 4 +- 20 files changed, 521 insertions(+), 47 deletions(-) create mode 100644 cmake/libraries/icu.cmake create mode 100644 support/3rd_party/icu_configure.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b7d2366..4570eb35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,9 @@ if(PROJECT_BUILD) @ONLY ) endif() + + find_package(ICU REQUIRED COMPONENTS uc i18n io) + link_libraries(ICU::uc ICU::i18n ICU::io) else() message(STATUS "-=[CMake Settings]=-") message(STATUS " C standard: ${CMAKE_C_STANDARD}") diff --git a/cmake/functions.cmake b/cmake/functions.cmake index b289fa32..2a6a08eb 100644 --- a/cmake/functions.cmake +++ b/cmake/functions.cmake @@ -16,6 +16,16 @@ function(set_common_target_options name) ${PROJECT_EXTERNAL_BUILD_ROOT}/lib ) + if (PROJECT_STATIC_LINK) + target_compile_definitions(${name} PRIVATE U_STATIC_IMPLEMENTATION) + endif() + + target_link_directories(${name} PRIVATE + ICU::uc + ICU::i18n + ICU::io + ) + target_include_directories(${name} AFTER PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}/${name}/include ${name}_INCLUDES diff --git a/cmake/libraries.cmake b/cmake/libraries.cmake index 0ee31fa6..cb820b7d 100644 --- a/cmake/libraries.cmake +++ b/cmake/libraries.cmake @@ -10,11 +10,11 @@ else() set(ZLIB_USE_STATIC_LIBS ${PROJECT_STATIC_LINK}) endif() set(wxWidgets_USE_STATIC ${PROJECT_STATIC_LINK}) +set(ICU_USE_STATIC_LIBS ${PROJECT_STATIC_LINK}) +include(cmake/libraries/icu.cmake) include(cmake/libraries/openssl.cmake) - include(cmake/libraries/boost.cmake) - include(cmake/libraries/cpp_httplib.cmake) include(cmake/libraries/curl.cmake) include(cmake/libraries/fuse.cmake) diff --git a/cmake/libraries/boost.cmake b/cmake/libraries/boost.cmake index 7997d191..fb68bc0b 100644 --- a/cmake/libraries/boost.cmake +++ b/cmake/libraries/boost.cmake @@ -141,6 +141,10 @@ if(PROJECT_ENABLE_BOOST) list(APPEND PROJECT_DEPENDENCIES boost_project) + if (PROJECT_IS_DARWIN) + add_dependencies(boost_project icu_project) + endif() + if (NOT CMAKE_HOST_WIN32) add_dependencies(boost_project openssl_project) endif() diff --git a/cmake/libraries/icu.cmake b/cmake/libraries/icu.cmake new file mode 100644 index 00000000..f278b9d6 --- /dev/null +++ b/cmake/libraries/icu.cmake @@ -0,0 +1,24 @@ +if(PROJECT_IS_DARWIN AND NOT PROJECT_BUILD) + if(PROJECT_BUILD_SHARED_LIBS) + set(ICU_ENABLE_SHARED yes) + else() + set(ICU_ENABLE_SHARED no) + endif() + + ExternalProject_Add(icu_project + PREFIX external + URL ${PROJECT_3RD_PARTY_DIR}/mingw64/icu-release-${ICU_VERSION}.tar.gz + URL_HASH SHA256=${ICU_HASH} + BUILD_IN_SOURCE 1 + LIST_SEPARATOR | + PATCH_COMMAND chmod +x ${PROJECT_3RD_PARTY_DIR}/icu_configure.sh + CONFIGURE_COMMAND cd icu4c/source && ${PROJECT_3RD_PARTY_DIR}/icu_configure.sh + ${PROJECT_MARCH} + ${PROJECT_EXTERNAL_BUILD_ROOT} + ${ICU_ENABLE_SHARED} + BUILD_COMMAND cd icu4c/source && make -j$ENV{CMAKE_BUILD_PARALLEL_LEVEL} + INSTALL_COMMAND cd icu4c/source && make install + ) + + list(APPEND PROJECT_DEPENDENCIES icu_project) +endif() diff --git a/scripts/make_common.sh b/scripts/make_common.sh index c43d15a7..f4ae5b91 100755 --- a/scripts/make_common.sh +++ b/scripts/make_common.sh @@ -69,7 +69,19 @@ for APP in ${PROJECT_APP_LIST[@]}; do mv "${PROJECT_DIST_DIR}/cacert.pem" "${PROJECT_DIST_DIR}/bin/cacert.pem" fi rsync -av --progress "${PROJECT_BUILD_DIR}/build/${APP}${PROJECT_APP_BINARY_EXT}" "${PROJECT_DIST_DIR}/bin/" - cat <>"${PROJECT_DIST_DIR}/${APP}${PROJECT_APP_BINARY_EXT}" + if [ "${PROJECT_IS_DARWIN}" == "1" ]; then + cat <>"${PROJECT_DIST_DIR}/${APP}${PROJECT_APP_BINARY_EXT}" +#!/bin/sh +PROJECT_SCRIPTS_DIR=\$(realpath "\$0") +PROJECT_SCRIPTS_DIR=\$(dirname "\${PROJECT_SCRIPTS_DIR}") + +DYLD_LIBRARY_PATH="\${PROJECT_SCRIPTS_DIR}/lib:\${PROJECT_SCRIPTS_DIR}/lib64:\${DYLD_LIBRARY_PATH}" +export DYLD_LIBRARY_PATH + +\${PROJECT_SCRIPTS_DIR}/bin/${APP}${PROJECT_APP_BINARY_EXT} \$* +EOF + else + cat <>"${PROJECT_DIST_DIR}/${APP}${PROJECT_APP_BINARY_EXT}" #!/bin/sh PROJECT_SCRIPTS_DIR=\$(realpath "\$0") PROJECT_SCRIPTS_DIR=\$(dirname "\${PROJECT_SCRIPTS_DIR}") @@ -78,6 +90,7 @@ export LD_LIBRARY_PATH="\${PROJECT_SCRIPTS_DIR}/lib:\${PROJECT_SCRIPTS_DIR}/lib6 \${PROJECT_SCRIPTS_DIR}/bin/${APP}${PROJECT_APP_BINARY_EXT} \$* EOF + fi chmod +x "${PROJECT_DIST_DIR}/${APP}${PROJECT_APP_BINARY_EXT}" fi elif [ ! -d "${PROJECT_BUILD_DIR}/build/${APP}.app" ]; then diff --git a/support/3rd_party/icu_configure.sh b/support/3rd_party/icu_configure.sh new file mode 100644 index 00000000..0dcaa5fb --- /dev/null +++ b/support/3rd_party/icu_configure.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +if [ "$(uname -m)" == "arm64" ] && + [ "${PROJECT_IS_ARM64}" == "0" ]; then + HOST_CFG="--host=x86_64-apple-darwin" + export CC="clang -arch x86_64" + export CXX="clang++ -arch x86_64" +fi + +CXXFLAGS="-std=gnu++17 -march=$1 -mtune=generic" ./configure \ + --disable-samples \ + --disable-tests \ + --enable-shared=$3 \ + --enable-static=yes \ + --prefix="$2" \ + ${HOST_CFG} diff --git a/support/include/utils/com_init_wrapper.hpp b/support/include/utils/com_init_wrapper.hpp index 2195eaf3..d65c32ee 100644 --- a/support/include/utils/com_init_wrapper.hpp +++ b/support/include/utils/com_init_wrapper.hpp @@ -46,7 +46,7 @@ struct com_init_wrapper final { [[nodiscard]] auto is_initialized() const -> bool { return initialized_; } private: - BOOL initialized_; + BOOL initialized_{}; }; } // namespace repertory::utils diff --git a/support/include/utils/config.hpp b/support/include/utils/config.hpp index 02b556e3..0d7348bc 100644 --- a/support/include/utils/config.hpp +++ b/support/include/utils/config.hpp @@ -107,7 +107,6 @@ #include #include #include -#include #include #include #include @@ -149,6 +148,9 @@ #include #endif // defined(__cplusplus) +#include +#include + #if defined(PROJECT_ENABLE_CURL) #include "curl/curl.h" #include "curl/multi.h" diff --git a/support/include/utils/encrypting_reader.hpp b/support/include/utils/encrypting_reader.hpp index 5fd76fd6..0170589c 100644 --- a/support/include/utils/encrypting_reader.hpp +++ b/support/include/utils/encrypting_reader.hpp @@ -97,7 +97,7 @@ private: private: std::unordered_map chunk_buffers_; - std::optional> kdf_header_; + std::optional kdf_header_; std::size_t last_data_chunk_{}; std::size_t last_data_chunk_size_{}; std::uint64_t read_offset_{}; diff --git a/support/include/utils/encryption.hpp b/support/include/utils/encryption.hpp index 456f9c72..118bd61a 100644 --- a/support/include/utils/encryption.hpp +++ b/support/include/utils/encryption.hpp @@ -101,27 +101,19 @@ struct kdf_config final { salt_t salt{}; std::uint64_t checksum{}; - [[nodiscard]] static constexpr auto size() -> std::size_t { - return sizeof(kdf_config); - } + [[nodiscard]] static auto from_header(std::span data, + kdf_config &cfg) -> bool; [[nodiscard]] auto generate_checksum() const -> std::uint64_t; void generate_salt(); - [[nodiscard]] static auto from_header(std::span data, - kdf_config &cfg) -> bool; - - [[nodiscard]] auto to_header() -> auto { - kdf_config tmp{*this}; - tmp.checksum = boost::endian::native_to_big(tmp.checksum); - tmp.magic = boost::endian::native_to_big(tmp.magic); - - std::array ret{}; - std::memcpy(ret.data(), &tmp, size()); - return ret; + [[nodiscard]] static constexpr auto size() -> std::size_t { + return sizeof(kdf_config); } + [[nodiscard]] auto to_header() const -> data_buffer; + [[nodiscard]] auto operator==(const kdf_config &) const -> bool = default; [[nodiscard]] auto operator!=(const kdf_config &) const -> bool = default; }; diff --git a/support/include/utils/hash.hpp b/support/include/utils/hash.hpp index a1577297..8f92f8f7 100644 --- a/support/include/utils/hash.hpp +++ b/support/include/utils/hash.hpp @@ -28,10 +28,33 @@ #include "utils/error.hpp" namespace repertory::utils::hash { +using hash_32_t = std::array; +using hash_64_t = std::array; +using hash_128_t = std::array; using hash_256_t = std::array; using hash_384_t = std::array; using hash_512_t = std::array; +[[nodiscard]] auto create_hash_blake2b_32(std::string_view data) -> hash_32_t; + +[[nodiscard]] auto create_hash_blake2b_32(std::wstring_view data) -> hash_32_t; + +[[nodiscard]] auto create_hash_blake2b_32(const data_buffer &data) -> hash_32_t; + +[[nodiscard]] auto create_hash_blake2b_64(std::string_view data) -> hash_64_t; + +[[nodiscard]] auto create_hash_blake2b_64(std::wstring_view data) -> hash_64_t; + +[[nodiscard]] auto create_hash_blake2b_64(const data_buffer &data) -> hash_64_t; + +[[nodiscard]] auto create_hash_blake2b_128(std::string_view data) -> hash_128_t; + +[[nodiscard]] auto create_hash_blake2b_128(std::wstring_view data) + -> hash_128_t; + +[[nodiscard]] auto create_hash_blake2b_128(const data_buffer &data) + -> hash_128_t; + [[nodiscard]] auto create_hash_blake2b_256(std::string_view data) -> hash_256_t; [[nodiscard]] auto create_hash_blake2b_256(std::wstring_view data) @@ -123,6 +146,27 @@ auto create_hash_blake2b_t(const unsigned char *data, std::size_t data_size) return hash; } +inline const std::function + blake2b_32_hasher = + [](const unsigned char *data, std::size_t data_size) -> hash_32_t { + return create_hash_blake2b_t(data, data_size); +}; + +inline const std::function + blake2b_64_hasher = + [](const unsigned char *data, std::size_t data_size) -> hash_64_t { + return create_hash_blake2b_t(data, data_size); +}; + +inline const std::function + blake2b_128_hasher = + [](const unsigned char *data, std::size_t data_size) -> hash_128_t { + return create_hash_blake2b_t(data, data_size); +}; + inline const std::function blake2b_256_hasher = @@ -158,6 +202,24 @@ inline const std::function +[[nodiscard]] inline auto default_create_hash() -> const + std::function & { + return blake2b_32_hasher; +} + +template <> +[[nodiscard]] inline auto default_create_hash() -> const + std::function & { + return blake2b_64_hasher; +} + +template <> +[[nodiscard]] inline auto default_create_hash() -> const + std::function & { + return blake2b_128_hasher; +} + template <> [[nodiscard]] inline auto default_create_hash() -> const std::function & { diff --git a/support/include/utils/windows.hpp b/support/include/utils/windows.hpp index b380f4b8..35017b7f 100644 --- a/support/include/utils/windows.hpp +++ b/support/include/utils/windows.hpp @@ -34,13 +34,23 @@ void free_console(); [[nodiscard]] auto get_last_error_code() -> DWORD; +[[nodiscard]] auto get_startup_folder() -> std::wstring; + [[nodiscard]] auto get_thread_id() -> std::uint64_t; [[nodiscard]] auto is_process_elevated() -> bool; [[nodiscard]] auto run_process_elevated(std::vector args) -> int; -void set_last_error_code(DWORD errorCode); +[[nodiscard]] +auto create_shortcut(const std::wstring &exe_path, + const std::wstring &arguments, + const std::wstring &working_directory, + const std::wstring &shortcut_name = L"", + const std::wstring &location = get_startup_folder()) + -> bool; + +void set_last_error_code(DWORD error_code); } // namespace repertory::utils #endif // defined(_WIN32) diff --git a/support/src/utils/encryption.cpp b/support/src/utils/encryption.cpp index 58dd90c1..f7b281bd 100644 --- a/support/src/utils/encryption.cpp +++ b/support/src/utils/encryption.cpp @@ -25,9 +25,19 @@ #include "utils/collection.hpp" #include "utils/encrypting_reader.hpp" +#include "utils/hash.hpp" #include "utils/path.hpp" namespace repertory::utils::encryption { +auto kdf_config::to_header() const -> data_buffer { + kdf_config tmp{*this}; + tmp.checksum = boost::endian::native_to_big(tmp.checksum); + tmp.magic = boost::endian::native_to_big(tmp.magic); + + data_buffer ret(size()); + std::memcpy(ret.data(), &tmp, ret.size()); + return ret; +} auto kdf_config::generate_checksum() const -> std::uint64_t { REPERTORY_USES_FUNCTION_NAME(); @@ -35,18 +45,8 @@ auto kdf_config::generate_checksum() const -> std::uint64_t { kdf_config tmp = *this; tmp.checksum = 0; - auto hdr = tmp.to_header(); - - std::uint64_t ret{0}; - if (crypto_generichash(reinterpret_cast(&ret), sizeof(ret), - hdr.data(), hdr.size(), nullptr, 0) != 0) { - throw utils::error::create_exception(function_name, - { - "failed to calculate checksum", - }); - } - - return ret; + auto hash = utils::hash::create_hash_blake2b_64(tmp.to_header()); + return *reinterpret_cast(hash.data()); } void kdf_config::generate_salt() { diff --git a/support/src/utils/hash.cpp b/support/src/utils/hash.cpp index 53b566b3..9594bb80 100644 --- a/support/src/utils/hash.cpp +++ b/support/src/utils/hash.cpp @@ -26,6 +26,57 @@ #include "utils/error.hpp" namespace repertory::utils::hash { +auto create_hash_blake2b_32(std::string_view data) -> hash_32_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), data.size()); +} + +auto create_hash_blake2b_32(std::wstring_view data) -> hash_32_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(wchar_t)); +} + +auto create_hash_blake2b_32(const data_buffer &data) -> hash_32_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(data_buffer::value_type)); +} + +auto create_hash_blake2b_64(std::string_view data) -> hash_64_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), data.size()); +} + +auto create_hash_blake2b_64(std::wstring_view data) -> hash_64_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(wchar_t)); +} + +auto create_hash_blake2b_64(const data_buffer &data) -> hash_64_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(data_buffer::value_type)); +} + +auto create_hash_blake2b_128(std::string_view data) -> hash_128_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), data.size()); +} + +auto create_hash_blake2b_128(std::wstring_view data) -> hash_128_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(wchar_t)); +} + +auto create_hash_blake2b_128(const data_buffer &data) -> hash_128_t { + return create_hash_blake2b_t( + reinterpret_cast(data.data()), + data.size() * sizeof(data_buffer::value_type)); +} + auto create_hash_blake2b_256(std::string_view data) -> hash_256_t { return create_hash_blake2b_t( reinterpret_cast(data.data()), data.size()); diff --git a/support/src/utils/string.cpp b/support/src/utils/string.cpp index 543b10dd..c3641e99 100644 --- a/support/src/utils/string.cpp +++ b/support/src/utils/string.cpp @@ -36,10 +36,49 @@ auto from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string { #endif // defined(PROJECT_ENABLE_BOOST) auto from_utf8(std::string_view str) -> std::wstring { - return str.empty() - ? L"" - : std::wstring_convert, wchar_t>() - .from_bytes(std::string{str}); + if (str.empty()) { + return L""; + } + + std::wstring out; + + const auto *str_ptr = reinterpret_cast(str.data()); + std::int32_t idx{}; + auto len{static_cast(str.size())}; + +#if WCHAR_MAX <= 0xFFFF + out.reserve((str.size() + 1U) / 2U); + while (idx < len) { + UChar32 uni_ch{}; + U8_NEXT(str_ptr, idx, len, uni_ch); + if (uni_ch < 0 || !U_IS_UNICODE_CHAR(uni_ch)) { + throw std::runtime_error("from_utf8: invalid UTF-8 sequence"); + } + std::array units{}; + std::int32_t off{}; + auto err{false}; + U16_APPEND(units.data(), off, 2, uni_ch, err); + if (err || off <= 0) { + throw std::runtime_error("from_utf8: U16_APPEND failed"); + } + out.push_back(static_cast(units[0U])); + if (off == 2) { + out.push_back(static_cast(units[1U])); + } + } +#else // WCHAR_MAX > 0xFFFF + out.reserve(str.size()); + while (idx < len) { + UChar32 uni_ch{}; + U8_NEXT(str_ptr, idx, len, uni_ch); + if (uni_ch < 0 || !U_IS_UNICODE_CHAR(uni_ch)) { + throw std::runtime_error("from_utf8: invalid UTF-8 sequence"); + } + out.push_back(static_cast(uni_ch)); + } +#endif // WCHAR_MAX <= 0xFFFF + + return out; } #if defined(PROJECT_ENABLE_SFML) @@ -55,8 +94,8 @@ auto replace_sf(sf::String &src, const sf::String &find, const sf::String &with, return src; } -auto split_sf(sf::String str, wchar_t delim, - bool should_trim) -> std::vector { +auto split_sf(sf::String str, wchar_t delim, bool should_trim) + -> std::vector { auto result = std::views::split(str.toWideString(), delim); std::vector ret{}; @@ -130,9 +169,51 @@ auto to_uint64(const std::string &val) -> std::uint64_t { auto to_utf8(std::string_view str) -> std::string { return std::string{str}; } auto to_utf8(std::wstring_view str) -> std::string { - return str.empty() - ? "" - : std::wstring_convert, wchar_t>() - .to_bytes(std::wstring{str}); + if (str.empty()) { + return ""; + } + + std::string out; + out.reserve(static_cast(str.size()) * 4); + +#if WCHAR_MAX <= 0xFFFF + const auto *u16 = reinterpret_cast(str.data()); + std::int32_t idx{}; + auto len{static_cast(str.size())}; + while (idx < len) { + UChar32 uni_ch{}; + U16_NEXT(u16, idx, len, uni_ch); + if (uni_ch < 0 || !U_IS_UNICODE_CHAR(uni_ch)) { + throw std::runtime_error("to_utf8: invalid UTF-16 sequence"); + } + std::array buf{}; + std::int32_t off{0}; + auto err{false}; + U8_APPEND(buf.data(), off, U8_MAX_LENGTH, uni_ch, err); + if (err || off <= 0) { + throw std::runtime_error("to_utf8: U8_APPEND failed"); + } + out.append(reinterpret_cast(buf.data()), + static_cast(off)); + } +#else // WCHAR_MAX > 0xFFFF + for (auto cur_ch : str) { + auto uni_char{static_cast(cur_ch)}; + if (!U_IS_UNICODE_CHAR(uni_char)) { + throw std::runtime_error("to_utf8: invalid Unicode scalar value"); + } + std::array buf{}; + std::int32_t off{0}; + auto err{false}; + U8_APPEND(buf.data(), off, U8_MAX_LENGTH, uni_char, err); + if (err || off <= 0) { + throw std::runtime_error("to_utf8: U8_APPEND failed"); + } + out.append(reinterpret_cast(buf.data()), + static_cast(off)); + } +#endif // WCHAR_MAX <= 0xFFFF + + return out; } } // namespace repertory::utils::string diff --git a/support/src/utils/windows.cpp b/support/src/utils/windows.cpp index e7dd8a98..46a42ea5 100644 --- a/support/src/utils/windows.cpp +++ b/support/src/utils/windows.cpp @@ -23,7 +23,6 @@ #include "utils/windows.hpp" -#include "utils/com_init_wrapper.hpp" #include "utils/error.hpp" #include "utils/string.hpp" @@ -65,7 +64,6 @@ auto get_local_app_data_directory() -> const std::string & { REPERTORY_USES_FUNCTION_NAME(); static std::string app_data = ([]() -> std::string { - com_init_wrapper cw; PWSTR local_app_data{}; if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &local_app_data))) { @@ -139,6 +137,142 @@ auto run_process_elevated(std::vector args) -> int { } void set_last_error_code(DWORD error_code) { ::SetLastError(error_code); } + +auto get_startup_folder() -> std::wstring { + PWSTR raw{nullptr}; + auto result = ::SHGetKnownFolderPath(FOLDERID_Startup, 0, nullptr, &raw); + if (FAILED(result)) { + if (raw != nullptr) { + ::CoTaskMemFree(raw); + } + + return {}; + } + + std::wstring str{raw}; + ::CoTaskMemFree(raw); + return str; +} + +auto create_shortcut(const std::wstring &exe_path, + const std::wstring &arguments, + const std::wstring &working_directory, + const std::wstring &shortcut_name, std::wstring location) + -> bool { + REPERTORY_USES_FUNCTION_NAME(); + + const auto hr_hex = [](HRESULT hr) -> std::string { + std::ostringstream oss; + oss << "0x" << std::uppercase << std::hex << std::setw(8) + << std::setfill('0') << static_cast(hr); + return oss.str(); + }; + + if (location.empty()) { + utils::error::handle_error(function_name, "Shortcut location was empty."); + return false; + } + + { + std::error_code ec_mk; + std::filesystem::create_directories(std::filesystem::path{location}, ec_mk); + } + + std::filesystem::path exe_p{exe_path}; + std::wstring final_name = shortcut_name.empty() + ? (exe_p.stem().wstring() + L".lnk") + : shortcut_name; + if (not final_name.ends_with(L".lnk")) { + final_name += L".lnk"; + } + + const std::filesystem::path lnk_path = + std::filesystem::path{location} / final_name; + + IShellLinkW *psl{nullptr}; + HRESULT result = ::CoCreateInstance(CLSID_ShellLink, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl)); + if (FAILED(result)) { + utils::error::handle_error( + function_name, + std::string("CoCreateInstance(CLSID_ShellLink) failed: ") + + hr_hex(result)); + return false; + } + + result = psl->SetPath(exe_path.c_str()); + if (FAILED(result)) { + utils::error::handle_error(function_name, + std::string("IShellLink::SetPath failed: ") + + hr_hex(result)); + psl->Release(); + return false; + } + + if (not arguments.empty()) { + result = psl->SetArguments(arguments.c_str()); + if (FAILED(result)) { + utils::error::handle_error( + function_name, + std::string("IShellLink::SetArguments failed: ") + hr_hex(result)); + psl->Release(); + return false; + } + } + + if (not working_directory.empty()) { + result = psl->SetWorkingDirectory(working_directory.c_str()); + if (FAILED(result)) { + utils::error::handle_error( + function_name, + std::string("IShellLink::SetWorkingDirectory failed: ") + + hr_hex(result)); + psl->Release(); + return false; + } + } + + result = psl->SetShowCmd(SW_SHOWNORMAL); + if (FAILED(result)) { + utils::error::handle_error(function_name, + std::string("IShellLink::SetShowCmd failed: ") + + hr_hex(result)); + psl->Release(); + return false; + } + + // Best-effort overwrite + { + std::error_code ec; + std::filesystem::remove(lnk_path, ec); + } + + IPersistFile *ppf{nullptr}; + result = psl->QueryInterface(IID_PPV_ARGS(&ppf)); + if (FAILED(result)) { + utils::error::handle_error( + function_name, + std::string("QueryInterface(IPersistFile) failed: ") + hr_hex(result)); + psl->Release(); + return false; + } + + result = ppf->Save(lnk_path.c_str(), TRUE); + ppf->SaveCompleted(lnk_path.c_str()); + + ppf->Release(); + psl->Release(); + + if (FAILED(result)) { + utils::error::handle_error(function_name, + std::string("IPersistFile::Save failed: ") + + hr_hex(result)); + return false; + } + + return true; +} + } // namespace repertory::utils #endif // defined(_WIN32) diff --git a/support/test/src/utils/hash_test.cpp b/support/test/src/utils/hash_test.cpp index 66d2a613..dc5dcda1 100644 --- a/support/test/src/utils/hash_test.cpp +++ b/support/test/src/utils/hash_test.cpp @@ -25,12 +25,24 @@ namespace repertory { TEST(utils_hash, hash_type_sizes) { + EXPECT_EQ(4U, utils::hash::hash_32_t{}.size()); + EXPECT_EQ(8U, utils::hash::hash_64_t{}.size()); + EXPECT_EQ(16U, utils::hash::hash_128_t{}.size()); EXPECT_EQ(32U, utils::hash::hash_256_t{}.size()); EXPECT_EQ(48U, utils::hash::hash_384_t{}.size()); EXPECT_EQ(64U, utils::hash::hash_512_t{}.size()); } TEST(utils_hash, default_hasher_is_blake2b) { + EXPECT_EQ(&utils::hash::blake2b_32_hasher, + &utils::hash::default_create_hash()); + + EXPECT_EQ(&utils::hash::blake2b_64_hasher, + &utils::hash::default_create_hash()); + + EXPECT_EQ(&utils::hash::blake2b_128_hasher, + &utils::hash::default_create_hash()); + EXPECT_EQ(&utils::hash::blake2b_256_hasher, &utils::hash::default_create_hash()); @@ -41,6 +53,60 @@ TEST(utils_hash, default_hasher_is_blake2b) { &utils::hash::default_create_hash()); } +TEST(utils_hash, blake2b_32) { + auto hash = utils::collection::to_hex_string( + utils::hash::create_hash_blake2b_32("a")); + EXPECT_STREQ("ca234c55", hash.c_str()); + + hash = utils::collection::to_hex_string( + utils::hash::create_hash_blake2b_32(L"a")); +#if defined(_WIN32) + EXPECT_STREQ("4c368117", hash.c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ("02a631b8", hash.c_str()); +#endif + + hash = utils::collection::to_hex_string( + utils::hash::create_hash_blake2b_32({1U})); + EXPECT_STREQ("593bda73", hash.c_str()); +} + +TEST(utils_hash, blake2b_64) { + auto hash = utils::collection::to_hex_string( + utils::hash::create_hash_blake2b_64("a")); + EXPECT_STREQ("40f89e395b66422f", hash.c_str()); + + hash = utils::collection::to_hex_string( + utils::hash::create_hash_blake2b_64(L"a")); +#if defined(_WIN32) + EXPECT_STREQ("4dd0bb1c45b748c1", hash.c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ("85ff8cc55b79d38a", hash.c_str()); +#endif + + hash = utils::collection::to_hex_string( + utils::hash::create_hash_blake2b_64({1U})); + EXPECT_STREQ("00e83d0a3f7519ad", hash.c_str()); +} + +TEST(utils_hash, blake2b_128) { + auto hash = utils::collection::to_hex_string( + utils::hash::create_hash_blake2b_128("a")); + EXPECT_STREQ("27c35e6e9373877f29e562464e46497e", hash.c_str()); + + hash = utils::collection::to_hex_string( + utils::hash::create_hash_blake2b_128(L"a")); +#if defined(_WIN32) + EXPECT_STREQ("396660e76c84bb7786f979f10b58fa79", hash.c_str()); +#else // !defined(_WIN32) + EXPECT_STREQ("dae64afb310a3426ad84f0739fde5cef", hash.c_str()); +#endif + + hash = utils::collection::to_hex_string( + utils::hash::create_hash_blake2b_128({1U})); + EXPECT_STREQ("4a9e6f9b8d43f6ad008f8c291929dee2", hash.c_str()); +} + TEST(utils_hash, blake2b_256) { auto hash = utils::collection::to_hex_string( utils::hash::create_hash_blake2b_256("a")); diff --git a/support/test/src/utils/string_test.cpp b/support/test/src/utils/string_test.cpp index 08d64f92..b1f6b2b6 100644 --- a/support/test/src/utils/string_test.cpp +++ b/support/test/src/utils/string_test.cpp @@ -134,4 +134,10 @@ TEST(utils_string, to_bool) { EXPECT_FALSE(utils::string::to_bool("0")); EXPECT_FALSE(utils::string::to_bool("00000.00000")); } + +TEST(utils_string, utf8_string_conversion) { + std::wstring ws = L"Hello 🌍 — 𝄞 漢字"; + std::wstring ws2 = utils::string::from_utf8(utils::string::to_utf8(ws)); + EXPECT_STREQ(ws.c_str(), ws2.c_str()); +} } // namespace repertory diff --git a/support/test/src/utils/ttl_cache_test.cpp b/support/test/src/utils/ttl_cache_test.cpp index cd8666e9..cd71608f 100644 --- a/support/test/src/utils/ttl_cache_test.cpp +++ b/support/test/src/utils/ttl_cache_test.cpp @@ -150,7 +150,7 @@ TEST(utils_ttl_cache, can_handle_concurrent_access) { for (std::uint8_t ttl = 0U; ttl < 100U; ++ttl) { auto data = cache.get("/key"); if (data) { - (void)data->load(); + [[maybe_unused]] auto res = data->load(); } std::this_thread::yield(); } @@ -162,7 +162,7 @@ TEST(utils_ttl_cache, can_handle_concurrent_access) { auto data = cache.get("/key"); ASSERT_NE(data, nullptr); - (void)data->load(); + [[maybe_unused]] auto res = data->load(); } TEST(utils_ttl_cache, can_handle_custom_atomic) {