diff --git a/.github/workflows/build_web.yml b/.github/workflows/build_web.yml new file mode 100644 index 000000000..3ed5f748a --- /dev/null +++ b/.github/workflows/build_web.yml @@ -0,0 +1,79 @@ +name: Build for the web + +on: + push: + branches: ["*"] + pull_request: + workflow_dispatch: + +env: + BUILD_TYPE: Release + +permissions: + pages: write + id-token: write + actions: write + +jobs: + + build: + runs-on: ubuntu-22.04 + name: 🌍 WebAssembly + steps: + - name: 🧰 Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: 📁 Restore docker /cache + uses: actions/cache@v3 + with: + path: cache + key: build-web-cache-${{ secrets.CACHE_VERSION }} + + - name: 🐳 Inject /cache into docker + uses: reproducible-containers/buildkit-cache-dance@v2.1.2 + with: + cache-source: cache + cache-target: /cache + + - name: 🛠️ Build using docker + run: | + docker buildx build . -f dist/web/Dockerfile --progress=plain --build-arg 'JOBS=4' --output out + + - name: 🔨 Fix permissions + run: | + chmod -c -R +rX "out/" | while read line; do + echo "::warning title=Invalid file permissions automatically fixed::$line" + done + + - name: ⬆️ Upload artifacts + uses: actions/upload-pages-artifact@v2 + with: + path: out/ + + # See https://github.com/actions/cache/issues/342#issuecomment-1711054115 + - name: 🗑️ Delete old cache + continue-on-error: true + env: + GH_TOKEN: ${{ github.token }} + run: | + gh extension install actions/gh-actions-cache + gh actions-cache delete "build-web-cache-${{ secrets.CACHE_VERSION }}" --confirm + + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + name: 📃 Deploy to GitHub Pages + runs-on: ubuntu-latest + + if: github.ref == 'refs/heads/master' + needs: build + + steps: + - name: 🌍 Deploy + id: deployment + uses: actions/deploy-pages@v2 \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index bd95c0bec..6fd006dcd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ option(IMHEX_BUNDLE_DOTNET "Bundle .NET runtime" ON) option(IMHEX_ENABLE_LTO "Enables Link Time Optimizations if possible" OFF) option(IMHEX_USE_DEFAULT_BUILD_SETTINGS "Use default build settings" OFF) option(IMHEX_STRICT_WARNINGS "Enable most available warnings and treat them as errors" ON) +option(IMHEX_STATIC_LINK_PLUGINS "Statically link all plugins into the main executable" OFF) # Basic compiler and cmake configurations set(CMAKE_CXX_STANDARD 23) diff --git a/README.md b/README.md index e79407da8..892e96874 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,12 @@

+

+ + Use the Web version of ImHex right in your browser! + +

+ ## Supporting If you like my work, please consider supporting me on GitHub Sponsors, Patreon or PayPal. Thanks a lot! diff --git a/cmake/build_helpers.cmake b/cmake/build_helpers.cmake index cbef0b1e5..d7785d1b7 100644 --- a/cmake/build_helpers.cmake +++ b/cmake/build_helpers.cmake @@ -30,6 +30,10 @@ macro(addDefines) set(IMHEX_VERSION_STRING ${IMHEX_VERSION_STRING}-MinSizeRel) add_compile_definitions(NDEBUG) endif () + + if (IMHEX_STATIC_LINK_PLUGINS) + add_compile_definitions(IMHEX_STATIC_LINK_PLUGINS) + endif () endmacro() function(addDefineToSource SOURCE DEFINE) @@ -54,6 +58,8 @@ macro(detectOS) set(PLUGINS_INSTALL_LOCATION "plugins") enable_language(OBJC) enable_language(OBJCXX) + elseif (EMSCRIPTEN) + add_compile_definitions(OS_WEB) elseif (UNIX AND NOT APPLE) add_compile_definitions(OS_LINUX) include(GNUInstallDirs) @@ -83,6 +89,8 @@ endmacro() macro(configurePackingResources) + set(IMHEX_FORCE_LINK_PLUGINS "") + option (CREATE_PACKAGE "Create a package with CPack" OFF) if (APPLE) @@ -351,8 +359,8 @@ endmacro() macro(setDefaultBuiltTypeIfUnset) if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Using Release build type as it was left unset" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release") + set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Using RelWithDebInfo build type as it was left unset" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "RelWithDebInfo") endif() endmacro() @@ -441,13 +449,17 @@ macro(setupCompilerFlags target) set(IMHEX_COMMON_FLAGS "-Wall -Wextra -Wpedantic -Werror") endif() - set(IMHEX_C_FLAGS "${IMHEX_COMMON_FLAGS} -Wno-array-bounds") + set(IMHEX_C_FLAGS "${IMHEX_COMMON_FLAGS} -Wno-array-bounds -Wno-deprecated-declarations") set(IMHEX_CXX_FLAGS "-fexceptions -frtti") if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(IMHEX_C_FLAGS "${IMHEX_C_FLAGS} -Wno-restrict -Wno-stringop-overread -Wno-stringop-overflow -Wno-dangling-reference") endif() endif() + if (EMSCRIPTEN) + set(IMHEX_C_FLAGS "${IMHEX_C_FLAGS} -pthread -Wno-dollar-in-identifier-extension -Wno-pthreads-mem-growth") + endif () + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${IMHEX_C_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${IMHEX_CXX_FLAGS} ${IMHEX_C_FLAGS}") set(CMAKE_OBJC_FLAGS "${CMAKE_OBJC_FLAGS} ${IMHEX_COMMON_FLAGS}") @@ -489,6 +501,8 @@ macro(addBundledLibraries) set(XDGPP_INCLUDE_DIRS "${EXTERN_LIBS_FOLDER}/xdgpp") set(FPHSA_NAME_MISMATCHED ON CACHE BOOL "") + find_package(PkgConfig REQUIRED) + if(NOT USE_SYSTEM_FMT) add_subdirectory(${EXTERN_LIBS_FOLDER}/fmt EXCLUDE_FROM_ALL) set_target_properties(fmt PROPERTIES POSITION_INDEPENDENT_CODE ON) @@ -504,13 +518,20 @@ macro(addBundledLibraries) set(NFD_PORTAL ON CACHE BOOL "Use GTK for Linux file dialogs" FORCE) endif () - if (NOT USE_SYSTEM_NFD) - add_subdirectory(${EXTERN_LIBS_FOLDER}/nativefiledialog EXCLUDE_FROM_ALL) - set_target_properties(nfd PROPERTIES POSITION_INDEPENDENT_CODE ON) - set(NFD_LIBRARIES nfd) - else() - find_package(nfd) - set(NFD_LIBRARIES nfd) + if (NOT EMSCRIPTEN) + # curl + find_package(PkgConfig REQUIRED) + pkg_check_modules(LIBCURL REQUIRED IMPORTED_TARGET libcurl>=7.60.0) + + # nfd + if (NOT USE_SYSTEM_NFD) + add_subdirectory(${EXTERN_LIBS_FOLDER}/nativefiledialog EXCLUDE_FROM_ALL) + set_target_properties(nfd PROPERTIES POSITION_INDEPENDENT_CODE ON) + set(NFD_LIBRARIES nfd) + else() + find_package(nfd) + set(NFD_LIBRARIES nfd) + endif() endif() if(NOT USE_SYSTEM_NLOHMANN_JSON) @@ -521,9 +542,6 @@ macro(addBundledLibraries) set(NLOHMANN_JSON_LIBRARIES nlohmann_json::nlohmann_json) endif() - find_package(PkgConfig REQUIRED) - pkg_check_modules(LIBCURL REQUIRED IMPORTED_TARGET libcurl>=7.60.0) - if (NOT USE_SYSTEM_LLVM) add_subdirectory(${EXTERN_LIBS_FOLDER}/llvm-demangle EXCLUDE_FROM_ALL) set_target_properties(LLVMDemangle PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/cmake/modules/ImHexPlugin.cmake b/cmake/modules/ImHexPlugin.cmake index cfec63875..4e4051f2d 100644 --- a/cmake/modules/ImHexPlugin.cmake +++ b/cmake/modules/ImHexPlugin.cmake @@ -4,20 +4,32 @@ macro(add_imhex_plugin) set(oneValueArgs NAME) set(multiValueArgs SOURCES INCLUDES LIBRARIES) cmake_parse_arguments(IMHEX_PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - + + if (IMHEX_STATIC_LINK_PLUGINS) + set(IMHEX_PLUGIN_LIBRARY_TYPE STATIC) + + target_link_libraries(libimhex PUBLIC ${IMHEX_PLUGIN_NAME}) + + configure_file(${CMAKE_SOURCE_DIR}/dist/web/plugin-bundle.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/plugin-bundle.cpp @ONLY) + target_sources(main PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/plugin-bundle.cpp) + else() + set(IMHEX_PLUGIN_LIBRARY_TYPE SHARED) + endif() + # Define new project for plugin project(${IMHEX_PLUGIN_NAME}) # Create a new shared library for the plugin source code - add_library(${IMHEX_PLUGIN_NAME} SHARED ${IMHEX_PLUGIN_SOURCES}) + add_library(${IMHEX_PLUGIN_NAME} ${IMHEX_PLUGIN_LIBRARY_TYPE} ${IMHEX_PLUGIN_SOURCES}) # Add include directories and link libraries - target_include_directories(${IMHEX_PLUGIN_NAME} PRIVATE ${IMHEX_PLUGIN_INCLUDES}) + target_include_directories(${IMHEX_PLUGIN_NAME} PUBLIC ${IMHEX_PLUGIN_INCLUDES}) target_link_libraries(${IMHEX_PLUGIN_NAME} PRIVATE libimhex ${FMT_LIBRARIES} ${IMHEX_PLUGIN_LIBRARIES}) # Add IMHEX_PROJECT_NAME and IMHEX_VERSION define target_compile_definitions(${IMHEX_PLUGIN_NAME} PRIVATE IMHEX_PROJECT_NAME="${IMHEX_PLUGIN_NAME}") target_compile_definitions(${IMHEX_PLUGIN_NAME} PRIVATE IMHEX_VERSION="${IMHEX_VERSION_STRING}") + target_compile_definitions(${IMHEX_PLUGIN_NAME} PRIVATE IMHEX_PLUGIN_NAME=${IMHEX_PLUGIN_NAME}) # Enable required compiler flags set_target_properties(${IMHEX_PLUGIN_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/dist/web/Dockerfile b/dist/web/Dockerfile new file mode 100644 index 000000000..bf772f8cf --- /dev/null +++ b/dist/web/Dockerfile @@ -0,0 +1,76 @@ +FROM emscripten/emsdk:latest as build + +# Used to invalidate layer cache but not mount cache +# See https://github.com/moby/moby/issues/41715#issuecomment-733976493 +ARG UNIQUEKEY 1 + +RUN apt update +RUN apt install -y git ccache autoconf automake libtool cmake pkg-config + +# Install vcpkg +# Note: we are using my fork of the repository with a libmagic patch +RUN git clone https://github.com/iTrooz/vcpkg --branch libmagic /vcpkg +RUN /vcpkg/bootstrap-vcpkg.sh + +# Patch vcpkg build instructions to add -pthread +RUN <> /emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake +EOF + +# Install dependencies with vcpkg +RUN /vcpkg/vcpkg install --triplet=wasm32-emscripten libmagic +RUN /vcpkg/vcpkg install --triplet=wasm32-emscripten freetype +RUN /vcpkg/vcpkg install --triplet=wasm32-emscripten josuttis-jthread +RUN /vcpkg/vcpkg install --triplet=wasm32-emscripten mbedtls + +# Build ImHex +ARG JOBS=4 +ENV CCACHE_DIR /cache/ccache + +RUN mkdir /build +WORKDIR /build +RUN --mount=type=cache,target=/cache \ + --mount=type=bind,source=.,target=/imhex < + +extern "C" void forceLinkPlugin_@IMHEX_PLUGIN_NAME@(); + +struct StaticLoad { + StaticLoad() { + forceLinkPlugin_@IMHEX_PLUGIN_NAME@(); + } +}; + +static StaticLoad staticLoad; \ No newline at end of file diff --git a/dist/web/serve.py b/dist/web/serve.py new file mode 100644 index 000000000..e0224f9a1 --- /dev/null +++ b/dist/web/serve.py @@ -0,0 +1,14 @@ +import http.server +import os + +class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler): + + def end_headers(self): + self.send_header("Cross-Origin-Embedder-Policy", "require-corp") + self.send_header("Cross-Origin-Opener-Policy", "same-origin") + http.server.SimpleHTTPRequestHandler.end_headers(self) + +if __name__ == '__main__': + os.chdir(".") + httpd = http.server.HTTPServer(("", 9090), MyHttpRequestHandler) + httpd.serve_forever() \ No newline at end of file diff --git a/dist/web/source/enable-threads.js b/dist/web/source/enable-threads.js new file mode 100644 index 000000000..01d7d084c --- /dev/null +++ b/dist/web/source/enable-threads.js @@ -0,0 +1,75 @@ +// NOTE: This file creates a service worker that cross-origin-isolates the page (read more here: https://web.dev/coop-coep/) which allows us to use wasm threads. +// Normally you would set the COOP and COEP headers on the server to do this, but Github Pages doesn't allow this, so this is a hack to do that. + +/* Edited version of: coi-serviceworker v0.1.6 - Guido Zuidhof, licensed under MIT */ +// From here: https://github.com/gzuidhof/coi-serviceworker +if(typeof window === 'undefined') { + self.addEventListener("install", () => self.skipWaiting()); + self.addEventListener("activate", e => e.waitUntil(self.clients.claim())); + + async function handleFetch(request) { + if(request.cache === "only-if-cached" && request.mode !== "same-origin") { + return; + } + + if(request.mode === "no-cors") { // We need to set `credentials` to "omit" for no-cors requests, per this comment: https://bugs.chromium.org/p/chromium/issues/detail?id=1309901#c7 + request = new Request(request.url, { + cache: request.cache, + credentials: "omit", + headers: request.headers, + integrity: request.integrity, + destination: request.destination, + keepalive: request.keepalive, + method: request.method, + mode: request.mode, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + signal: request.signal, + }); + } + + let r = await fetch(request).catch(e => console.error(e)); + + if(r.status === 0) { + return r; + } + + const headers = new Headers(r.headers); + headers.set("Cross-Origin-Embedder-Policy", "require-corp"); // or: require-corp + headers.set("Cross-Origin-Opener-Policy", "same-origin"); + + return new Response(r.body, { status: r.status, statusText: r.statusText, headers }); + } + + self.addEventListener("fetch", function(e) { + e.respondWith(handleFetch(e.request)); // respondWith must be executed synchonously (but can be passed a Promise) + }); + +} else { + (async function() { + if(window.crossOriginIsolated !== false) return; + + let registration = await navigator.serviceWorker.register(window.document.currentScript.src).catch(e => console.error("COOP/COEP Service Worker failed to register:", e)); + if(registration) { + console.log("COOP/COEP Service Worker registered", registration.scope); + + registration.addEventListener("updatefound", () => { + console.log("Reloading page to make use of updated COOP/COEP Service Worker."); + window.location.reload(); + }); + + // If the registration is active, but it's not controlling the page + if(registration.active && !navigator.serviceWorker.controller) { + console.log("Reloading page to make use of COOP/COEP Service Worker."); + window.location.reload(); + } + } + })(); +} + +// Code to deregister: +// let registrations = await navigator.serviceWorker.getRegistrations(); +// for(let registration of registrations) { +// await registration.unregister(); +// } \ No newline at end of file diff --git a/dist/web/source/favicon.ico b/dist/web/source/favicon.ico new file mode 100644 index 000000000..a72e2ab21 Binary files /dev/null and b/dist/web/source/favicon.ico differ diff --git a/dist/web/source/index.html b/dist/web/source/index.html new file mode 100644 index 000000000..d33a2459c --- /dev/null +++ b/dist/web/source/index.html @@ -0,0 +1,73 @@ + + + + + + + + + ImHex - Hex Editor + + + + + + + + + + + + + + + + + + + + + + + + + + + + ImHex Web + + + + +

ImHex is loading...

+ + + + + + \ No newline at end of file diff --git a/dist/web/source/style.css b/dist/web/source/style.css new file mode 100644 index 000000000..4b9c77497 --- /dev/null +++ b/dist/web/source/style.css @@ -0,0 +1,28 @@ +html, body { + height: 100%; + margin: 0px; + user-select: none; +} + +body { + display: flex; + align-items: center; + background-color: #121212; + overflow: hidden; +} + +.emscripten { + padding-right: 0; + margin-left: auto; + margin-right: auto; + display: block; + border: 0px none; +} + +#loading_text { + color: #F0F0F0; + font-size: 30px; + font-family: monospace; + width: 100%; + text-align: center; +} \ No newline at end of file diff --git a/dist/web/source/wasm-config.js b/dist/web/source/wasm-config.js new file mode 100644 index 000000000..45995aa59 --- /dev/null +++ b/dist/web/source/wasm-config.js @@ -0,0 +1,68 @@ +function glfwSetCursorCustom(wnd, shape) { + let body = document.getElementsByTagName("body")[0] + switch (shape) { + case 0x00036001: // GLFW_ARROW_CURSOR + body.style.cursor = "default"; + break; + case 0x00036002: // GLFW_IBEAM_CURSOR + body.style.cursor = "text"; + break; + case 0x00036003: // GLFW_CROSSHAIR_CURSOR + body.style.cursor = "crosshair"; + break; + case 0x00036004: // GLFW_HAND_CURSOR + body.style.cursor = "pointer"; + break; + case 0x00036005: // GLFW_HRESIZE_CURSOR + body.style.cursor = "ew-resize"; + break; + case 0x00036006: // GLFW_VRESIZE_CURSOR + body.style.cursor = "ns-resize"; + break; + default: + body.style.cursor = "default"; + break; + } + +} + +function glfwCreateStandardCursorCustom(shape) { + return shape +} + +var Module = { + preRun: [], + postRun: [], + onRuntimeInitialized: function() { + // Triggered when the wasm module is loaded and ready to use. + document.getElementById("loading_text").style.display = "none" + document.getElementById("canvas").style.display = "initial" + }, + print: (function() { })(), + printErr: function(text) { }, + canvas: (function() { + let canvas = document.getElementById('canvas'); + // As a default initial behavior, pop up an alert when webgl context is lost. To make your + // application robust, you may want to override this behavior before shipping! + // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 + canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); + + return canvas; + })(), + setStatus: function(text) { }, + totalDependencies: 0, + monitorRunDependencies: function(left) { }, + instantiateWasm: function(imports, successCallback) { + imports.env.glfwSetCursor = glfwSetCursorCustom + imports.env.glfwCreateStandardCursor = glfwCreateStandardCursorCustom + instantiateAsync(wasmBinary, wasmBinaryFile, imports, (result) => successCallback(result.instance, result.module)); + } +}; + + +window.addEventListener('resize', js_resizeCanvas, false); +function js_resizeCanvas() { + let canvas = document.getElementById('canvas'); + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; +} \ No newline at end of file diff --git a/lib/external/fmt b/lib/external/fmt index f5e54359d..f91828936 160000 --- a/lib/external/fmt +++ b/lib/external/fmt @@ -1 +1 @@ -Subproject commit f5e54359df4c26b6230fc61d38aa294581393084 +Subproject commit f9182893631e4a93e8a2d947b726f95a5367fce9 diff --git a/lib/external/imgui/CMakeLists.txt b/lib/external/imgui/CMakeLists.txt index f81b03ccc..6502bdccc 100644 --- a/lib/external/imgui/CMakeLists.txt +++ b/lib/external/imgui/CMakeLists.txt @@ -32,9 +32,8 @@ add_library(imgui OBJECT ) target_compile_definitions(imgui PUBLIC IMGUI_IMPL_OPENGL_LOADER_GLAD) -target_compile_options(imgui PRIVATE -Wno-stringop-overflow) - target_compile_definitions(imgui PUBLIC IMGUI_USER_CONFIG="imgui_config.h") +target_compile_options(imgui PRIVATE -Wno-unknown-warning-option) target_include_directories(imgui PUBLIC include ${FREETYPE_INCLUDE_DIRS} ${GLFW_INCLUDE_DIRS} ${OpenGL_INCLUDE_DIRS}) target_link_directories(imgui PUBLIC ${GLFW_LIBRARY_DIRS} ${OpenGL_LIBRARY_DIRS}) diff --git a/lib/external/imgui/source/imgui_impl_glfw.cpp b/lib/external/imgui/source/imgui_impl_glfw.cpp index 4be45aa8d..e2728c7a1 100644 --- a/lib/external/imgui/source/imgui_impl_glfw.cpp +++ b/lib/external/imgui/source/imgui_impl_glfw.cpp @@ -126,6 +126,8 @@ #define GLFW_HAS_GETKEYNAME (GLFW_VERSION_COMBINED >= 3200) // 3.2+ glfwGetKeyName() #define GLFW_HAS_GETERROR (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetError() +#undef GLFW_HAS_VULKAN + // GLFW data enum GlfwClientApi { diff --git a/lib/external/libromfs b/lib/external/libromfs index 31b331c02..03365d8c5 160000 --- a/lib/external/libromfs +++ b/lib/external/libromfs @@ -1 +1 @@ -Subproject commit 31b331c02af7464e436f38e7aed27646e097b1bd +Subproject commit 03365d8c5a43baa22cc6bbd5725db15a3038d035 diff --git a/lib/external/libwolv b/lib/external/libwolv index 094d87ca3..f5f081c28 160000 --- a/lib/external/libwolv +++ b/lib/external/libwolv @@ -1 +1 @@ -Subproject commit 094d87ca30fb3f03485fac1c8c11f53baad52210 +Subproject commit f5f081c28efb9c06e14fa3b4a6d85866d75f7350 diff --git a/lib/external/pattern_language b/lib/external/pattern_language index ff92cf631..a3574fe6c 160000 --- a/lib/external/pattern_language +++ b/lib/external/pattern_language @@ -1 +1 @@ -Subproject commit ff92cf631a7483faee461947538479919e736114 +Subproject commit a3574fe6c2dcca40cac5ac61dc95a063b6799249 diff --git a/lib/external/yara/CMakeLists.txt b/lib/external/yara/CMakeLists.txt index 11b19423b..1a61f9622 100644 --- a/lib/external/yara/CMakeLists.txt +++ b/lib/external/yara/CMakeLists.txt @@ -116,11 +116,13 @@ target_compile_definitions(libyara PRIVATE if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(libyara PRIVATE -Wno-shift-count-overflow -Wno-stringop-overflow) +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + target_compile_options(libyara PRIVATE -Wno-pointer-sign -Wno-tautological-constant-out-of-range-compare) endif () target_include_directories( libyara - PUBLIC $ $ $ + PUBLIC $ $ PRIVATE ${LIBYARA_SOURCE_PATH} ${MBEDTLS_INCLUDE_DIR} ) diff --git a/lib/libimhex/CMakeLists.txt b/lib/libimhex/CMakeLists.txt index 117239a36..49592b2c7 100644 --- a/lib/libimhex/CMakeLists.txt +++ b/lib/libimhex/CMakeLists.txt @@ -27,6 +27,8 @@ set(LIBIMHEX_SOURCES source/helpers/magic.cpp source/helpers/crypto.cpp source/helpers/http_requests.cpp + source/helpers/http_requests_native.cpp + source/helpers/http_requests_emscripten.cpp source/helpers/opengl.cpp source/helpers/patches.cpp source/helpers/encoding_file.cpp @@ -58,7 +60,12 @@ endif () add_compile_definitions(IMHEX_PROJECT_NAME="${PROJECT_NAME}") -add_library(libimhex SHARED ${LIBIMHEX_SOURCES}) +if (IMHEX_STATIC_LINK_PLUGINS) + add_library(libimhex STATIC ${LIBIMHEX_SOURCES}) +else() + add_library(libimhex SHARED ${LIBIMHEX_SOURCES}) +endif() + set_target_properties(libimhex PROPERTIES POSITION_INDEPENDENT_CODE ON) setupCompilerFlags(libimhex) @@ -68,6 +75,16 @@ generate_export_header(libimhex) target_include_directories(libimhex PUBLIC include ${XDGPP_INCLUDE_DIRS} ${MBEDTLS_INCLUDE_DIR} ${CAPSTONE_INCLUDE_DIRS} ${MAGIC_INCLUDE_DIRS} ${LLVM_INCLUDE_DIRS} ${FMT_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} ${YARA_INCLUDE_DIRS} ${LIBBACKTRACE_INCLUDE_DIRS}) target_link_directories(libimhex PUBLIC ${MBEDTLS_LIBRARY_DIR} ${CAPSTONE_LIBRARY_DIRS} ${MAGIC_LIBRARY_DIRS}) +if (EMSCRIPTEN) + find_path(JOSUTTIS_JTHREAD_INCLUDE_DIRS "condition_variable_any2.hpp") + target_include_directories(libimhex PRIVATE ${JOSUTTIS_JTHREAD_INCLUDE_DIRS}) +else() + # curl is only used in non-emscripten builds + target_include_directories(libimhex PUBLIC ${CURL_INCLUDE_DIRS}) + target_link_libraries(libimhex PUBLIC ${LIBCURL_LIBRARIES}) +endif() + + if (WIN32) set_target_properties(libimhex PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) target_link_options(libimhex PRIVATE -Wl,--export-all-symbols) @@ -77,7 +94,7 @@ elseif (APPLE) endif () target_link_libraries(libimhex PRIVATE ${FMT_LIBRARIES}) -target_link_libraries(libimhex PUBLIC dl imgui ${NFD_LIBRARIES} magic ${CAPSTONE_LIBRARIES} LLVMDemangle microtar ${NLOHMANN_JSON_LIBRARIES} ${YARA_LIBRARIES} ${LIBCURL_LIBRARIES} ${MBEDTLS_LIBRARIES} ${LIBBACKTRACE_LIBRARIES} plcli libpl libpl-gen ${MINIAUDIO_LIBRARIES} libwolv-utils libwolv-io libwolv-hash libwolv-net libwolv-containers) +target_link_libraries(libimhex PUBLIC dl imgui ${NFD_LIBRARIES} magic ${CAPSTONE_LIBRARIES} LLVMDemangle microtar ${NLOHMANN_JSON_LIBRARIES} ${YARA_LIBRARIES} ${MBEDTLS_LIBRARIES} ${LIBBACKTRACE_LIBRARIES} plcli libpl libpl-gen ${MINIAUDIO_LIBRARIES} libwolv-utils libwolv-io libwolv-hash libwolv-net libwolv-containers) set_property(TARGET libimhex PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE) diff --git a/lib/libimhex/include/hex/api/content_registry.hpp b/lib/libimhex/include/hex/api/content_registry.hpp index c5b06c965..b676f82bc 100644 --- a/lib/libimhex/include/hex/api/content_registry.hpp +++ b/lib/libimhex/include/hex/api/content_registry.hpp @@ -16,6 +16,10 @@ #include #include +#if defined(OS_WEB) +#include +#endif + #include using ImGuiDataType = int; diff --git a/lib/libimhex/include/hex/api/plugin_manager.hpp b/lib/libimhex/include/hex/api/plugin_manager.hpp index e15f1adf5..f3c75b265 100644 --- a/lib/libimhex/include/hex/api/plugin_manager.hpp +++ b/lib/libimhex/include/hex/api/plugin_manager.hpp @@ -24,9 +24,31 @@ namespace hex { std::function&)> callback; }; + struct PluginFunctions { + using InitializePluginFunc = void (*)(); + using GetPluginNameFunc = const char *(*)(); + using GetPluginAuthorFunc = const char *(*)(); + using GetPluginDescriptionFunc = const char *(*)(); + using GetCompatibleVersionFunc = const char *(*)(); + using SetImGuiContextFunc = void (*)(ImGuiContext *); + using IsBuiltinPluginFunc = bool (*)(); + using GetSubCommandsFunc = void* (*)(); + + InitializePluginFunc initializePluginFunction = nullptr; + GetPluginNameFunc getPluginNameFunction = nullptr; + GetPluginAuthorFunc getPluginAuthorFunction = nullptr; + GetPluginDescriptionFunc getPluginDescriptionFunction = nullptr; + GetCompatibleVersionFunc getCompatibleVersionFunction = nullptr; + SetImGuiContextFunc setImGuiContextFunction = nullptr; + IsBuiltinPluginFunc isBuiltinPluginFunction = nullptr; + GetSubCommandsFunc getSubCommandsFunction = nullptr; + }; + class Plugin { public: explicit Plugin(const std::fs::path &path); + explicit Plugin(PluginFunctions functions); + Plugin(const Plugin &) = delete; Plugin(Plugin &&other) noexcept; ~Plugin(); @@ -46,15 +68,6 @@ namespace hex { [[nodiscard]] std::span getSubCommands() const; private: - using InitializePluginFunc = void (*)(); - using GetPluginNameFunc = const char *(*)(); - using GetPluginAuthorFunc = const char *(*)(); - using GetPluginDescriptionFunc = const char *(*)(); - using GetCompatibleVersionFunc = const char *(*)(); - using SetImGuiContextFunc = void (*)(ImGuiContext *); - using IsBuiltinPluginFunc = bool (*)(); - using GetSubCommandsFunc = void* (*)(); - #if defined(OS_WINDOWS) HMODULE m_handle = nullptr; #else @@ -64,14 +77,7 @@ namespace hex { mutable bool m_initialized = false; - InitializePluginFunc m_initializePluginFunction = nullptr; - GetPluginNameFunc m_getPluginNameFunction = nullptr; - GetPluginAuthorFunc m_getPluginAuthorFunction = nullptr; - GetPluginDescriptionFunc m_getPluginDescriptionFunction = nullptr; - GetCompatibleVersionFunc m_getCompatibleVersionFunction = nullptr; - SetImGuiContextFunc m_setImGuiContextFunction = nullptr; - IsBuiltinPluginFunc m_isBuiltinPluginFunction = nullptr; - GetSubCommandsFunc m_getSubCommandsFunction = nullptr; + PluginFunctions m_functions = {}; template [[nodiscard]] auto getPluginFunction(const std::string &symbol) { @@ -90,7 +96,10 @@ namespace hex { static void unload(); static void reload(); - static const std::vector &getPlugins(); + static void addPlugin(PluginFunctions functions); + + static std::vector &getPlugins(); + static std::vector &getPluginPaths(); }; } \ No newline at end of file diff --git a/lib/libimhex/include/hex/api/task.hpp b/lib/libimhex/include/hex/api/task.hpp index a5fc2eca5..c3ecd9973 100644 --- a/lib/libimhex/include/hex/api/task.hpp +++ b/lib/libimhex/include/hex/api/task.hpp @@ -12,6 +12,10 @@ #include #include +#if defined(OS_WEB) +#include +#endif + namespace hex { class TaskHolder; diff --git a/lib/libimhex/include/hex/helpers/fs.hpp b/lib/libimhex/include/hex/helpers/fs.hpp index 8a04454ef..070297731 100644 --- a/lib/libimhex/include/hex/helpers/fs.hpp +++ b/lib/libimhex/include/hex/helpers/fs.hpp @@ -8,8 +8,6 @@ #include #include -#include - #include namespace hex::fs { @@ -20,8 +18,15 @@ namespace hex::fs { Folder }; + struct ItemFilter { + // Human-friendly name + std::string name; + // Extensions that constitute this filter + std::string spec; + }; + void setFileBrowserErrorCallback(const std::function &callback); - bool openFileBrowser(DialogMode mode, const std::vector &validExtensions, const std::function &callback, const std::string &defaultPath = {}, bool multiple = false); + bool openFileBrowser(DialogMode mode, const std::vector &validExtensions, const std::function &callback, const std::string &defaultPath = {}, bool multiple = false); void openFileExternal(const std::fs::path &filePath); void openFolderExternal(const std::fs::path &dirPath); @@ -53,7 +58,7 @@ namespace hex::fs { std::vector getDefaultPaths(ImHexPath path, bool listNonExisting = false); - // temporarily expose these for the migration function + // Temporarily expose these for the migration function std::vector getDataPaths(); std::vector appendPath(std::vector paths, const std::fs::path &folder); } \ No newline at end of file diff --git a/lib/libimhex/include/hex/helpers/http_requests.hpp b/lib/libimhex/include/hex/helpers/http_requests.hpp index 69b1a4293..609d17341 100644 --- a/lib/libimhex/include/hex/helpers/http_requests.hpp +++ b/lib/libimhex/include/hex/helpers/http_requests.hpp @@ -7,8 +7,6 @@ #include #include -#include - #include #include @@ -19,6 +17,14 @@ #include +#if defined(OS_WEB) + #include + + using curl_off_t = long; +#else + #include +#endif + namespace hex { class HttpRequest { @@ -67,27 +73,9 @@ namespace hex { HttpRequest(const HttpRequest&) = delete; HttpRequest& operator=(const HttpRequest&) = delete; - HttpRequest(HttpRequest &&other) noexcept { - this->m_curl = other.m_curl; - other.m_curl = nullptr; + HttpRequest(HttpRequest &&other) noexcept; - this->m_method = std::move(other.m_method); - this->m_url = std::move(other.m_url); - this->m_headers = std::move(other.m_headers); - this->m_body = std::move(other.m_body); - } - - HttpRequest& operator=(HttpRequest &&other) noexcept { - this->m_curl = other.m_curl; - other.m_curl = nullptr; - - this->m_method = std::move(other.m_method); - this->m_url = std::move(other.m_url); - this->m_headers = std::move(other.m_headers); - this->m_body = std::move(other.m_body); - - return *this; - } + HttpRequest& operator=(HttpRequest &&other) noexcept; static void setProxy(std::string proxy); @@ -120,173 +108,28 @@ namespace hex { } template - std::future> downloadFile(const std::fs::path &path) { - return std::async(std::launch::async, [this, path] { - std::vector response; + std::future> downloadFile(const std::fs::path &path); - wolv::io::File file(path, wolv::io::File::Mode::Create); - curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToFile); - curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &file); - - return this->executeImpl(response); - }); - } - - std::future>> downloadFile() { - return std::async(std::launch::async, [this] { - std::vector response; - - curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); - curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &response); - - return this->executeImpl>(response); - }); - } + std::future>> downloadFile(); template - std::future> uploadFile(const std::fs::path &path, const std::string &mimeName = "filename") { - return std::async(std::launch::async, [this, path, mimeName]{ - auto fileName = wolv::util::toUTF8String(path.filename()); - - curl_mime *mime = curl_mime_init(this->m_curl); - curl_mimepart *part = curl_mime_addpart(mime); - - wolv::io::File file(path, wolv::io::File::Mode::Read); - - curl_mime_data_cb(part, file.getSize(), - [](char *buffer, size_t size, size_t nitems, void *arg) -> size_t { - auto file = static_cast(arg); - - return fread(buffer, size, nitems, file); - }, - [](void *arg, curl_off_t offset, int origin) -> int { - auto file = static_cast(arg); - - if (fseek(file, offset, origin) != 0) - return CURL_SEEKFUNC_CANTSEEK; - else - return CURL_SEEKFUNC_OK; - }, - [](void *arg) { - auto file = static_cast(arg); - - fclose(file); - }, - file.getHandle()); - curl_mime_filename(part, fileName.c_str()); - curl_mime_name(part, mimeName.c_str()); - - curl_easy_setopt(this->m_curl, CURLOPT_MIMEPOST, mime); - - std::vector responseData; - curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); - curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData); - - return this->executeImpl(responseData); - }); - } - template - std::future> uploadFile(std::vector data, const std::string &mimeName = "filename", const std::fs::path &fileName = "data.bin") { - return std::async(std::launch::async, [this, data = std::move(data), mimeName, fileName]{ - curl_mime *mime = curl_mime_init(this->m_curl); - curl_mimepart *part = curl_mime_addpart(mime); - - curl_mime_data(part, reinterpret_cast(data.data()), data.size()); - auto fileNameStr = wolv::util::toUTF8String(fileName.filename()); - curl_mime_filename(part, fileNameStr.c_str()); - curl_mime_name(part, mimeName.c_str()); - - curl_easy_setopt(this->m_curl, CURLOPT_MIMEPOST, mime); - - std::vector responseData; - curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); - curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData); - - return this->executeImpl(responseData); - }); - } + std::future> uploadFile(const std::fs::path &path, const std::string &mimeName = "filename"); template - std::future> execute() { - return std::async(std::launch::async, [this] { + std::future> uploadFile(std::vector data, const std::string &mimeName = "filename", const std::fs::path &fileName = "data.bin"); - std::vector responseData; - curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); - curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData); + template + std::future> execute(); - return this->executeImpl(responseData); - }); - } + std::string urlEncode(const std::string &input); - std::string urlEncode(const std::string &input) { - auto escapedString = curl_easy_escape(this->m_curl, input.c_str(), std::strlen(input.c_str())); - - if (escapedString != nullptr) { - std::string output = escapedString; - curl_free(escapedString); - - return output; - } - - return {}; - } - - std::string urlDecode(const std::string &input) { - auto unescapedString = curl_easy_unescape(this->m_curl, input.c_str(), std::strlen(input.c_str()), nullptr); - - if (unescapedString != nullptr) { - std::string output = unescapedString; - curl_free(unescapedString); - - return output; - } - - return {}; - } + std::string urlDecode(const std::string &input); protected: void setDefaultConfig(); template - Result executeImpl(std::vector &data) { - curl_easy_setopt(this->m_curl, CURLOPT_URL, this->m_url.c_str()); - curl_easy_setopt(this->m_curl, CURLOPT_CUSTOMREQUEST, this->m_method.c_str()); - - setDefaultConfig(); - - if (!this->m_body.empty()) { - curl_easy_setopt(this->m_curl, CURLOPT_POSTFIELDS, this->m_body.c_str()); - } - - curl_slist *headers = nullptr; - headers = curl_slist_append(headers, "Cache-Control: no-cache"); - ON_SCOPE_EXIT { curl_slist_free_all(headers); }; - - for (auto &[key, value] : this->m_headers) { - std::string header = hex::format("{}: {}", key, value); - headers = curl_slist_append(headers, header.c_str()); - } - curl_easy_setopt(this->m_curl, CURLOPT_HTTPHEADER, headers); - - { - std::scoped_lock lock(this->m_transmissionMutex); - - auto result = curl_easy_perform(this->m_curl); - if (result != CURLE_OK){ - char *url = nullptr; - curl_easy_getinfo(this->m_curl, CURLINFO_EFFECTIVE_URL, &url); - log::error("Http request '{0} {1}' failed with error {2}: '{3}'", this->m_method, url, u32(result), curl_easy_strerror(result)); - checkProxyErrors(); - - return { }; - } - } - - long statusCode = 0; - curl_easy_getinfo(this->m_curl, CURLINFO_RESPONSE_CODE, &statusCode); - - return Result(statusCode, { data.begin(), data.end() }); - } + Result executeImpl(std::vector &data); static size_t writeToVector(void *contents, size_t size, size_t nmemb, void *userdata); static size_t writeToFile(void *contents, size_t size, size_t nmemb, void *userdata); @@ -296,18 +139,29 @@ namespace hex { static void checkProxyErrors(); private: + #if defined(OS_WEB) + emscripten_fetch_attr_t m_attr; + #else CURL *m_curl; + #endif std::mutex m_transmissionMutex; std::string m_method; std::string m_url; std::string m_body; + std::promise> m_promise; std::map m_headers; u32 m_timeout = 1000; std::atomic m_progress = 0.0F; std::atomic m_canceled = false; }; +} -} \ No newline at end of file + +#if defined(OS_WEB) +#include +#else +#include +#endif diff --git a/lib/libimhex/include/hex/helpers/http_requests_emscripten.hpp b/lib/libimhex/include/hex/helpers/http_requests_emscripten.hpp new file mode 100644 index 000000000..f5a094ed9 --- /dev/null +++ b/lib/libimhex/include/hex/helpers/http_requests_emscripten.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include + +#include + +namespace hex { + template + std::future> HttpRequest::downloadFile(const std::fs::path &path) { + return std::async(std::launch::async, [this, path] { + std::vector response; + + // Execute the request + auto result = this->executeImpl(response); + + // Write the result to the file + wolv::io::File file(path, wolv::io::File::Mode::Create); + file.writeBuffer(reinterpret_cast(result.getData().data()), result.getData().size()); + + return result; + }); + } + + template + std::future> HttpRequest::uploadFile(const std::fs::path &path, const std::string &mimeName) { + hex::unused(path, mimeName); + throw std::logic_error("Not implemented"); + } + + template + std::future> HttpRequest::uploadFile(std::vector data, const std::string &mimeName, const std::fs::path &fileName) { + hex::unused(data, mimeName, fileName); + throw std::logic_error("Not implemented"); + } + + template + std::future> HttpRequest::execute() { + return std::async(std::launch::async, [this] { + std::vector responseData; + + return this->executeImpl(responseData); + }); + } + + template + HttpRequest::Result HttpRequest::executeImpl(std::vector &data) { + strcpy(this->m_attr.requestMethod, this->m_method.c_str()); + this->m_attr.attributes = EMSCRIPTEN_FETCH_SYNCHRONOUS | EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + + if (!this->m_body.empty()) { + this->m_attr.requestData = this->m_body.c_str(); + this->m_attr.requestDataSize = this->m_body.size(); + } + + std::vector headers; + for (auto it = this->m_headers.begin(); it != this->m_headers.end(); it++) { + headers.push_back(it->first.c_str()); + headers.push_back(it->second.c_str()); + } + headers.push_back(nullptr); + this->m_attr.requestHeaders = headers.data(); + + // Send request + emscripten_fetch_t* fetch = emscripten_fetch(&this->m_attr, this->m_url.c_str()); + + data.resize(fetch->numBytes); + std::copy(fetch->data, fetch->data + fetch->numBytes, data.begin()); + + return Result(fetch->status, { data.begin(), data.end() }); + } + +} \ No newline at end of file diff --git a/lib/libimhex/include/hex/helpers/http_requests_native.hpp b/lib/libimhex/include/hex/helpers/http_requests_native.hpp new file mode 100644 index 000000000..7b5226940 --- /dev/null +++ b/lib/libimhex/include/hex/helpers/http_requests_native.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include +#include + +#include + +namespace hex { + + template + std::future> HttpRequest::downloadFile(const std::fs::path &path) { + return std::async(std::launch::async, [this, path] { + std::vector response; + + wolv::io::File file(path, wolv::io::File::Mode::Create); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToFile); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &file); + + return this->executeImpl(response); + }); + } + + template + std::future> HttpRequest::uploadFile(const std::fs::path &path, const std::string &mimeName) { + return std::async(std::launch::async, [this, path, mimeName]{ + auto fileName = wolv::util::toUTF8String(path.filename()); + + curl_mime *mime = curl_mime_init(this->m_curl); + curl_mimepart *part = curl_mime_addpart(mime); + + wolv::io::File file(path, wolv::io::File::Mode::Read); + + curl_mime_data_cb(part, file.getSize(), + [](char *buffer, size_t size, size_t nitems, void *arg) -> size_t { + auto file = static_cast(arg); + + return fread(buffer, size, nitems, file); + }, + [](void *arg, curl_off_t offset, int origin) -> int { + auto file = static_cast(arg); + + if (fseek(file, offset, origin) != 0) + return CURL_SEEKFUNC_CANTSEEK; + else + return CURL_SEEKFUNC_OK; + }, + [](void *arg) { + auto file = static_cast(arg); + + fclose(file); + }, + file.getHandle()); + curl_mime_filename(part, fileName.c_str()); + curl_mime_name(part, mimeName.c_str()); + + curl_easy_setopt(this->m_curl, CURLOPT_MIMEPOST, mime); + + std::vector responseData; + curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData); + + return this->executeImpl(responseData); + }); + } + + template + std::future> HttpRequest::uploadFile(std::vector data, const std::string &mimeName, const std::fs::path &fileName) { + return std::async(std::launch::async, [this, data = std::move(data), mimeName, fileName]{ + curl_mime *mime = curl_mime_init(this->m_curl); + curl_mimepart *part = curl_mime_addpart(mime); + + curl_mime_data(part, reinterpret_cast(data.data()), data.size()); + auto fileNameStr = wolv::util::toUTF8String(fileName.filename()); + curl_mime_filename(part, fileNameStr.c_str()); + curl_mime_name(part, mimeName.c_str()); + + curl_easy_setopt(this->m_curl, CURLOPT_MIMEPOST, mime); + + std::vector responseData; + curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData); + + return this->executeImpl(responseData); + }); + } + + template + std::future> HttpRequest::execute() { + return std::async(std::launch::async, [this] { + + std::vector responseData; + curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData); + + return this->executeImpl(responseData); + }); + } + + template + HttpRequest::Result HttpRequest::executeImpl(std::vector &data) { + curl_easy_setopt(this->m_curl, CURLOPT_URL, this->m_url.c_str()); + curl_easy_setopt(this->m_curl, CURLOPT_CUSTOMREQUEST, this->m_method.c_str()); + + setDefaultConfig(); + + if (!this->m_body.empty()) { + curl_easy_setopt(this->m_curl, CURLOPT_POSTFIELDS, this->m_body.c_str()); + } + + curl_slist *headers = nullptr; + headers = curl_slist_append(headers, "Cache-Control: no-cache"); + ON_SCOPE_EXIT { curl_slist_free_all(headers); }; + + for (auto &[key, value] : this->m_headers) { + std::string header = hex::format("{}: {}", key, value); + headers = curl_slist_append(headers, header.c_str()); + } + curl_easy_setopt(this->m_curl, CURLOPT_HTTPHEADER, headers); + + { + std::scoped_lock lock(this->m_transmissionMutex); + + auto result = curl_easy_perform(this->m_curl); + if (result != CURLE_OK){ + char *url = nullptr; + curl_easy_getinfo(this->m_curl, CURLINFO_EFFECTIVE_URL, &url); + log::error("Http request '{0} {1}' failed with error {2}: '{3}'", this->m_method, url, u32(result), curl_easy_strerror(result)); + checkProxyErrors(); + + return { }; + } + } + + long statusCode = 0; + curl_easy_getinfo(this->m_curl, CURLINFO_RESPONSE_CODE, &statusCode); + + return Result(statusCode, { data.begin(), data.end() }); + } + +} \ No newline at end of file diff --git a/lib/libimhex/include/hex/helpers/opengl.hpp b/lib/libimhex/include/hex/helpers/opengl.hpp index 1f8eb8c85..f0423041b 100644 --- a/lib/libimhex/include/hex/helpers/opengl.hpp +++ b/lib/libimhex/include/hex/helpers/opengl.hpp @@ -9,7 +9,14 @@ #include #include -#include +#if defined(OS_WEB) + #define GLFW_INCLUDE_ES3 + #include +#else + #include +#endif + +#include namespace hex::gl { diff --git a/lib/libimhex/include/hex/helpers/tar.hpp b/lib/libimhex/include/hex/helpers/tar.hpp index fee117da0..531c12901 100644 --- a/lib/libimhex/include/hex/helpers/tar.hpp +++ b/lib/libimhex/include/hex/helpers/tar.hpp @@ -52,7 +52,7 @@ namespace hex { bool m_valid = false; - // these will be updated when the constructor is called + // These will be updated when the constructor is called int m_tarOpenErrno = MTAR_ESUCCESS; int m_fileOpenErrno = 0; }; diff --git a/lib/libimhex/include/hex/helpers/types.hpp b/lib/libimhex/include/hex/helpers/types.hpp index 0df6343f2..983650941 100644 --- a/lib/libimhex/include/hex/helpers/types.hpp +++ b/lib/libimhex/include/hex/helpers/types.hpp @@ -21,7 +21,7 @@ namespace hex { struct Region { u64 address; - size_t size; + u64 size; [[nodiscard]] constexpr bool isWithin(const Region &other) const { if (*this == Invalid() || other == Invalid()) diff --git a/lib/libimhex/include/hex/plugin.hpp b/lib/libimhex/include/hex/plugin.hpp index 346498d1a..ac0c265a0 100644 --- a/lib/libimhex/include/hex/plugin.hpp +++ b/lib/libimhex/include/hex/plugin.hpp @@ -9,6 +9,13 @@ #include #include +#include + +#if defined (IMHEX_STATIC_LINK_PLUGINS) + #define IMHEX_PLUGIN_VISIBILITY_PREFIX static +#else + #define IMHEX_PLUGIN_VISIBILITY_PREFIX extern "C" [[gnu::visibility("default")]] +#endif /** * This macro is used to define all the required entry points for a plugin. @@ -16,16 +23,29 @@ */ #define IMHEX_PLUGIN_SETUP(name, author, description) IMHEX_PLUGIN_SETUP_IMPL(name, author, description) -#define IMHEX_PLUGIN_SETUP_IMPL(name, author, description) \ - extern "C" [[gnu::visibility("default")]] const char *getPluginName() { return name; } \ - extern "C" [[gnu::visibility("default")]] const char *getPluginAuthor() { return author; } \ - extern "C" [[gnu::visibility("default")]] const char *getPluginDescription() { return description; } \ - extern "C" [[gnu::visibility("default")]] const char *getCompatibleVersion() { return IMHEX_VERSION; } \ - extern "C" [[gnu::visibility("default")]] void setImGuiContext(ImGuiContext *ctx) { \ - ImGui::SetCurrentContext(ctx); \ - GImGui = ctx; \ - } \ - extern "C" [[gnu::visibility("default")]] void initializePlugin() +#define IMHEX_PLUGIN_SETUP_IMPL(name, author, description) \ + IMHEX_PLUGIN_VISIBILITY_PREFIX const char *getPluginName() { return name; } \ + IMHEX_PLUGIN_VISIBILITY_PREFIX const char *getPluginAuthor() { return author; } \ + IMHEX_PLUGIN_VISIBILITY_PREFIX const char *getPluginDescription() { return description; } \ + IMHEX_PLUGIN_VISIBILITY_PREFIX const char *getCompatibleVersion() { return IMHEX_VERSION; } \ + IMHEX_PLUGIN_VISIBILITY_PREFIX void setImGuiContext(ImGuiContext *ctx) { \ + ImGui::SetCurrentContext(ctx); \ + GImGui = ctx; \ + } \ + IMHEX_PLUGIN_VISIBILITY_PREFIX void initializePlugin(); \ + extern "C" [[gnu::visibility("default")]] void WOLV_TOKEN_CONCAT(forceLinkPlugin_, IMHEX_PLUGIN_NAME)() { \ + hex::PluginManager::addPlugin(hex::PluginFunctions { \ + initializePlugin, \ + getPluginName, \ + getPluginAuthor, \ + getPluginDescription, \ + getCompatibleVersion, \ + setImGuiContext, \ + nullptr, \ + nullptr \ + }); \ + } \ + IMHEX_PLUGIN_VISIBILITY_PREFIX void initializePlugin() /** * This macro is used to define subcommands defined by the plugin diff --git a/lib/libimhex/include/hex/providers/provider_data.hpp b/lib/libimhex/include/hex/providers/provider_data.hpp index f80cec521..b36e0b1dd 100644 --- a/lib/libimhex/include/hex/providers/provider_data.hpp +++ b/lib/libimhex/include/hex/providers/provider_data.hpp @@ -83,18 +83,18 @@ namespace hex { this->m_data.clear(); }); - // moves the data of this PerProvider instance from one provider to another + // Moves the data of this PerProvider instance from one provider to another EventManager::subscribe(this, [this](prv::Provider *from, prv::Provider *to) { - // get the value from the old provider, (removes it from the map) + // Get the value from the old provider, (removes it from the map) auto node = m_data.extract(from); - // ensure the value existed + // Ensure the value existed if (node.empty()) return; - // delete the value from the new provider, that we want to replace + // Delete the value from the new provider, that we want to replace this->m_data.erase(to); - // re-insert it with the key of the new provider + // Re-insert it with the key of the new provider node.key() = to; this->m_data.insert(std::move(node)); }); diff --git a/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h b/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h index 3dbf71b9d..756420772 100644 --- a/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h +++ b/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h @@ -177,7 +177,7 @@ namespace ImGui { } inline void TextFormattedWrappedSelectable(const std::string &fmt, auto &&...args) { - //Manually wrap text, using the letter M (generally the widest character in non-monospaced fonts) to calculate the character width to use. + // Manually wrap text, using the letter M (generally the widest character in non-monospaced fonts) to calculate the character width to use. auto text = wolv::util::wrapMonospacedString( hex::format(fmt, std::forward(args)...), ImGui::CalcTextSize("M").x, diff --git a/lib/libimhex/source/api/content_registry.cpp b/lib/libimhex/source/api/content_registry.cpp index 8242d4edc..62270ca2b 100644 --- a/lib/libimhex/source/api/content_registry.cpp +++ b/lib/libimhex/source/api/content_registry.cpp @@ -8,6 +8,10 @@ #include #include +#if defined(OS_WEB) +#include +#include +#endif #include @@ -18,7 +22,7 @@ namespace hex { namespace ContentRegistry::Settings { - constexpr auto SettingsFile = "settings.json"; + [[maybe_unused]] constexpr auto SettingsFile = "settings.json"; namespace impl { @@ -49,43 +53,71 @@ namespace hex { return settings; } - void load() { - bool loaded = false; - for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) { - wolv::io::File file(dir / SettingsFile, wolv::io::File::Mode::Read); + #if defined(OS_WEB) + void load() { + char *data = (char *) MAIN_THREAD_EM_ASM_INT({ + let data = localStorage.getItem("config"); + return data ? stringToNewUTF8(data) : null; + }); - if (file.isValid()) { - getSettingsData() = nlohmann::json::parse(file.readString()); - loaded = true; - break; + if (data == nullptr) { + store(); + } else { + getSettingsData() = nlohmann::json::parse(data); } } - if (!loaded) - store(); - } - - void store() { - // During a crash settings can be empty, causing them to be overwritten. - if(getSettingsData().empty()) { - return; + void store() { + auto data = getSettingsData().dump(); + MAIN_THREAD_EM_ASM({ + localStorage.setItem("config", UTF8ToString($0)); + }, data.c_str()); } - for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) { - wolv::io::File file(dir / SettingsFile, wolv::io::File::Mode::Create); + void clear() { + MAIN_THREAD_EM_ASM({ + localStorage.removeItem("config"); + }); + } + #else + void load() { + bool loaded = false; + for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) { + wolv::io::File file(dir / SettingsFile, wolv::io::File::Mode::Read); - if (file.isValid()) { - file.writeString(getSettingsData().dump(4)); - break; + if (file.isValid()) { + getSettingsData() = nlohmann::json::parse(file.readString()); + loaded = true; + break; + } + } + + if (!loaded) + store(); + } + + void store() { + // During a crash settings can be empty, causing them to be overwritten. + if(getSettingsData().empty()) { + return; + } + + for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) { + wolv::io::File file(dir / SettingsFile, wolv::io::File::Mode::Create); + + if (file.isValid()) { + file.writeString(getSettingsData().dump(4)); + break; + } } } - } - void clear() { - for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) { - wolv::io::fs::remove(dir / SettingsFile); + void clear() { + for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) { + wolv::io::fs::remove(dir / SettingsFile); + } } - } + #endif static auto getCategoryEntry(const std::string &unlocalizedCategory) { auto &entries = getEntries(); diff --git a/lib/libimhex/source/api/imhex_api.cpp b/lib/libimhex/source/api/imhex_api.cpp index 7eb451b89..0eca8739c 100644 --- a/lib/libimhex/source/api/imhex_api.cpp +++ b/lib/libimhex/source/api/imhex_api.cpp @@ -370,7 +370,7 @@ namespace hex { namespace impl { - // default to true means we forward to ourselves by default + // Default to true means we forward to ourselves by default static bool s_isMainInstance = true; void setMainInstanceStatus(bool status) { @@ -547,6 +547,8 @@ namespace hex { return "Linux"; #elif defined(OS_MACOS) return "macOS"; + #elif defined(OS_WEB) + return "Web"; #else return "Unknown"; #endif @@ -559,7 +561,7 @@ namespace hex { ::GetVersionExA(&info); return hex::format("{}.{}.{}", info.dwMajorVersion, info.dwMinorVersion, info.dwBuildNumber); - #elif defined(OS_LINUX) || defined(OS_MACOS) + #elif defined(OS_LINUX) || defined(OS_MACOS) || defined(OS_WEB) struct utsname details; if (uname(&details) != 0) { @@ -591,7 +593,7 @@ namespace hex { default: return "Unknown"; } - #elif defined(OS_LINUX) || defined(OS_MACOS) + #elif defined(OS_LINUX) || defined(OS_MACOS) || defined(OS_WEB) struct utsname details; if (uname(&details) != 0) { diff --git a/lib/libimhex/source/api/plugin_manager.cpp b/lib/libimhex/source/api/plugin_manager.cpp index 47a438cdb..6000bc204 100644 --- a/lib/libimhex/source/api/plugin_manager.cpp +++ b/lib/libimhex/source/api/plugin_manager.cpp @@ -12,6 +12,7 @@ namespace hex { Plugin::Plugin(const std::fs::path &path) : m_path(path) { + #if defined(OS_WINDOWS) this->m_handle = LoadLibraryW(path.c_str()); @@ -28,38 +29,30 @@ namespace hex { } #endif - this->m_initializePluginFunction = getPluginFunction("initializePlugin"); - this->m_getPluginNameFunction = getPluginFunction("getPluginName"); - this->m_getPluginAuthorFunction = getPluginFunction("getPluginAuthor"); - this->m_getPluginDescriptionFunction = getPluginFunction("getPluginDescription"); - this->m_getCompatibleVersionFunction = getPluginFunction("getCompatibleVersion"); - this->m_setImGuiContextFunction = getPluginFunction("setImGuiContext"); - this->m_isBuiltinPluginFunction = getPluginFunction("isBuiltinPlugin"); - this->m_getSubCommandsFunction = getPluginFunction("getSubCommands"); + this->m_functions.initializePluginFunction = getPluginFunction("initializePlugin"); + this->m_functions.getPluginNameFunction = getPluginFunction("getPluginName"); + this->m_functions.getPluginAuthorFunction = getPluginFunction("getPluginAuthor"); + this->m_functions.getPluginDescriptionFunction = getPluginFunction("getPluginDescription"); + this->m_functions.getCompatibleVersionFunction = getPluginFunction("getCompatibleVersion"); + this->m_functions.setImGuiContextFunction = getPluginFunction("setImGuiContext"); + this->m_functions.isBuiltinPluginFunction = getPluginFunction("isBuiltinPlugin"); + this->m_functions.getSubCommandsFunction = getPluginFunction("getSubCommands"); } + Plugin::Plugin(hex::PluginFunctions functions) { + this->m_handle = nullptr; + this->m_functions = functions; + } + + Plugin::Plugin(Plugin &&other) noexcept { this->m_handle = other.m_handle; + other.m_handle = nullptr; + this->m_path = std::move(other.m_path); - this->m_initializePluginFunction = other.m_initializePluginFunction; - this->m_getPluginNameFunction = other.m_getPluginNameFunction; - this->m_getPluginAuthorFunction = other.m_getPluginAuthorFunction; - this->m_getPluginDescriptionFunction = other.m_getPluginDescriptionFunction; - this->m_getCompatibleVersionFunction = other.m_getCompatibleVersionFunction; - this->m_setImGuiContextFunction = other.m_setImGuiContextFunction; - this->m_isBuiltinPluginFunction = other.m_isBuiltinPluginFunction; - this->m_getSubCommandsFunction = other.m_getSubCommandsFunction; - - other.m_handle = nullptr; - other.m_initializePluginFunction = nullptr; - other.m_getPluginNameFunction = nullptr; - other.m_getPluginAuthorFunction = nullptr; - other.m_getPluginDescriptionFunction = nullptr; - other.m_getCompatibleVersionFunction = nullptr; - other.m_setImGuiContextFunction = nullptr; - other.m_isBuiltinPluginFunction = nullptr; - other.m_getSubCommandsFunction = nullptr; + this->m_functions = other.m_functions; + other.m_functions = {}; } Plugin::~Plugin() { @@ -67,15 +60,10 @@ namespace hex { if (this->m_handle != nullptr) FreeLibrary(this->m_handle); #else - if (this->m_handle != nullptr) - dlclose(this->m_handle); #endif } bool Plugin::initializePlugin() const { - if (this->m_handle == nullptr) - return false; - const auto pluginName = wolv::util::toUTF8String(this->m_path.filename()); const auto requestedVersion = getCompatibleVersion(); @@ -88,9 +76,9 @@ namespace hex { } } - if (this->m_initializePluginFunction != nullptr) { + if (this->m_functions.initializePluginFunction != nullptr) { try { - this->m_initializePluginFunction(); + this->m_functions.initializePluginFunction(); } catch (const std::exception &e) { log::error("Plugin '{}' threw an exception on init: {}", pluginName, e.what()); return false; @@ -99,6 +87,7 @@ namespace hex { return false; } } else { + log::error("Plugin '{}' does not have a proper entrypoint", pluginName); return false; } @@ -107,41 +96,41 @@ namespace hex { } std::string Plugin::getPluginName() const { - if (this->m_getPluginNameFunction != nullptr) - return this->m_getPluginNameFunction(); + if (this->m_functions.getPluginNameFunction != nullptr) + return this->m_functions.getPluginNameFunction(); else return hex::format("Unknown Plugin @ 0x{0:016X}", reinterpret_cast(this->m_handle)); } std::string Plugin::getPluginAuthor() const { - if (this->m_getPluginAuthorFunction != nullptr) - return this->m_getPluginAuthorFunction(); + if (this->m_functions.getPluginAuthorFunction != nullptr) + return this->m_functions.getPluginAuthorFunction(); else return "Unknown"; } std::string Plugin::getPluginDescription() const { - if (this->m_getPluginDescriptionFunction != nullptr) - return this->m_getPluginDescriptionFunction(); + if (this->m_functions.getPluginDescriptionFunction != nullptr) + return this->m_functions.getPluginDescriptionFunction(); else return ""; } std::string Plugin::getCompatibleVersion() const { - if (this->m_getCompatibleVersionFunction != nullptr) - return this->m_getCompatibleVersionFunction(); + if (this->m_functions.getCompatibleVersionFunction != nullptr) + return this->m_functions.getCompatibleVersionFunction(); else return ""; } void Plugin::setImGuiContext(ImGuiContext *ctx) const { - if (this->m_setImGuiContextFunction != nullptr) - this->m_setImGuiContextFunction(ctx); + if (this->m_functions.setImGuiContextFunction != nullptr) + this->m_functions.setImGuiContextFunction(ctx); } [[nodiscard]] bool Plugin::isBuiltinPlugin() const { - if (this->m_isBuiltinPluginFunction != nullptr) - return this->m_isBuiltinPluginFunction(); + if (this->m_functions.isBuiltinPluginFunction != nullptr) + return this->m_functions.isBuiltinPluginFunction(); else return false; } @@ -155,8 +144,8 @@ namespace hex { } std::span Plugin::getSubCommands() const { - if (this->m_getSubCommandsFunction != nullptr) { - auto result = this->m_getSubCommandsFunction(); + if (this->m_functions.getSubCommandsFunction != nullptr) { + auto result = this->m_functions.getSubCommandsFunction(); return *reinterpret_cast*>(result); } else return { }; @@ -171,43 +160,50 @@ namespace hex { #endif } - - namespace { - - std::fs::path s_pluginFolder; - std::vector s_plugins; - - } - bool PluginManager::load(const std::fs::path &pluginFolder) { if (!wolv::io::fs::exists(pluginFolder)) return false; - s_pluginFolder = pluginFolder; + getPluginPaths().push_back(pluginFolder); for (auto &pluginPath : std::fs::directory_iterator(pluginFolder)) { if (pluginPath.is_regular_file() && pluginPath.path().extension() == ".hexplug") - s_plugins.emplace_back(pluginPath.path()); + getPlugins().emplace_back(pluginPath.path()); } - if (s_plugins.empty()) + if (getPlugins().empty()) return false; return true; } void PluginManager::unload() { - s_plugins.clear(); - s_pluginFolder.clear(); + getPlugins().clear(); + getPluginPaths().clear(); } void PluginManager::reload() { + auto paths = getPluginPaths(); + PluginManager::unload(); - PluginManager::load(s_pluginFolder); + for (const auto &path : paths) + PluginManager::load(path); } - const std::vector &PluginManager::getPlugins() { - return s_plugins; + void PluginManager::addPlugin(hex::PluginFunctions functions) { + getPlugins().emplace_back(functions); + } + + std::vector &PluginManager::getPlugins() { + static std::vector plugins; + + return plugins; + } + + std::vector &PluginManager::getPluginPaths() { + static std::vector pluginPaths; + + return pluginPaths; } } diff --git a/lib/libimhex/source/api/task.cpp b/lib/libimhex/source/api/task.cpp index 14cc59149..98b7f16d9 100644 --- a/lib/libimhex/source/api/task.cpp +++ b/lib/libimhex/source/api/task.cpp @@ -49,6 +49,8 @@ namespace hex { RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); #elif defined(OS_LINUX) pthread_setname_np(pthread_self(), name.c_str()); + #elif defined(OS_WEB) + hex::unused(name); #elif defined(OS_MACOS) pthread_setname_np(name.c_str()); #endif diff --git a/lib/libimhex/source/helpers/crypto.cpp b/lib/libimhex/source/helpers/crypto.cpp index 7539d859d..7ce3b1d50 100644 --- a/lib/libimhex/source/helpers/crypto.cpp +++ b/lib/libimhex/source/helpers/crypto.cpp @@ -82,7 +82,7 @@ namespace hex::crypt { template requires (std::has_single_bit(NumBits)) class Crc { - // use reflected algorithm, so we reflect only if refin / refout is FALSE + // Use reflected algorithm, so we reflect only if refin / refout is FALSE // mask values, 0b1 << 64 is UB, so use 0b10 << 63 public: @@ -396,7 +396,7 @@ namespace hex::crypt { ON_SCOPE_EXIT { mbedtls_mpi_free(&ctx); }; - // read buffered + // Read buffered constexpr static auto BufferSize = 0x100; for (size_t offset = 0; offset < input.size(); offset += BufferSize) { std::string inputPart = input.substr(offset, std::min(BufferSize, input.size() - offset)); diff --git a/lib/libimhex/source/helpers/fs.cpp b/lib/libimhex/source/helpers/fs.cpp index 07d3beaa8..6ba9326d4 100644 --- a/lib/libimhex/source/helpers/fs.cpp +++ b/lib/libimhex/source/helpers/fs.cpp @@ -13,7 +13,13 @@ #include #elif defined(OS_LINUX) #include - #include + #include +#endif + +#if !defined(OS_WEB) +#include +#else +#include #endif #include @@ -84,66 +90,173 @@ namespace hex::fs { )); system(R"(osascript -e 'tell application "Finder" to activate')"); #elif defined(OS_LINUX) - // fallback to only opening the folder for now + // Fallback to only opening the folder for now // TODO actually select the file executeCmd({"xdg-open", wolv::util::toUTF8String(selectedFilePath.parent_path())}); #endif } - bool openFileBrowser(DialogMode mode, const std::vector &validExtensions, const std::function &callback, const std::string &defaultPath, bool multiple) { - NFD::ClearError(); + #if defined(OS_WEB) - if (NFD::Init() != NFD_OKAY) { - log::error("NFD init returned an error: {}", NFD::GetError()); - if (s_fileBrowserErrorCallback != nullptr) - s_fileBrowserErrorCallback(NFD::GetError() ? NFD::GetError() : "No details"); - return false; + std::function currentCallback; + + EMSCRIPTEN_KEEPALIVE + extern "C" void fileBrowserCallback(char* path) { + currentCallback(path); } - NFD::UniquePathU8 outPath; - NFD::UniquePathSet outPaths; - nfdresult_t result; - switch (mode) { - case DialogMode::Open: - if (multiple) - result = NFD::OpenDialogMultiple(outPaths, validExtensions.data(), validExtensions.size(), defaultPath.empty() ? nullptr : defaultPath.c_str()); - else - result = NFD::OpenDialog(outPath, validExtensions.data(), validExtensions.size(), defaultPath.empty() ? nullptr : defaultPath.c_str()); - break; - case DialogMode::Save: - result = NFD::SaveDialog(outPath, validExtensions.data(), validExtensions.size(), defaultPath.empty() ? nullptr : defaultPath.c_str()); - break; - case DialogMode::Folder: - result = NFD::PickFolder(outPath, defaultPath.empty() ? nullptr : defaultPath.c_str()); - break; - default: - std::unreachable(); - } + EM_JS(int, callJs_saveFile, (const char *rawFilename), { + let filename = UTF8ToString(rawFilename) || "file.bin"; + FS.createPath("/", "savedFiles"); - if (result == NFD_OKAY){ - if(outPath != nullptr) { - callback(reinterpret_cast(outPath.get())); + if (FS.analyzePath(filename).exists) { + FS.unlink(filename); } - if (outPaths != nullptr) { - nfdpathsetsize_t numPaths = 0; - if (NFD::PathSet::Count(outPaths, numPaths) == NFD_OKAY) { - for (size_t i = 0; i < numPaths; i++) { - NFD::UniquePathSetPath path; - if (NFD::PathSet::GetPath(outPaths, i, path) == NFD_OKAY) - callback(reinterpret_cast(path.get())); + + // Call callback that will write the file + Module._fileBrowserCallback(stringToNewUTF8("/savedFiles/" + filename)); + + let data = FS.readFile("/savedFiles/" + filename); + + const reader = Object.assign(new FileReader(), { + onload: () => { + + // Show popup to user to download + let saver = document.createElement('a'); + saver.href = reader.result; + saver.download = filename; + saver.style = "display: none"; + + saver.click(); + + }, + onerror: () => { + throw new Error(reader.error); + }, + }); + reader.readAsDataURL(new File([data], "", { type: "application/octet-stream" })); + + }); + + EM_JS(int, callJs_openFile, (bool multiple), { + let selector = document.createElement("input"); + selector.type = "file"; + selector.style = "display: none"; + if (multiple) { + selector.multiple = true; + } + selector.onchange = () => { + if (selector.files.length == 0) return; + + FS.createPath("/", "openedFiles"); + for (let file of selector.files) { + const fr = new FileReader(); + fr.onload = () => { + let path = "/openedFiles/"+file.name; + if (FS.analyzePath(path).exists) { + FS.unlink(path); + } + FS.createDataFile("/openedFiles/", file.name, fr.result, true, true); + Module._fileBrowserCallback(stringToNewUTF8(path)); + }; + + fr.readAsBinaryString(file); + } + }; + selector.click(); + }); + + bool openFileBrowser(DialogMode mode, const std::vector &validExtensions, const std::function &callback, const std::string &defaultPath, bool multiple) { + switch (mode) { + case DialogMode::Open: { + currentCallback = callback; + callJs_openFile(multiple); + break; + } + case DialogMode::Save: { + currentCallback = callback; + std::fs::path path; + + if (!defaultPath.empty()) + path = std::fs::path(defaultPath).filename(); + else if (!validExtensions.empty()) + path = "file." + validExtensions[0].spec; + + callJs_saveFile(path.filename().string().c_str()); + break; + } + case DialogMode::Folder: { + throw std::logic_error("Selecting a folder is not implemented"); + return false; + } + default: + std::unreachable(); + } + return true; + } + + #else + + bool openFileBrowser(DialogMode mode, const std::vector &validExtensions, const std::function &callback, const std::string &defaultPath, bool multiple) { + std::vector validExtensionsNfd; + for (auto ext : validExtensions) { + validExtensionsNfd.emplace_back(nfdfilteritem_t{ext.name.c_str(), ext.spec.c_str()}); + } + NFD::ClearError(); + + if (NFD::Init() != NFD_OKAY) { + log::error("NFD init returned an error: {}", NFD::GetError()); + if (s_fileBrowserErrorCallback != nullptr) + s_fileBrowserErrorCallback(NFD::GetError() ? NFD::GetError() : "No details"); + return false; + } + + NFD::UniquePathU8 outPath; + NFD::UniquePathSet outPaths; + nfdresult_t result; + switch (mode) { + case DialogMode::Open: + if (multiple) + result = NFD::OpenDialogMultiple(outPaths, validExtensionsNfd.data(), validExtensionsNfd.size(), defaultPath.empty() ? nullptr : defaultPath.c_str()); + else + result = NFD::OpenDialog(outPath, validExtensionsNfd.data(), validExtensionsNfd.size(), defaultPath.empty() ? nullptr : defaultPath.c_str()); + break; + case DialogMode::Save: + result = NFD::SaveDialog(outPath, validExtensionsNfd.data(), validExtensionsNfd.size(), defaultPath.empty() ? nullptr : defaultPath.c_str()); + break; + case DialogMode::Folder: + result = NFD::PickFolder(outPath, defaultPath.empty() ? nullptr : defaultPath.c_str()); + break; + default: + std::unreachable(); + } + + if (result == NFD_OKAY){ + if(outPath != nullptr) { + callback(reinterpret_cast(outPath.get())); + } + if (outPaths != nullptr) { + nfdpathsetsize_t numPaths = 0; + if (NFD::PathSet::Count(outPaths, numPaths) == NFD_OKAY) { + for (size_t i = 0; i < numPaths; i++) { + NFD::UniquePathSetPath path; + if (NFD::PathSet::GetPath(outPaths, i, path) == NFD_OKAY) + callback(reinterpret_cast(path.get())); + } } } + } else if (result == NFD_ERROR) { + log::error("Requested file dialog returned an error: {}", NFD::GetError()); + if (s_fileBrowserErrorCallback != nullptr) + s_fileBrowserErrorCallback(NFD::GetError() ? NFD::GetError() : "No details"); } - } else if (result == NFD_ERROR) { - log::error("Requested file dialog returned an error: {}", NFD::GetError()); - if (s_fileBrowserErrorCallback != nullptr) - s_fileBrowserErrorCallback(NFD::GetError() ? NFD::GetError() : "No details"); + + NFD::Quit(); + + return result == NFD_OKAY; } - NFD::Quit(); - - return result == NFD_OKAY; - } + #endif std::vector getDataPaths() { std::vector paths; @@ -202,7 +315,7 @@ namespace hex::fs { return getDataPaths(); #elif defined(OS_MACOS) return getDataPaths(); - #elif defined(OS_LINUX) + #elif defined(OS_LINUX) || defined(OS_WEB) return {xdg::ConfigHomeDir() / "imhex"}; #endif } diff --git a/lib/libimhex/source/helpers/http_requests.cpp b/lib/libimhex/source/helpers/http_requests.cpp index 88e0f1572..cfa4c762e 100644 --- a/lib/libimhex/source/helpers/http_requests.cpp +++ b/lib/libimhex/source/helpers/http_requests.cpp @@ -1,47 +1,9 @@ #include +#include + namespace hex { - namespace { - - std::string s_proxyUrl; - - } - - - HttpRequest::HttpRequest(std::string method, std::string url) : m_method(std::move(method)), m_url(std::move(url)) { - AT_FIRST_TIME { - curl_global_init(CURL_GLOBAL_ALL); - }; - - AT_FINAL_CLEANUP { - curl_global_cleanup(); - }; - - this->m_curl = curl_easy_init(); - } - - HttpRequest::~HttpRequest() { - curl_easy_cleanup(this->m_curl); - } - - void HttpRequest::setDefaultConfig() { - curl_easy_setopt(this->m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); - curl_easy_setopt(this->m_curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); - curl_easy_setopt(this->m_curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(this->m_curl, CURLOPT_USERAGENT, "ImHex/1.0"); - curl_easy_setopt(this->m_curl, CURLOPT_DEFAULT_PROTOCOL, "https"); - curl_easy_setopt(this->m_curl, CURLOPT_SSL_VERIFYPEER, 1L); - curl_easy_setopt(this->m_curl, CURLOPT_SSL_VERIFYHOST, 2L); - curl_easy_setopt(this->m_curl, CURLOPT_TIMEOUT_MS, 0L); - curl_easy_setopt(this->m_curl, CURLOPT_CONNECTTIMEOUT_MS, this->m_timeout); - curl_easy_setopt(this->m_curl, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt(this->m_curl, CURLOPT_NOPROGRESS, 0L); - curl_easy_setopt(this->m_curl, CURLOPT_XFERINFODATA, this); - curl_easy_setopt(this->m_curl, CURLOPT_XFERINFOFUNCTION, progressCallback); - curl_easy_setopt(this->m_curl, CURLOPT_PROXY, s_proxyUrl.c_str()); - } - size_t HttpRequest::writeToVector(void *contents, size_t size, size_t nmemb, void *userdata) { auto &response = *reinterpret_cast*>(userdata); auto startSize = response.size(); @@ -60,27 +22,36 @@ namespace hex { return size * nmemb; } - int HttpRequest::progressCallback(void *contents, curl_off_t dlTotal, curl_off_t dlNow, curl_off_t ulTotal, curl_off_t ulNow) { - auto &request = *static_cast(contents); - - if (dlTotal > 0) - request.m_progress = float(dlNow) / dlTotal; - else if (ulTotal > 0) - request.m_progress = float(ulNow) / ulTotal; - else - request.m_progress = 0.0F; - - return request.m_canceled ? CURLE_ABORTED_BY_CALLBACK : CURLE_OK; - } - - void HttpRequest::setProxy(std::string proxy) { - s_proxyUrl = std::move(proxy); - } - - void HttpRequest::checkProxyErrors() { - if (!s_proxyUrl.empty()){ - log::info("A custom proxy '{0}' is in use. Is it working correctly?", s_proxyUrl); + std::string HttpRequest::urlEncode(const std::string &input) { + std::string result; + for (char c : input){ + if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') + result += c; + else + result += hex::format("%02X", c); } + return result; + } + + std::string HttpRequest::urlDecode(const std::string &input) { + std::string result; + + for (u32 i = 0; i < input.size(); i++){ + if (input[i] != '%'){ + if (input[i] == '+') + result += ' '; + else + result += input[i]; + } else { + const auto hex = crypt::decode16(input.substr(i + 1, 2)); + if (hex.empty()) + return ""; + + result += char(hex[0]); + i += 2; + } + } + return input; } } \ No newline at end of file diff --git a/lib/libimhex/source/helpers/http_requests_emscripten.cpp b/lib/libimhex/source/helpers/http_requests_emscripten.cpp new file mode 100644 index 000000000..d13a0698c --- /dev/null +++ b/lib/libimhex/source/helpers/http_requests_emscripten.cpp @@ -0,0 +1,55 @@ +#if defined(OS_WEB) + +#include + +namespace hex { + + HttpRequest::HttpRequest(std::string method, std::string url) : m_method(std::move(method)), m_url(std::move(url)) { + emscripten_fetch_attr_init(&this->m_attr); + } + + HttpRequest::HttpRequest(HttpRequest &&other) noexcept { + this->m_attr = other.m_attr; + + this->m_method = std::move(other.m_method); + this->m_url = std::move(other.m_url); + this->m_headers = std::move(other.m_headers); + this->m_body = std::move(other.m_body); + } + + HttpRequest& HttpRequest::operator=(HttpRequest &&other) noexcept { + this->m_attr = other.m_attr; + + this->m_method = std::move(other.m_method); + this->m_url = std::move(other.m_url); + this->m_headers = std::move(other.m_headers); + this->m_body = std::move(other.m_body); + + return *this; + } + + HttpRequest::~HttpRequest() { } + + void HttpRequest::setDefaultConfig() { } + + std::future>> HttpRequest::downloadFile() { + return std::async(std::launch::async, [this] { + std::vector response; + + return this->executeImpl>(response); + }); + } + + void HttpRequest::setProxy(std::string proxy) { + hex::unused(proxy); + } + + void HttpRequest::checkProxyErrors() { } + + int HttpRequest::progressCallback(void *contents, curl_off_t dlTotal, curl_off_t dlNow, curl_off_t ulTotal, curl_off_t ulNow) { + hex::unused(contents, dlTotal, dlNow, ulTotal, ulNow); + return -1; + } +} + +#endif \ No newline at end of file diff --git a/lib/libimhex/source/helpers/http_requests_native.cpp b/lib/libimhex/source/helpers/http_requests_native.cpp new file mode 100644 index 000000000..cae9953df --- /dev/null +++ b/lib/libimhex/source/helpers/http_requests_native.cpp @@ -0,0 +1,105 @@ +#if !defined(OS_WEB) + +#include + +namespace hex { + + namespace { + std::string s_proxyUrl; + } + + HttpRequest::HttpRequest(std::string method, std::string url) : m_method(std::move(method)), m_url(std::move(url)) { + AT_FIRST_TIME { + curl_global_init(CURL_GLOBAL_ALL); + }; + + AT_FINAL_CLEANUP { + curl_global_cleanup(); + }; + + this->m_curl = curl_easy_init(); + } + + HttpRequest::~HttpRequest() { + curl_easy_cleanup(this->m_curl); + } + + HttpRequest::HttpRequest(HttpRequest &&other) noexcept { + this->m_curl = other.m_curl; + other.m_curl = nullptr; + + this->m_method = std::move(other.m_method); + this->m_url = std::move(other.m_url); + this->m_headers = std::move(other.m_headers); + this->m_body = std::move(other.m_body); + } + + HttpRequest& HttpRequest::operator=(HttpRequest &&other) noexcept { + this->m_curl = other.m_curl; + other.m_curl = nullptr; + + this->m_method = std::move(other.m_method); + this->m_url = std::move(other.m_url); + this->m_headers = std::move(other.m_headers); + this->m_body = std::move(other.m_body); + + return *this; + } + + void HttpRequest::setDefaultConfig() { + curl_easy_setopt(this->m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + curl_easy_setopt(this->m_curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); + curl_easy_setopt(this->m_curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(this->m_curl, CURLOPT_USERAGENT, "ImHex/1.0"); + curl_easy_setopt(this->m_curl, CURLOPT_DEFAULT_PROTOCOL, "https"); + curl_easy_setopt(this->m_curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(this->m_curl, CURLOPT_SSL_VERIFYHOST, 2L); + curl_easy_setopt(this->m_curl, CURLOPT_TIMEOUT_MS, 0L); + curl_easy_setopt(this->m_curl, CURLOPT_CONNECTTIMEOUT_MS, this->m_timeout); + curl_easy_setopt(this->m_curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(this->m_curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(this->m_curl, CURLOPT_XFERINFODATA, this); + curl_easy_setopt(this->m_curl, CURLOPT_XFERINFOFUNCTION, progressCallback); + curl_easy_setopt(this->m_curl, CURLOPT_PROXY, s_proxyUrl.c_str()); + } + + std::future>> HttpRequest::downloadFile() { + return std::async(std::launch::async, [this] { + std::vector response; + + curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector); + curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &response); + + return this->executeImpl>(response); + }); + } + + + + void HttpRequest::setProxy(std::string proxy) { + s_proxyUrl = std::move(proxy); + } + + void HttpRequest::checkProxyErrors() { + if (!s_proxyUrl.empty()){ + log::info("A custom proxy '{0}' is in use. Is it working correctly?", s_proxyUrl); + } + } + + int HttpRequest::progressCallback(void *contents, curl_off_t dlTotal, curl_off_t dlNow, curl_off_t ulTotal, curl_off_t ulNow) { + auto &request = *static_cast(contents); + + if (dlTotal > 0) + request.m_progress = float(dlNow) / dlTotal; + else if (ulTotal > 0) + request.m_progress = float(ulNow) / ulTotal; + else + request.m_progress = 0.0F; + + return request.m_canceled ? CURLE_ABORTED_BY_CALLBACK : CURLE_OK; + } + +} + + +#endif \ No newline at end of file diff --git a/lib/libimhex/source/helpers/opengl.cpp b/lib/libimhex/source/helpers/opengl.cpp index 81ac3c6bd..0a2d489ef 100644 --- a/lib/libimhex/source/helpers/opengl.cpp +++ b/lib/libimhex/source/helpers/opengl.cpp @@ -5,6 +5,13 @@ #include +#if defined(OS_WEB) + #define GLFW_INCLUDE_ES3 + #include +#else + #include +#endif + namespace hex::gl { Shader::Shader(std::string_view vertexSource, std::string_view fragmentSource) { diff --git a/lib/libimhex/source/helpers/tar.cpp b/lib/libimhex/source/helpers/tar.cpp index 75d75563b..69c76da08 100644 --- a/lib/libimhex/source/helpers/tar.cpp +++ b/lib/libimhex/source/helpers/tar.cpp @@ -35,7 +35,7 @@ namespace hex { if (!this->m_valid) { this->m_tarOpenErrno = tar_error; - // hopefully this errno corresponds to the file open call in mtar_open + // Hopefully this errno corresponds to the file open call in mtar_open this->m_fileOpenErrno = errno; } } diff --git a/lib/libimhex/source/helpers/utils.cpp b/lib/libimhex/source/helpers/utils.cpp index 4b93238e3..1231fcb0a 100644 --- a/lib/libimhex/source/helpers/utils.cpp +++ b/lib/libimhex/source/helpers/utils.cpp @@ -20,6 +20,8 @@ #elif defined(OS_MACOS) #include #include +#elif defined(OS_WEB) + #include "emscripten.h" #endif namespace hex { @@ -319,6 +321,8 @@ namespace hex { hex::unused(system(hex::format("open {0}", command).c_str())); #elif defined(OS_LINUX) executeCmd({"xdg-open", command}); + #elif defined(OS_WEB) + hex::unused(command); #endif } @@ -332,6 +336,10 @@ namespace hex { openWebpageMacos(url.c_str()); #elif defined(OS_LINUX) executeCmd({"xdg-open", url}); + #elif defined(OS_WEB) + EM_ASM({ + window.open(UTF8ToString($0), '_blank'); + }, url.c_str()); #else #warning "Unknown OS, can't open webpages" #endif @@ -497,26 +505,27 @@ namespace hex { } bool isProcessElevated() { -#if defined(OS_WINDOWS) - bool elevated = false; - HANDLE token = INVALID_HANDLE_VALUE; + #if defined(OS_WINDOWS) + bool elevated = false; + HANDLE token = INVALID_HANDLE_VALUE; - if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) { - TOKEN_ELEVATION elevation; - DWORD elevationSize = sizeof(TOKEN_ELEVATION); + if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) { + TOKEN_ELEVATION elevation; + DWORD elevationSize = sizeof(TOKEN_ELEVATION); - if (::GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &elevationSize)) - elevated = elevation.TokenIsElevated; - } + if (::GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &elevationSize)) + elevated = elevation.TokenIsElevated; + } - if (token != INVALID_HANDLE_VALUE) - ::CloseHandle(token); + if (token != INVALID_HANDLE_VALUE) + ::CloseHandle(token); - return elevated; - -#elif defined(OS_LINUX) || defined(OS_MACOS) - return getuid() == 0 || getuid() != geteuid(); -#endif + return elevated; + #elif defined(OS_LINUX) || defined(OS_MACOS) + return getuid() == 0 || getuid() != geteuid(); + #else + return false; + #endif } std::optional getEnvironmentVariable(const std::string &env) { diff --git a/lib/libimhex/source/subcommands/subcommands.cpp b/lib/libimhex/source/subcommands/subcommands.cpp index 8971d7ad5..10c82e67d 100644 --- a/lib/libimhex/source/subcommands/subcommands.cpp +++ b/lib/libimhex/source/subcommands/subcommands.cpp @@ -34,26 +34,26 @@ namespace hex::subcommands { auto argsIter = args.begin(); - // get subcommand associated with the first argument + // Get subcommand associated with the first argument std::optional currentSubCommand = findSubCommand(*argsIter); if (currentSubCommand) { argsIter++; - // if it is a valid subcommand, remove it from the argument list + // If it is a valid subcommand, remove it from the argument list } else { - // if no (valid) subcommand was provided, the default one is --open + // If no (valid) subcommand was provided, the default one is --open currentSubCommand = findSubCommand("--open"); } - // arguments of the current subcommand + // Arguments of the current subcommand std::vector currentSubCommandArgs; - // compute all subcommands to run + // Compute all subcommands to run while (argsIter != args.end()) { const std::string &arg = *argsIter; if (arg == "--othercmd") { - // save command to run + // Save command to run if (currentSubCommand) { subCommands.emplace_back(*currentSubCommand, currentSubCommandArgs); } @@ -62,10 +62,10 @@ namespace hex::subcommands { currentSubCommandArgs = { }; } else if (currentSubCommand) { - // add current argument to the current command + // Add current argument to the current command currentSubCommandArgs.push_back(arg); } else { - // get next subcommand from current argument + // Get next subcommand from current argument currentSubCommand = findSubCommand(arg); if (!currentSubCommand) { log::error("No subcommand named '{}' found", arg); @@ -76,17 +76,17 @@ namespace hex::subcommands { argsIter++; } - // save last command to run + // Save last command to run if (currentSubCommand) { subCommands.emplace_back(*currentSubCommand, currentSubCommandArgs); } - // run the subcommands + // Run the subcommands for (auto& subCommandPair : subCommands) { subCommandPair.first.callback(subCommandPair.second); } - // exit the process if its not the main instance (the commands have been forwarded to another instance) + // Exit the process if its not the main instance (the commands have been forwarded to another instance) if (!ImHexApi::System::isMainInstance()) { exit(0); } diff --git a/lib/libimhex/source/ui/imgui_imhex_extensions.cpp b/lib/libimhex/source/ui/imgui_imhex_extensions.cpp index 111091c4a..cb6b76ee9 100644 --- a/lib/libimhex/source/ui/imgui_imhex_extensions.cpp +++ b/lib/libimhex/source/ui/imgui_imhex_extensions.cpp @@ -9,8 +9,6 @@ #include -#include - #include #include diff --git a/main/gui/CMakeLists.txt b/main/gui/CMakeLists.txt index 69dbdf92f..b01a84aa4 100644 --- a/main/gui/CMakeLists.txt +++ b/main/gui/CMakeLists.txt @@ -8,11 +8,13 @@ add_executable(main ${APPLICATION_TYPE} source/window/win_window.cpp source/window/macos_window.cpp source/window/linux_window.cpp + source/window/web_window.cpp source/messaging/common.cpp source/messaging/linux.cpp source/messaging/macos.cpp source/messaging/win.cpp + source/messaging/web.cpp source/init/splash_window.cpp source/init/tasks.cpp @@ -29,6 +31,18 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../lib/external/libromfs ${CMAKE set_target_properties(${LIBROMFS_LIBRARY} PROPERTIES POSITION_INDEPENDENT_CODE ON) add_dependencies(imhex_all main) +if (EMSCRIPTEN) + target_link_options(main PRIVATE -sUSE_GLFW=3 -sUSE_PTHREADS=1 -sALLOW_MEMORY_GROWTH=1) + target_link_options(main PRIVATE -sTOTAL_MEMORY=134217728) + target_link_options(main PRIVATE -sMAX_WEBGL_VERSION=2) + target_link_options(main PRIVATE -sEXPORTED_RUNTIME_METHODS=ccall) + target_link_options(main PRIVATE -sFETCH) + target_link_options(main PRIVATE -sWASM_BIGINT) + target_link_options(main PRIVATE -O1) + target_link_options(main PRIVATE -sLEGACY_GL_EMULATION) + target_link_libraries(main PRIVATE idbfs.js) +endif () + set_target_properties(main PROPERTIES OUTPUT_NAME ${IMHEX_APPLICATION_NAME} RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../.. diff --git a/main/gui/include/window.hpp b/main/gui/include/window.hpp index de061d56d..3110230b8 100644 --- a/main/gui/include/window.hpp +++ b/main/gui/include/window.hpp @@ -28,6 +28,8 @@ namespace hex { static void initNative(); + void resize(i32 width, i32 height); + private: void setupNativeWindow(); void beginNativeWindowFrame(); diff --git a/main/gui/source/init/splash_window.cpp b/main/gui/source/init/splash_window.cpp index 236499628..d78bde129 100644 --- a/main/gui/source/init/splash_window.cpp +++ b/main/gui/source/init/splash_window.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -29,6 +28,14 @@ #include #include +#if defined(OS_WEB) + #define GLFW_INCLUDE_ES3 + #include + #include +#else + #include +#endif + using namespace std::literals::chrono_literals; namespace hex::init { @@ -324,6 +331,8 @@ namespace hex::init { glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); + glfwWindowHint(GLFW_SAMPLES, 1); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // Create the splash screen window this->m_window = glfwCreateWindow(1, 400, "Starting ImHex...", nullptr, nullptr); @@ -373,6 +382,8 @@ namespace hex::init { #if defined(OS_MACOS) ImGui_ImplOpenGL3_Init("#version 150"); + #elif defined(OS_WEB) + ImGui_ImplOpenGL3_Init(); #else ImGui_ImplOpenGL3_Init("#version 130"); #endif diff --git a/main/gui/source/init/tasks.cpp b/main/gui/source/init/tasks.cpp index 9bf3b9335..09832227e 100644 --- a/main/gui/source/init/tasks.cpp +++ b/main/gui/source/init/tasks.cpp @@ -80,6 +80,13 @@ namespace hex::init { } TaskManager::createBackgroundTask("Sending statistics...", [uuid, versionString](auto&) { + // To avoid potentially flooding our database with lots of dead users + // from people just visiting the website, don't send telemetry data from + // the web version + #if defined(OS_WEB) + return; + #endif + // Make telemetry request nlohmann::json telemetry = { { "uuid", uuid }, @@ -145,7 +152,7 @@ namespace hex::init { wolv::io::File newConfigFile(newConfigPath / "settings.json", wolv::io::File::Mode::Read); if (!newConfigFile.isValid()) { - // find an old config + // Find an old config std::fs::path oldConfigPath; for (const auto &dir : hex::fs::appendPath(hex::fs::getDataPaths(), "config")) { wolv::io::File oldConfigFile(dir / "settings.json", wolv::io::File::Mode::Read); @@ -493,15 +500,17 @@ namespace hex::init { // ImHex requires exactly one built-in plugin // If no built-in plugin or more than one was found, something's wrong and we can't continue - if (builtinPlugins == 0) { - log::error("Built-in plugin not found!"); - ImHexApi::System::impl::addInitArgument("no-builtin-plugin"); - return false; - } else if (builtinPlugins > 1) { - log::error("Found more than one built-in plugin!"); - ImHexApi::System::impl::addInitArgument("multiple-builtin-plugins"); - return false; - } + #if !defined(EMSCRIPTEN) + if (builtinPlugins == 0) { + log::error("Built-in plugin not found!"); + ImHexApi::System::impl::addInitArgument("no-builtin-plugin"); + return false; + } else if (builtinPlugins > 1) { + log::error("Found more than one built-in plugin!"); + ImHexApi::System::impl::addInitArgument("multiple-builtin-plugins"); + return false; + } + #endif return true; } diff --git a/main/gui/source/main.cpp b/main/gui/source/main.cpp index 3f32e35c2..d722853dc 100644 --- a/main/gui/source/main.cpp +++ b/main/gui/source/main.cpp @@ -19,6 +19,11 @@ #include #include +#if defined(OS_WEB) + #include + #include +#endif + using namespace hex; namespace { @@ -66,6 +71,7 @@ namespace { /** * @brief Displays ImHex's splash screen and runs all initialization tasks. The splash screen will be displayed until all tasks have finished. */ + [[maybe_unused]] void initializeImHex() { init::WindowSplash splashWindow; @@ -105,6 +111,103 @@ namespace { } } + + #if defined(OS_WEB) + using namespace hex::init; + + void saveFsData() { + EM_ASM({ + FS.syncfs(function (err) { + if (!err) + return; + alert("Failed to save permanent file system: "+err); + }); + }); + } + + int runImHex() { + auto splashWindow = new WindowSplash(); + + log::info("Using '{}' GPU", ImHexApi::System::getGPUVendor()); + + // Add initialization tasks to run + TaskManager::init(); + for (const auto &[name, task, async] : init::getInitTasks()) + splashWindow->addStartupTask(name, task, async); + + splashWindow->startStartupTasks(); + + // Draw the splash window while tasks are running + emscripten_set_main_loop_arg([](void *arg) { + auto splashWindow = reinterpret_cast(arg); + + FrameResult res = splashWindow->fullFrame(); + if (res == FrameResult::success) { + handleFileOpenRequest(); + + // Clean up everything after the main window is closed + emscripten_set_beforeunload_callback(nullptr, [](int eventType, const void *reserved, void *userData) { + hex::unused(eventType, reserved, userData); + + try { + saveFsData(); + deinitializeImHex(); + return ""; + } catch (const std::exception &ex) { + std::string *msg = new std::string("Failed to deinitialize ImHex. This is just a message warning you of this, the application has already closed, you probably can't do anything about it. Message: "); + msg->append(std::string(ex.what())); + log::fatal("{}", *msg); + return msg->c_str(); + } + }); + + // Delete splash window (do it before creating the main window so glfw destroys the window) + delete splashWindow; + + emscripten_cancel_main_loop(); + + // Main window + static Window window; + emscripten_set_main_loop([]() { + window.fullFrame(); + }, 60, 0); + } + }, splashWindow, 60, 0); + + return -1; + } + + #else + + int runImHex() { + + bool shouldRestart = false; + do { + // Register an event handler that will make ImHex restart when requested + shouldRestart = false; + EventManager::subscribe([&] { + shouldRestart = true; + }); + + initializeImHex(); + handleFileOpenRequest(); + + // Clean up everything after the main window is closed + ON_SCOPE_EXIT { + deinitializeImHex(); + }; + + // Main window + Window window; + window.loop(); + + } while (shouldRestart); + + return EXIT_SUCCESS; + } + + #endif + } /** @@ -127,27 +230,5 @@ int main(int argc, char **argv) { ImHexApi::System::impl::setPortableVersion(isPortableVersion()); - bool shouldRestart = false; - do { - // Register an event handler that will make ImHex restart when requested - shouldRestart = false; - EventManager::subscribe([&] { - shouldRestart = true; - }); - - initializeImHex(); - handleFileOpenRequest(); - - // Clean up everything after the main window is closed - ON_SCOPE_EXIT { - deinitializeImHex(); - }; - - // Main window - Window window; - window.loop(); - - } while (shouldRestart); - - return EXIT_SUCCESS; -} + return runImHex(); +}; diff --git a/main/gui/source/messaging/web.cpp b/main/gui/source/messaging/web.cpp new file mode 100644 index 000000000..5c6b9a4f5 --- /dev/null +++ b/main/gui/source/messaging/web.cpp @@ -0,0 +1,24 @@ +#if defined(OS_WEB) + +#include + +#include +#include + +#include "messaging.hpp" + +namespace hex::messaging { + + void sendToOtherInstance(const std::string &eventName, const std::vector &args) { + hex::unused(eventName); + hex::unused(args); + log::error("Unimplemented function 'sendToOtherInstance()' called"); + } + + // Not implemented, so lets say we are the main instance every time so events are forwarded to ourselves + bool setupNative() { + return true; + } +} + +#endif diff --git a/main/gui/source/messaging/win.cpp b/main/gui/source/messaging/win.cpp index aea59cb31..d77310882 100644 --- a/main/gui/source/messaging/win.cpp +++ b/main/gui/source/messaging/win.cpp @@ -22,13 +22,13 @@ namespace hex::messaging { // Check if the window is visible and if it's an ImHex window if (::IsWindowVisible(hWnd) == TRUE && length != 0) { if (windowName.starts_with("ImHex")) { - // it's our window, return it and stop iteration + // It's our window, return it and stop iteration *reinterpret_cast(ret) = hWnd; return FALSE; } } - // continue iteration + // Continue iteration return TRUE; }, reinterpret_cast(&imhexWindow)); @@ -69,7 +69,7 @@ namespace hex::messaging { constexpr static auto UniqueMutexId = "ImHex/a477ea68-e334-4d07-a439-4f159c683763"; - // check if an ImHex instance is already running by opening a global mutex + // Check if an ImHex instance is already running by opening a global mutex HANDLE globalMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, UniqueMutexId); if (globalMutex == nullptr) { // If no ImHex instance is running, create a new global mutex diff --git a/main/gui/source/window/linux_window.cpp b/main/gui/source/window/linux_window.cpp index 75b6ee868..457faa250 100644 --- a/main/gui/source/window/linux_window.cpp +++ b/main/gui/source/window/linux_window.cpp @@ -44,7 +44,7 @@ namespace hex { executeCmd({"zenity", "--error", "--text", message}); } else if(isFileInPath("notify-send")) { executeCmd({"notify-send", "-i", "script-error", "Error", message}); - } // hopefully one of these commands is installed + } // Hopefully one of these commands is installed } void Window::initNative() { diff --git a/main/gui/source/window/web_window.cpp b/main/gui/source/window/web_window.cpp new file mode 100644 index 000000000..0f05d3167 --- /dev/null +++ b/main/gui/source/window/web_window.cpp @@ -0,0 +1,71 @@ +#include "window.hpp" + +#if defined(OS_WEB) + +#include +#include + +// Function used by c++ to get the size of the html canvas +EM_JS(int, canvas_get_width, (), { + return Module.canvas.width; +}); + +// Function used by c++ to get the size of the html canvas +EM_JS(int, canvas_get_height, (), { + return Module.canvas.height; +}); + +// Function called by javascript +EM_JS(void, resizeCanvas, (), { + js_resizeCanvas(); +}); + +namespace hex { + + void nativeErrorMessage(const std::string &message) { + log::fatal(message); + EM_ASM({ + alert(UTF8ToString($0)); + }, message.c_str()); + } + + void Window::initNative() { + EM_ASM({ + // Save data directory + FS.mkdir("/home/web_user/.local"); + FS.mount(IDBFS, {}, '/home/web_user/.local'); + + FS.syncfs(true, function (err) { + if (!err) + return; + alert("Failed to load permanent file system: "+err); + }); + }); + } + + void Window::setupNativeWindow() { + resizeCanvas(); + } + + void Window::beginNativeWindowFrame() { + static i32 prevWidth = 0; + static i32 prevHeight = 0; + + auto width = canvas_get_width(); + auto height = canvas_get_height(); + + if (prevWidth != width || prevHeight != height) { + // Size has changed + + prevWidth = width; + prevHeight = height; + this->resize(width, height); + } + } + + void Window::endNativeWindowFrame() { + } + +} + +#endif \ No newline at end of file diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index 193c163f7..29a0c004c 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -156,6 +156,8 @@ namespace hex { } void Window::fullFrame() { + this->m_lastFrameTime = glfwGetTime(); + glfwPollEvents(); // Render frame @@ -255,7 +257,7 @@ namespace hex { ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_ScrollbarGrabActive)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_ScrollbarGrabHovered)); - // custom titlebar buttons implementation for borderless window mode + // Custom titlebar buttons implementation for borderless window mode auto &titleBarButtons = ContentRegistry::Interface::impl::getTitleBarButtons(); // Draw custom title bar buttons @@ -812,6 +814,8 @@ namespace hex { glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); + glfwWindowHint(GLFW_SAMPLES, 1); if (restoreWindowPos) { int maximized = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.maximized", GLFW_FALSE); @@ -924,30 +928,32 @@ namespace hex { win->processEvent(); }); - // Register key press callback - glfwSetKeyCallback(this->m_window, [](GLFWwindow *window, int key, int scancode, int action, int mods) { - hex::unused(mods); + #if !defined(OS_WEB) + // Register key press callback + glfwSetKeyCallback(this->m_window, [](GLFWwindow *window, int key, int scancode, int action, int mods) { + hex::unused(mods); - auto win = static_cast(glfwGetWindowUserPointer(window)); + auto win = static_cast(glfwGetWindowUserPointer(window)); - if (action == GLFW_RELEASE) { - win->m_buttonDown = false; - } else { - win->m_buttonDown = true; - } + if (action == GLFW_RELEASE) { + win->m_buttonDown = false; + } else { + win->m_buttonDown = true; + } - if (key == GLFW_KEY_UNKNOWN) return; + if (key == GLFW_KEY_UNKNOWN) return; - auto keyName = glfwGetKeyName(key, scancode); - if (keyName != nullptr) - key = std::toupper(keyName[0]); + auto keyName = glfwGetKeyName(key, scancode); + if (keyName != nullptr) + key = std::toupper(keyName[0]); - if (action == GLFW_PRESS || action == GLFW_REPEAT) { - win->m_pressedKeys.push_back(key); - } + if (action == GLFW_PRESS || action == GLFW_REPEAT) { + win->m_pressedKeys.push_back(key); + } - win->processEvent(); - }); + win->processEvent(); + }); + #endif // Register cursor position callback glfwSetCursorPosCallback(this->m_window, [](GLFWwindow *window, double x, double y) { @@ -995,6 +1001,10 @@ namespace hex { glfwShowWindow(this->m_window); } + void Window::resize(i32 width, i32 height) { + glfwSetWindowSize(this->m_window, width, height); + } + void Window::initImGui() { IMGUI_CHECKVERSION(); @@ -1094,10 +1104,13 @@ namespace hex { } } + ImGui_ImplGlfw_InitForOpenGL(this->m_window, true); #if defined(OS_MACOS) ImGui_ImplOpenGL3_Init("#version 150"); + #elif defined(OS_WEB) + ImGui_ImplOpenGL3_Init(); #else ImGui_ImplOpenGL3_Init("#version 130"); #endif diff --git a/plugins/builtin/include/content/helpers/diagrams.hpp b/plugins/builtin/include/content/helpers/diagrams.hpp index c6800feb3..43b6ca5f5 100644 --- a/plugins/builtin/include/content/helpers/diagrams.hpp +++ b/plugins/builtin/include/content/helpers/diagrams.hpp @@ -183,7 +183,7 @@ namespace hex { private: size_t m_sampleSize; - // The number of byte processed and the size of + // The number of bytes processed and the size of // the file to analyze (useful for iterative analysis) u64 m_byteCount; u64 m_fileSize; @@ -276,7 +276,7 @@ namespace hex { private: size_t m_sampleSize; - // The number of byte processed and the size of + // The number of bytes processed and the size of // the file to analyze (useful for iterative analysis) u64 m_byteCount; u64 m_fileSize; @@ -313,7 +313,7 @@ namespace hex { ImPlot::PlotLine("##ChunkBasedAnalysisLine", this->m_xBlockEntropy.data(), this->m_yBlockEntropySampled.data(), this->m_xBlockEntropy.size()); // The parameter updateHandle is used when using the pattern language since we don't have a provider - // but just a set of bytes we won't be able to use the drag bar correctly. + // but just a set of bytes, we won't be able to use the drag bar correctly. if (updateHandle) { // Set a draggable line on the plot if (ImPlot::DragLineX(1, &this->m_handlePosition, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { @@ -427,7 +427,7 @@ namespace hex { } // Method used to compute the entropy of a block of size `blockSize` - // using the bytes occurrences from `valueCounts` array. + // using the byte occurrences from `valueCounts` array. double calculateEntropy(std::array &valueCounts, size_t blockSize) { double entropy = 0; @@ -560,8 +560,8 @@ namespace hex { // Position of the handle inside the plot double m_handlePosition = 0.0; - // Hold the number of block that have been processed - // during the chunk based entropy analysis + // Hold the number of blocks that have been processed + // during the chunk-based entropy analysis u64 m_blockCount; // Hold the number of bytes that have been processed @@ -572,7 +572,7 @@ namespace hex { // (useful for the iterative analysis) std::array m_blockValueCounts; - // Variable to hold the result of the chunk based + // Variable to hold the result of the chunk-based // entropy analysis std::vector m_xBlockEntropy; std::vector m_yBlockEntropy, m_yBlockEntropySampled; @@ -708,7 +708,7 @@ namespace hex { } // The parameter updateHandle is used when using the pattern language since we don't have a provider - // but just a set of bytes we won't be able to use the drag bar correctly. + // but just a set of bytes, we won't be able to use the drag bar correctly. if (updateHandle) { // Set a draggable line on the plot if (ImPlot::DragLineX(1, &this->m_handlePosition, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { @@ -934,8 +934,8 @@ namespace hex { // Position of the handle inside the plot double m_handlePosition = 0.0; - // Hold the number of block that have been processed - // during the chunk based entropy analysis + // Hold the number of blocks that have been processed + // during the chunk-based entropy analysis u64 m_blockCount; // Hold the number of bytes that have been processed @@ -950,7 +950,7 @@ namespace hex { // (useful for the iterative analysis) std::array m_blockValueCounts; - // The m_xBlockTypeDistributions attributes is used to specify the position of + // The m_xBlockTypeDistributions attributes are used to specify the position of // the values in the plot when the Y axis doesn't start at 0 std::vector m_xBlockTypeDistributions; // Hold the result of the byte distribution analysis diff --git a/plugins/builtin/include/content/popups/popup_file_chooser.hpp b/plugins/builtin/include/content/popups/popup_file_chooser.hpp index 67b2121f5..e6254f2b5 100644 --- a/plugins/builtin/include/content/popups/popup_file_chooser.hpp +++ b/plugins/builtin/include/content/popups/popup_file_chooser.hpp @@ -11,7 +11,7 @@ namespace hex::plugin::builtin { class PopupFileChooser : public Popup { public: - PopupFileChooser(const std::vector &files, const std::vector &validExtensions, bool multiple, const std::function &callback) + PopupFileChooser(const std::vector &files, const std::vector &validExtensions, bool multiple, const std::function &callback) : hex::Popup("hex.builtin.common.choose_file"), m_indices({ }), m_files(files), m_openCallback(callback), @@ -80,7 +80,7 @@ namespace hex::plugin::builtin { std::set m_indices; std::vector m_files; std::function m_openCallback; - std::vector m_validExtensions; + std::vector m_validExtensions; bool m_multiple = false; }; diff --git a/plugins/builtin/include/content/providers/disk_provider.hpp b/plugins/builtin/include/content/providers/disk_provider.hpp index cd0c2acb5..dc0816953 100644 --- a/plugins/builtin/include/content/providers/disk_provider.hpp +++ b/plugins/builtin/include/content/providers/disk_provider.hpp @@ -1,4 +1,5 @@ #pragma once +#if !defined(OS_WEB) #include @@ -72,4 +73,5 @@ namespace hex::plugin::builtin { bool m_writable = false; }; -} \ No newline at end of file +} +#endif diff --git a/plugins/builtin/include/content/views/view_pattern_data.hpp b/plugins/builtin/include/content/views/view_pattern_data.hpp index c6c0a3947..d6d178550 100644 --- a/plugins/builtin/include/content/views/view_pattern_data.hpp +++ b/plugins/builtin/include/content/views/view_pattern_data.hpp @@ -19,7 +19,7 @@ namespace hex::plugin::builtin { void drawContent() override; private: - ui::PatternDrawer m_patternDrawer; + std::unique_ptr m_patternDrawer; }; } \ No newline at end of file diff --git a/plugins/builtin/include/content/views/view_pattern_editor.hpp b/plugins/builtin/include/content/views/view_pattern_editor.hpp index 24a21550b..6d584844f 100644 --- a/plugins/builtin/include/content/views/view_pattern_editor.hpp +++ b/plugins/builtin/include/content/views/view_pattern_editor.hpp @@ -156,7 +156,7 @@ namespace hex::plugin::builtin { bool m_syncPatternSourceCode = false; bool m_autoLoadPatterns = true; - std::map> m_sectionWindowDrawer; + std::map> m_sectionWindowDrawer; ui::HexEditor m_sectionHexEditor; @@ -175,7 +175,7 @@ namespace hex::plugin::builtin { PerProvider m_shouldAnalyze; PerProvider m_breakpointHit; - PerProvider m_debuggerDrawer; + PerProvider> m_debuggerDrawer; std::atomic m_resetDebuggerVariables; int m_debuggerScopeIndex = 0; diff --git a/plugins/builtin/include/ui/pattern_drawer.hpp b/plugins/builtin/include/ui/pattern_drawer.hpp index 7935f0937..f6ede7290 100644 --- a/plugins/builtin/include/ui/pattern_drawer.hpp +++ b/plugins/builtin/include/ui/pattern_drawer.hpp @@ -20,6 +20,8 @@ namespace hex::plugin::builtin::ui { this->m_formatters = pl::gen::fmt::createFormatters(); } + virtual ~PatternDrawer() = default; + void draw(const std::vector> &patterns, pl::PatternLanguage *runtime = nullptr, float height = 0.0F); enum class TreeStyle { diff --git a/plugins/builtin/source/content/data_inspector.cpp b/plugins/builtin/source/content/data_inspector.cpp index 863221aa4..a3e051408 100644 --- a/plugins/builtin/source/content/data_inspector.cpp +++ b/plugins/builtin/source/content/data_inspector.cpp @@ -275,9 +275,10 @@ namespace hex::plugin::builtin { auto format = (style == Style::Decimal) ? "{0}{1:d}" : ((style == Style::Hexadecimal) ? "{0}0x{1:X}" : "{0}0o{1:o}"); + auto number = hex::crypt::decodeSleb128(buffer); bool negative = number < 0; - auto value = hex::format(format, negative ? "-" : "", std::abs(number)); + auto value = hex::format(format, negative ? "-" : "", negative ? -number : number); return [value] { ImGui::TextUnformatted(value.c_str()); return value; }; }, diff --git a/plugins/builtin/source/content/helpers/math_evaluator.cpp b/plugins/builtin/source/content/helpers/math_evaluator.cpp index ec721ccb0..338bc6693 100644 --- a/plugins/builtin/source/content/helpers/math_evaluator.cpp +++ b/plugins/builtin/source/content/helpers/math_evaluator.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace hex { diff --git a/plugins/builtin/source/content/pl_visualizers.cpp b/plugins/builtin/source/content/pl_visualizers.cpp index 6bc3f855a..871fe2716 100644 --- a/plugins/builtin/source/content/pl_visualizers.cpp +++ b/plugins/builtin/source/content/pl_visualizers.cpp @@ -9,7 +9,16 @@ #include #include -#include + +#if defined(OS_WEB) + #define GLFW_INCLUDE_ES3 + #include +#else + #include +#endif + +#include + #include #include @@ -476,17 +485,17 @@ namespace hex::plugin::builtin { } void drawChunkBasedEntropyVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span arguments) { - // variable used to store the result to avoid having to recalculate the result at each frame + // Variable used to store the result to avoid having to recalculate the result at each frame static DiagramChunkBasedEntropyAnalysis analyzer; - // compute data + // Compute data if (shouldReset) { auto pattern = arguments[0].toPattern(); auto chunkSize = arguments[1].toUnsigned(); analyzer.process(pattern->getBytes(), chunkSize); } - // show results + // Show results analyzer.draw(ImVec2(400, 250), ImPlotFlags_NoChild | ImPlotFlags_CanvasOnly); } diff --git a/plugins/builtin/source/content/project.cpp b/plugins/builtin/source/content/project.cpp index a733f4d9d..7c27f3b6f 100644 --- a/plugins/builtin/source/content/project.cpp +++ b/plugins/builtin/source/content/project.cpp @@ -66,7 +66,7 @@ namespace hex::plugin::builtin { for (const auto &handler : ProjectFile::getHandlers()) { bool result = true; - // handlers are supposed to show the error/warning popup to the user themselves, so we don't show one here + // Handlers are supposed to show the error/warning popup to the user themselves, so we don't show one here try { if (!handler.load(handler.basePath, tar)) { log::warn("Project file handler for {} failed to load {}", filePath.string(), handler.basePath.string()); @@ -157,14 +157,15 @@ namespace hex::plugin::builtin { ImHexApi::Provider::resetDirty(); - // if saveLocation is false, reset the project path (do not release the lock) + // If saveLocation is false, reset the project path (do not release the lock) if (updateLocation) { resetPath.release(); } AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.save_project.name"); - EventManager::post(); // request, as this puts us into a project state + // Request, as this puts us into a project state + EventManager::post(); return result; } diff --git a/plugins/builtin/source/content/providers.cpp b/plugins/builtin/source/content/providers.cpp index f41b190cf..69b27fce1 100644 --- a/plugins/builtin/source/content/providers.cpp +++ b/plugins/builtin/source/content/providers.cpp @@ -25,7 +25,9 @@ namespace hex::plugin::builtin { ContentRegistry::Provider::add(false); ContentRegistry::Provider::add(false); + #if !defined(OS_WEB) ContentRegistry::Provider::add(); + #endif ContentRegistry::Provider::add(); ContentRegistry::Provider::add(); ContentRegistry::Provider::add(); @@ -59,7 +61,7 @@ namespace hex::plugin::builtin { }; if (provider == nullptr) { - // if a provider is not created, it will be overwritten when saving the project, + // If a provider is not created, it will be overwritten when saving the project, // so we should prevent the project from loading at all showError(hex::format("hex.builtin.popup.error.project.load"_lang, hex::format("hex.builtin.popup.error.project.load.create_provider"_lang, providerType) @@ -91,7 +93,7 @@ namespace hex::plugin::builtin { hex::format("\n - {} : {}", warning.first->getName(), warning.second)); } - // if no providers were opened, display an error with + // If no providers were opened, display an error with // the warnings that happened when opening them if (ImHexApi::Provider::getProviders().size() == 0) { showError(hex::format("hex.builtin.popup.error.project.load"_lang, @@ -100,7 +102,7 @@ namespace hex::plugin::builtin { return false; } else { - // else, if are warnings, still display them + // Else, if are warnings, still display them if (warningMsg.empty()) return true; else { showWarning( diff --git a/plugins/builtin/source/content/providers/disk_provider.cpp b/plugins/builtin/source/content/providers/disk_provider.cpp index 3168f51cb..f021442bf 100644 --- a/plugins/builtin/source/content/providers/disk_provider.cpp +++ b/plugins/builtin/source/content/providers/disk_provider.cpp @@ -1,3 +1,4 @@ +#if !defined(OS_WEB) #include #include "content/providers/disk_provider.hpp" @@ -105,7 +106,7 @@ namespace hex::plugin::builtin { return -1; if (st.st_size == 0) { - // try BLKGETSIZE + // Try BLKGETSIZE unsigned long long bytes64; if (ioctl(fd, BLKGETSIZE, &bytes64) >= 0) { *bytes = bytes64; @@ -453,4 +454,5 @@ namespace hex::plugin::builtin { return Provider::queryInformation(category, argument); } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/plugins/builtin/source/content/recent.cpp b/plugins/builtin/source/content/recent.cpp index 04e04d310..597ac94e0 100644 --- a/plugins/builtin/source/content/recent.cpp +++ b/plugins/builtin/source/content/recent.cpp @@ -30,10 +30,10 @@ namespace hex::plugin::builtin::recent { if (ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.save_recent_providers", 1) == 1) { auto fileName = hex::format("{:%y%m%d_%H%M%S}.json", fmt::gmtime(std::chrono::system_clock::now())); - // do not save to recents if the provider is part of a project + // Do not save to recents if the provider is part of a project if (ProjectFile::hasPath()) return; - // do not save to recents if the provider doesnt want it + // Do not save to recents if the provider doesnt want it if (!provider->isSavableAsRecent()) return; // The recent provider is saved to every "recent" directory @@ -57,7 +57,7 @@ namespace hex::plugin::builtin::recent { updateRecentEntries(); }); - // save opened projects as a "recent" shortcut + // Save opened projects as a "recent" shortcut (void)EventManager::subscribe([] { if (ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.save_recent_providers", 1) == 1) { auto fileName = hex::format("{:%y%m%d_%H%M%S}.json", fmt::gmtime(std::chrono::system_clock::now())); @@ -208,7 +208,7 @@ namespace hex::plugin::builtin::recent { ImGui::EndPopup(); } - // handle deletion from vector and on disk + // Handle deletion from vector and on disk if (shouldRemove) { wolv::io::fs::remove(recentEntry.entryFilePath); it = s_recentEntries.erase(it); diff --git a/plugins/builtin/source/content/settings_entries.cpp b/plugins/builtin/source/content/settings_entries.cpp index ea5e6b9aa..0a914cbc8 100644 --- a/plugins/builtin/source/content/settings_entries.cpp +++ b/plugins/builtin/source/content/settings_entries.cpp @@ -211,7 +211,8 @@ namespace hex::plugin::builtin { static auto lang = std::string(setting); if (ImGui::InputText(name.data(), lang, ImGuiInputTextFlags_CharsNoBlank)) { - setting = std::string(lang.c_str()); // remove following zero bytes + // Remove trailing null bytes + setting = std::string(lang.c_str()); return true; } diff --git a/plugins/builtin/source/content/tools_entries.cpp b/plugins/builtin/source/content/tools_entries.cpp index 179c84a2f..5667e205f 100644 --- a/plugins/builtin/source/content/tools_entries.cpp +++ b/plugins/builtin/source/content/tools_entries.cpp @@ -1133,17 +1133,17 @@ namespace hex::plugin::builtin { } } - // Tool for converting between different number formats - // There are three places where input can be changed; the bit checkboxes, the hex input and the decimal input. + // Tool for converting between different number formats. + // There are three places where input can be changed; the bit checkboxes, the hex input, and the decimal input. // The bit checkboxes and the hex input are directly related and can be converted between each other easily. // The decimal input is a bit more complicated. IEEE 754 floating point numbers are represented as a sign bit, // an exponent and a mantissa. For details see https://en.wikipedia.org/wiki/IEEE_754. // Workflow is as follows: // From the bit checkboxes determine the integer hex value. This is straightforward. - // From the hex value determine the binary floating point value by extracting the sign, exponent and mantissa. - // From the binary floating point value determine the decimal floating point value using third party library. + // From the hex value determine the binary floating point value by extracting the sign, exponent, and mantissa. + // From the binary floating point value determine the decimal floating point value using a third party library. // From the decimal floating point we reconstruct the binary floating point value using internal hardware. - // If format is non-standard the reconstruction is done using properties of the format. + // If the format is non-standard, the reconstruction is done using properties of the format. void drawIEEE754Decoder() { constexpr static auto flags = ImGuiInputTextFlags_EnterReturnsTrue; @@ -1235,16 +1235,16 @@ namespace hex::plugin::builtin { const static auto BitsToFloat = [](IEEE754 &ieee754) { // Zero or denormal if (ieee754.exponentBits == 0) { - // result doesn't fit in 128 bits + // Result doesn't fit in 128 bits if ((ieee754.exponentBias - 1) > 128) ieee754.exponentValue = std::pow(2.0L, static_cast(-ieee754.exponentBias + 1)); else { if (ieee754.exponentBias == 0) { - // exponent is zero + // Exponent is zero if (ieee754.mantissaBits == 0) ieee754.exponentValue = 1.0; else - // exponent is one + // Exponent is one ieee754.exponentValue = 2.0; } else @@ -1253,18 +1253,18 @@ namespace hex::plugin::builtin { } // Normal else { - // result doesn't fit in 128 bits + // Result doesn't fit in 128 bits if (std::abs(ieee754.exponentBits - ieee754.exponentBias) > 128) ieee754.exponentValue = std::pow(2.0L, static_cast(ieee754.exponentBits - ieee754.exponentBias)); - //result fits in 128 bits + // Result fits in 128 bits else { - // exponent is positive + // Exponent is positive if (ieee754.exponentBits > ieee754.exponentBias) ieee754.exponentValue = static_cast(u128(1) << (ieee754.exponentBits - ieee754.exponentBias)); - // exponent is negative + // Exponent is negative else if (ieee754.exponentBits < ieee754.exponentBias) ieee754.exponentValue = 1.0 / static_cast(u128(1) << (ieee754.exponentBias - ieee754.exponentBits)); - // exponent is zero + // Exponent is zero else ieee754.exponentValue = 1.0; } } @@ -1275,7 +1275,7 @@ namespace hex::plugin::builtin { // Check if all exponent bits are set. if (std::popcount(static_cast(ieee754.exponentBits)) == static_cast(ieee754statics.exponentBitCount)) { - // if fraction is zero number is infinity. + // If fraction is zero number is infinity. if (ieee754.mantissaBits == 0) { if (ieee754.signBits == 0) { @@ -1290,7 +1290,7 @@ namespace hex::plugin::builtin { } ieee754.numberType = NumberType::Infinity; - // otherwise number is NaN. + // Otherwise number is NaN. } else { if (ieee754.mantissaBits & (u128(1) << (ieee754statics.mantissaBitCount - 1))) { @@ -1305,9 +1305,9 @@ namespace hex::plugin::builtin { } ieee754.numberType = NumberType::NaN; } - // if all exponent bits are zero, but we have a non-zero fraction - // then the number is denormal which are smaller than regular numbers - // but not as precise. + // If all exponent bits are zero, but we have a non-zero fraction, + // then the number is denormal. + // These are smaller than regular numbers but not as precise. } else if (ieee754.exponentBits == 0 && ieee754.mantissaBits != 0) { ieee754.numberType = NumberType::Denormal; @@ -1336,7 +1336,7 @@ namespace hex::plugin::builtin { // Sign ImGui::TableNextColumn(); - // this has the effect of dimming the color of the numbers so user doesn't try + // This has the effect of dimming the color of the numbers so user doesn't try // to interact with them. ImVec4 textColor = ImGui::GetStyleColorVec4(ImGuiCol_Text); ImGui::BeginDisabled(); @@ -1350,7 +1350,7 @@ namespace hex::plugin::builtin { ImGui::Text("+1"); ImGui::Unindent(20_scaled); - //times + // Times ImGui::TableNextColumn(); ImGui::Text("x"); ImGui::TableNextColumn(); @@ -1376,7 +1376,7 @@ namespace hex::plugin::builtin { ImGui::Unindent(20_scaled); - //times + // Times ImGui::TableNextColumn(); ImGui::Text("x"); ImGui::TableNextColumn(); @@ -1510,22 +1510,21 @@ namespace hex::plugin::builtin { ImGui::Unindent(indent); }; - const static auto FloatToBits = [&specialNumbers](IEEE754 &ieee754, std::string decimalFloatingPointNumberString - , std::string_view decimalStrView, std::from_chars_result &res,int totalBitCount) { + const static auto FloatToBits = [&specialNumbers](IEEE754 &ieee754, std::string decimalFloatingPointNumberString, int totalBitCount) { // Always obtain sign first. if (decimalFloatingPointNumberString[0] == '-') { - // and remove it from the string. + // And remove it from the string. ieee754.signBits = 1; decimalFloatingPointNumberString.erase(0, 1); } else - //important to switch from - to +. + // Important to switch from - to +. ieee754.signBits = 0; InputType inputType; bool matchFound = false; i32 i; - // detect and use special numbers. + // Detect and use special numbers. for (i = 0; i < 12; i++) { if (decimalFloatingPointNumberString == specialNumbers[i]) { inputType = InputType(i/3); @@ -1538,10 +1537,9 @@ namespace hex::plugin::builtin { inputType = InputType::regular; if (inputType == InputType::regular) { - decimalStrView = decimalFloatingPointNumberString; - res = std::from_chars(decimalStrView.data(), decimalStrView.data() + decimalStrView.size(), ieee754statics.resultFloat); - // this is why we use from_chars - if (res.ec != std::errc()) { + try { + ieee754statics.resultFloat = stod(decimalFloatingPointNumberString); + } catch(const std::invalid_argument& _) { inputType = InputType::invalid; } } else if (inputType == InputType::infinity) { @@ -1560,7 +1558,7 @@ namespace hex::plugin::builtin { long double log2Result; if (inputType != InputType::invalid) { - // deal with zero first so we can use log2. + // Deal with zero first so we can use log2. if (ieee754statics.resultFloat == 0.0) { if (ieee754.signBits == 1) ieee754statics.resultFloat = -0.0; @@ -1638,13 +1636,13 @@ namespace hex::plugin::builtin { }; const static auto ToolMenu = [](i64 &inputFieldWidth) { - // we are done. The rest selects the format if user interacts with the widgets. - // If precision and exponent match one of the IEEE 754 formats the format is highlighted - // and remains highlighted until user changes to a different format. Matching formats occur when + // We are done. The rest selects the format if user interacts with the widgets. + // If precision and exponent match one of the IEEE 754 formats, the format is highlighted + // and remains highlighted until the user changes to a different format. Matching formats occur when // the user clicks on one of the selections or if the slider values match the format in question. - // when a new format is selected it may have a smaller number of digits than + // When a new format is selected, it may have a smaller number of digits than // the previous selection. Since the largest of the hexadecimal and the decimal - // representation widths sets both field widths to the same value we need to + // representation widths set both field widths to the same value, we need to // reset it here when a new choice is set. auto exponentBitCount = ieee754statics.exponentBitCount; @@ -1705,7 +1703,7 @@ namespace hex::plugin::builtin { needsPop = false; if (ImGui::Button("hex.builtin.tools.ieee754.clear"_lang)) - //this will reset all interactive widgets to zero. + // This will reset all interactive widgets to zero. ieee754statics.value = 0; ImGui::Separator(); @@ -1789,11 +1787,11 @@ namespace hex::plugin::builtin { ieee754.precision = std::ceil(1+(ieee754statics.mantissaBitCount + 1) * std::log10(2.0L)); // For C++ from_chars is better than strtold. - // the main problem is that from_chars will not process special numbers + // The main problem is that from_chars will not process special numbers // like inf and nan, so we handle them manually static std::string decimalFloatingPointNumberString; - static std::string_view decimalStrView; - // use qnan for quiet NaN and snan for signaling NaN + + // Use qnan for quiet NaN and snan for signaling NaN if (ieee754.numberType == NumberType::NaN) { if (ieee754.valueType == ValueType::QuietNaN) decimalFloatingPointNumberString = "qnan"; @@ -1809,9 +1807,8 @@ namespace hex::plugin::builtin { // We allow any input in order to accept infinities and NaNs, all invalid entries // are detected by from_chars. You can also enter -0 or -inf. - std::from_chars_result res; if (ImGui::InputText("##resultFloat", decimalFloatingPointNumberString, flags)) { - FloatToBits(ieee754, decimalFloatingPointNumberString, decimalStrView, res, totalBitCount); + FloatToBits(ieee754, decimalFloatingPointNumberString, totalBitCount); } ImGui::PopItemWidth(); diff --git a/plugins/builtin/source/content/views/view_hex_editor.cpp b/plugins/builtin/source/content/views/view_hex_editor.cpp index 9194a91bd..45b5a3164 100644 --- a/plugins/builtin/source/content/views/view_hex_editor.cpp +++ b/plugins/builtin/source/content/views/view_hex_editor.cpp @@ -1066,7 +1066,7 @@ namespace hex::plugin::builtin { } } - PopupFileChooser::open(paths, std::vector{ {"Thingy Table File", "tbl"} }, false, + PopupFileChooser::open(paths, std::vector{ {"Thingy Table File", "tbl"} }, false, [this](const auto &path) { TaskManager::createTask("Loading encoding file", 0, [this, path](auto&) { auto encoding = EncodingFile(EncodingFile::Type::Thingy, path); diff --git a/plugins/builtin/source/content/views/view_information.cpp b/plugins/builtin/source/content/views/view_information.cpp index 4f579ce5d..b9029140d 100644 --- a/plugins/builtin/source/content/views/view_information.cpp +++ b/plugins/builtin/source/content/views/view_information.cpp @@ -112,8 +112,8 @@ namespace hex::plugin::builtin { u64 count = 0; - // Loop over each byte of the [part of the] file and update each analysis - // one byte at the time in order to process the file only once + // Loop over each byte of the selection and update each analysis + // one byte at a time in order to process the file only once for (u8 byte : reader) { this->m_byteDistribution.update(byte); this->m_byteTypesDistribution.update(byte); diff --git a/plugins/builtin/source/content/views/view_pattern_data.cpp b/plugins/builtin/source/content/views/view_pattern_data.cpp index e0d981e01..9e4a7b3c2 100644 --- a/plugins/builtin/source/content/views/view_pattern_data.cpp +++ b/plugins/builtin/source/content/views/view_pattern_data.cpp @@ -9,27 +9,29 @@ namespace hex::plugin::builtin { ViewPatternData::ViewPatternData() : View("hex.builtin.view.pattern_data.name") { + this->m_patternDrawer = std::make_unique(); + // Handle tree style setting changes EventManager::subscribe(this, [this]() { auto patternStyle = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.pattern_tree_style", 0); - this->m_patternDrawer.setTreeStyle(static_cast(patternStyle)); + this->m_patternDrawer->setTreeStyle(static_cast(patternStyle)); }); // Reset the pattern drawer when the provider changes EventManager::subscribe(this, [this](auto, auto) { - this->m_patternDrawer.reset(); + this->m_patternDrawer->reset(); }); EventManager::subscribe(this, [this]{ - this->m_patternDrawer.reset(); + this->m_patternDrawer->reset(); }); EventManager::subscribe(this, [this](auto){ - this->m_patternDrawer.reset(); + this->m_patternDrawer->reset(); }); // Handle jumping to a pattern's location when it is clicked - this->m_patternDrawer.setSelectionCallback([](Region region){ ImHexApi::HexEditor::setSelection(region); }); + this->m_patternDrawer->setSelectionCallback([](Region region){ ImHexApi::HexEditor::setSelection(region); }); } ViewPatternData::~ViewPatternData() { @@ -46,11 +48,11 @@ namespace hex::plugin::builtin { // Make sure the runtime has finished evaluating and produced valid patterns auto &runtime = ContentRegistry::PatternLanguage::getRuntime(); if (!runtime.arePatternsValid()) { - this->m_patternDrawer.draw({ }); + this->m_patternDrawer->draw({ }); } else { // If the runtime has finished evaluating, draw the patterns if (TRY_LOCK(ContentRegistry::PatternLanguage::getRuntimeLock())) { - this->m_patternDrawer.draw(runtime.getPatterns(), &runtime); + this->m_patternDrawer->draw(runtime.getPatterns(), &runtime); } } } diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 155d3574c..4357d98a1 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -521,7 +521,7 @@ namespace hex::plugin::builtin { ImGui::TextFormatted("{} | 0x{:02X}", hex::toByteString(section.data.size()), section.data.size()); ImGui::TableNextColumn(); if (ImGui::IconButton(ICON_VS_OPEN_PREVIEW, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { - auto dataProvider = std::make_unique(); + auto dataProvider = std::make_shared(); dataProvider->resize(section.data.size()); dataProvider->writeRaw(0x00, section.data.data(), section.data.size()); dataProvider->setReadOnly(true); @@ -551,10 +551,10 @@ namespace hex::plugin::builtin { auto patternProvider = ImHexApi::Provider::get(); - this->m_sectionWindowDrawer[patternProvider] = [this, id, patternProvider, dataProvider = std::move(dataProvider), hexEditor, patternDrawer = ui::PatternDrawer(), &runtime] mutable { + this->m_sectionWindowDrawer[patternProvider] = [this, id, patternProvider, dataProvider, hexEditor, patternDrawer = std::make_shared(), &runtime] mutable { hexEditor.setProvider(dataProvider.get()); hexEditor.draw(480_scaled); - patternDrawer.setSelectionCallback([&](const auto ®ion) { + patternDrawer->setSelectionCallback([&](const auto ®ion) { hexEditor.setSelection(region); }); @@ -568,7 +568,7 @@ namespace hex::plugin::builtin { }(); if (*this->m_executionDone) - patternDrawer.draw(patterns, &runtime, 150_scaled); + patternDrawer->draw(patterns, &runtime, 150_scaled); }; } @@ -635,7 +635,7 @@ namespace hex::plugin::builtin { if (this->m_resetDebuggerVariables) { auto pauseLine = evaluator->getPauseLine(); - this->m_debuggerDrawer->reset(); + (*this->m_debuggerDrawer)->reset(); this->m_resetDebuggerVariables = false; this->m_textEditor.SetCursorPosition(TextEditor::Coordinates(pauseLine.value_or(0) - 1, 0)); @@ -644,7 +644,7 @@ namespace hex::plugin::builtin { } auto &currScope = evaluator->getScope(-this->m_debuggerScopeIndex); - this->m_debuggerDrawer->draw(*currScope.scope, &runtime, size.y - ImGui::GetTextLineHeightWithSpacing() * 4); + (*this->m_debuggerDrawer)->draw(*currScope.scope, &runtime, size.y - ImGui::GetTextLineHeightWithSpacing() * 4); } } ImGui::EndChild(); @@ -791,7 +791,11 @@ namespace hex::plugin::builtin { continue; try { - runtime.getInternals().preprocessor->preprocess(runtime, file.readString()); + auto &preprocessor = runtime.getInternals().preprocessor; + auto ret = preprocessor->preprocess(runtime, file.readString()); + if (!ret.has_value()) { + log::warn("Failed to preprocess file {} during MIME analysis: {}", entry.path().string(), (*preprocessor->getError()).what()); + } } catch (pl::core::err::PreprocessorError::Exception &e) { log::warn("Failed to preprocess file {} during MIME analysis: {}", entry.path().string(), e.what()); } @@ -1065,6 +1069,10 @@ namespace hex::plugin::builtin { } }); + EventManager::subscribe(this, [this](prv::Provider *provider) { + this->m_debuggerDrawer.get(provider) = std::make_unique(); + }); + EventManager::subscribe(this, [this](prv::Provider *) { if (this->m_syncPatternSourceCode && ImHexApi::Provider::getProviders().empty()) { this->m_textEditor.SetText(""); @@ -1124,7 +1132,7 @@ namespace hex::plugin::builtin { } } - PopupFileChooser::open(paths, std::vector{ { "Pattern File", "hexpat" } }, false, + PopupFileChooser::open(paths, std::vector{ { "Pattern File", "hexpat" } }, false, [this, provider](const std::fs::path &path) { this->loadPatternFile(path, provider); AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.load_existing.name"); diff --git a/plugins/builtin/source/content/views/view_store.cpp b/plugins/builtin/source/content/views/view_store.cpp index 08edbd0be..eab4a0385 100644 --- a/plugins/builtin/source/content/views/view_store.cpp +++ b/plugins/builtin/source/content/views/view_store.cpp @@ -79,7 +79,7 @@ namespace hex::plugin::builtin { ImGui::EndTooltip(); } ImGui::TableNextColumn(); - // the space makes a padding in the UI + // The space makes a padding in the UI ImGui::Text("%s ", wolv::util::combineStrings(entry.authors, ", ").c_str()); ImGui::TableNextColumn(); @@ -181,7 +181,7 @@ namespace hex::plugin::builtin { } void ViewStore::refresh() { - // do not refresh if a refresh is already in progress + // Do not refresh if a refresh is already in progress if (this->m_requestStatus == RequestStatus::InProgress) return; this->m_requestStatus = RequestStatus::InProgress; @@ -275,7 +275,7 @@ namespace hex::plugin::builtin { if (!fs::isPathWritable(folderPath)) continue; - // verify that we write the file to the right folder + // Verify that we write the file to the right folder // this is to prevent the filename from having elements like ../ auto fullPath = std::fs::weakly_canonical(folderPath / std::fs::path(fileName)); auto [folderIter, pathIter] = std::mismatch(folderPath.begin(), folderPath.end(), fullPath.begin()); diff --git a/plugins/builtin/source/content/views/view_yara.cpp b/plugins/builtin/source/content/views/view_yara.cpp index 70b5ffc3f..2ceb2f8e0 100644 --- a/plugins/builtin/source/content/views/view_yara.cpp +++ b/plugins/builtin/source/content/views/view_yara.cpp @@ -126,7 +126,7 @@ namespace hex::plugin::builtin { } } - PopupFileChooser::open(paths, std::vector{ { "Yara File", "yara" }, { "Yara File", "yar" } }, true, + PopupFileChooser::open(paths, std::vector{ { "Yara File", "yara" }, { "Yara File", "yar" } }, true, [&](const auto &path) { this->m_rules->push_back({ path.filename(), path }); }); diff --git a/plugins/builtin/source/content/welcome_screen.cpp b/plugins/builtin/source/content/welcome_screen.cpp index dd46eccd2..26c393f07 100644 --- a/plugins/builtin/source/content/welcome_screen.cpp +++ b/plugins/builtin/source/content/welcome_screen.cpp @@ -202,7 +202,7 @@ namespace hex::plugin::builtin { ImGui::EndPopup(); } - // draw recent entries + // Draw recent entries recent::draw(); if (ImHexApi::System::getInitArguments().contains("update-available")) { @@ -440,11 +440,15 @@ namespace hex::plugin::builtin { }); EventManager::subscribe([] { - // documentation of the value above the setting definition + // Documentation of the value above the setting definition auto allowServerContact = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.server_contact", 2); if (allowServerContact == 2) { ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.server_contact", 0); - PopupTelemetryRequest::open(); + + // Open the telemetry popup but only on desktop versions + #if !defined(OS_WEB) + PopupTelemetryRequest::open(); + #endif } }); @@ -486,10 +490,11 @@ namespace hex::plugin::builtin { bool hasBackupFile = wolv::io::fs::exists(backupFilePath); PopupRestoreBackup::open( - // path of log file + // Path of log file crashFileData.value("logFile", ""), - // restore callback - [=]{ + + // Restore callback + [=] { if (hasBackupFile) { ProjectFile::load(backupFilePath); if (hasProject) { @@ -504,8 +509,9 @@ namespace hex::plugin::builtin { } } }, - // delete callback (also executed after restore) - [crashFilePath, backupFilePath]{ + + // Delete callback (also executed after restore) + [crashFilePath, backupFilePath] { wolv::io::fs::remove(crashFilePath); wolv::io::fs::remove(backupFilePath); } diff --git a/plugins/script_loader/CMakeLists.txt b/plugins/script_loader/CMakeLists.txt index f63488e6c..84c7e7be0 100644 --- a/plugins/script_loader/CMakeLists.txt +++ b/plugins/script_loader/CMakeLists.txt @@ -1,47 +1,52 @@ cmake_minimum_required(VERSION 3.16) -find_package(CoreClrEmbed) +if (NOT EMSCRIPTEN) -add_imhex_plugin( - NAME - script_loader + include(ImHexPlugin) + find_package(CoreClrEmbed) - SOURCES - source/plugin_script_loader.cpp + add_imhex_plugin( + NAME + script_loader - INCLUDES - include -) + SOURCES + source/plugin_script_loader.cpp -if (CoreClrEmbed_FOUND) - add_library(nethost SHARED IMPORTED) - target_include_directories(nethost INTERFACE "${CoreClrEmbed_INCLUDE_DIRS}") - get_filename_component(CoreClrEmbed_FOLDER ${CoreClrEmbed_SHARED_LIBRARIES} DIRECTORY) - set_target_properties(nethost - PROPERTIES - IMPORTED_IMPLIB ${CoreClrEmbed_SHARED_LIBRARIES} - IMPORTED_LOCATION ${CoreClrEmbed_LIBRARIES} - BUILD_RPATH ${CoreClrEmbed_FOLDER} - INSTALL_RPATH ${CoreClrEmbed_FOLDER}) - - target_link_directories(script_loader PRIVATE ${CoreClrEmbed_FOLDER}) - target_include_directories(script_loader PRIVATE ${CoreClrEmbed_INCLUDE_DIRS}) - target_compile_definitions(script_loader PRIVATE DOTNET_PLUGINS=1) - target_sources(script_loader PRIVATE - source/loaders/dotnet/dotnet_loader.cpp - - source/script_api/v1/mem.cpp - source/script_api/v1/bookmarks.cpp - source/script_api/v1/ui.cpp + INCLUDES + include ) - set(EXTRA_BUNDLE_LIBRARY_PATHS "${CoreClrEmbed_FOLDER}" PARENT_SCOPE) + if (CoreClrEmbed_FOUND) + add_library(nethost SHARED IMPORTED) + target_include_directories(nethost INTERFACE "${CoreClrEmbed_INCLUDE_DIRS}") + get_filename_component(CoreClrEmbed_FOLDER ${CoreClrEmbed_SHARED_LIBRARIES} DIRECTORY) + set_target_properties(nethost + PROPERTIES + IMPORTED_IMPLIB ${CoreClrEmbed_SHARED_LIBRARIES} + IMPORTED_LOCATION ${CoreClrEmbed_LIBRARIES} + BUILD_RPATH ${CoreClrEmbed_FOLDER} + INSTALL_RPATH ${CoreClrEmbed_FOLDER}) + + target_link_directories(script_loader PRIVATE ${CoreClrEmbed_FOLDER}) + target_include_directories(script_loader PRIVATE ${CoreClrEmbed_INCLUDE_DIRS}) + target_compile_definitions(script_loader PRIVATE DOTNET_PLUGINS=1) + target_sources(script_loader PRIVATE + source/loaders/dotnet/dotnet_loader.cpp + + source/script_api/v1/mem.cpp + source/script_api/v1/bookmarks.cpp + source/script_api/v1/ui.cpp + ) + + set(EXTRA_BUNDLE_LIBRARY_PATHS "${CoreClrEmbed_FOLDER}" PARENT_SCOPE) + + if (IMHEX_BUNDLE_DOTNET) + install(FILES ${CoreClrEmbed_SHARED_LIBRARIES} DESTINATION ${CMAKE_INSTALL_LIBDIR}) + endif () + + add_subdirectory(dotnet) + add_dependencies(script_loader AssemblyLoader) - if (IMHEX_BUNDLE_DOTNET) - install(FILES ${CoreClrEmbed_SHARED_LIBRARIES} DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif () - add_subdirectory(dotnet) - add_dependencies(script_loader AssemblyLoader) - endif () \ No newline at end of file diff --git a/plugins/windows/CMakeLists.txt b/plugins/windows/CMakeLists.txt index 9d0253220..664263672 100644 --- a/plugins/windows/CMakeLists.txt +++ b/plugins/windows/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.16) if (WIN32) include(ImHexPlugin) - add_imhex_plugin( NAME windows diff --git a/resources/dist/common/try_online_banner.png b/resources/dist/common/try_online_banner.png new file mode 100644 index 000000000..4ef4a483c Binary files /dev/null and b/resources/dist/common/try_online_banner.png differ diff --git a/resources/projects/splash_wasm.xcf b/resources/projects/splash_wasm.xcf new file mode 100644 index 000000000..e37837ec7 Binary files /dev/null and b/resources/projects/splash_wasm.xcf differ