feat: Support for building ImHex for the web (#1328)

Co-authored-by: WerWolv <werwolv98@gmail.com>
Co-authored-by: AnnsAnn <git@annsann.eu>
This commit is contained in:
iTrooz 2023-10-04 12:00:32 +02:00 committed by GitHub
parent a62ede7840
commit d15bd4771d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 1825 additions and 676 deletions

79
.github/workflows/build_web.yml vendored Normal file
View file

@ -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

View file

@ -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)

View file

@ -33,6 +33,12 @@
</a>
</p>
<p align="center">
<a title="Use the Web version of ImHex right in your browser!" href="https://web.imhex.werwolv.net">
<img alt="Use the Web version of ImHex right in your browser!" src="resources/dist/common/try_online_banner.png">
</a>
</p>
## Supporting
If you like my work, please consider supporting me on GitHub Sponsors, Patreon or PayPal. Thanks a lot!

View file

@ -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)

View file

@ -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)

76
dist/web/Dockerfile vendored Normal file
View file

@ -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 <<EOF
set -xe
echo '
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
' >> /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 <<EOF
set -xe
ccache -zs
cmake /imhex \
-DIMHEX_OFFLINE_BUILD=ON \
-DIMHEX_STATIC_LINK_PLUGINS=ON \
-DNATIVE_CMAKE_C_COMPILER=gcc \
-DNATIVE_CMAKE_CXX_COMPILER=g++ \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_TOOLCHAIN_FILE=/vcpkg/scripts/buildsystems/vcpkg.cmake \
-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
-DCMAKE_BUILD_TYPE=Release
make -j $JOBS
cp /imhex/dist/web/source/* /build
ccache -s
EOF
FROM scratch
COPY --from=build [ \
# ImHex \
"/build/imhex.wasm", \
"/build/imhex.js", \
"/build/imhex.worker.js", \
\
# Static files \
"/build/index.html", \
"/build/style.css", \
"/build/wasm-config.js", \
"/build/enable-threads.js", \
"/build/favicon.ico", \
\
# Destination \
"./" \
]

11
dist/web/plugin-bundle.cpp.in vendored Normal file
View file

@ -0,0 +1,11 @@
#include <hex/helpers/logger.hpp>
extern "C" void forceLinkPlugin_@IMHEX_PLUGIN_NAME@();
struct StaticLoad {
StaticLoad() {
forceLinkPlugin_@IMHEX_PLUGIN_NAME@();
}
};
static StaticLoad staticLoad;

14
dist/web/serve.py vendored Normal file
View file

@ -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()

75
dist/web/source/enable-threads.js vendored Normal file
View file

@ -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();
// }

BIN
dist/web/source/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

73
dist/web/source/index.html vendored Normal file
View file

@ -0,0 +1,73 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!-- Primary Meta Tags -->
<title>ImHex - Hex Editor</title>
<meta name="title" content="ImHex">
<meta name="description" content="A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM.">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://imhex.werwolv.net/">
<meta property="og:title" content="ImHex Web">
<meta property="og:description">
<meta name="description" content="A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM.">
<meta property="og:image" content="https://imhex.werwolv.net/assets/splash_wasm.png">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="https://imhex.werwolv.net/">
<meta property="twitter:title" content="ImHex Web">
<meta property="twitter:description"
content="A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM.">
<meta property="twitter:image" content="https://imhex.werwolv.net/assets/splash_wasm.png">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" type="text/css" href="style.css" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"alumni": "WerWolv",
"email": "hey@werwolv.net",
"founder": "WerWolv",
"slogan": "A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM.",
"url": "https://imhex.werwolv.net",
"logo": "https://imhex.werwolv.net/assets/logos/logo.png"
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "ImHex",
"operatingSystem": "Windows, MacOS, Linux",
"applicationCategory": "DeveloperApplication",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
}
}
</script>
<title>ImHex Web</title>
<script src="enable-threads.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<p id="loading_text">ImHex is loading...</p>
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" style="display: none; image-rendering: crisp-edges"></canvas>
<script type="text/javascript" src="wasm-config.js"></script>
<script async type="text/javascript" src="imhex.js"></script>
</body>
</html>

28
dist/web/source/style.css vendored Normal file
View file

@ -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;
}

68
dist/web/source/wasm-config.js vendored Normal file
View file

@ -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;
}

2
lib/external/fmt vendored

@ -1 +1 @@
Subproject commit f5e54359df4c26b6230fc61d38aa294581393084
Subproject commit f9182893631e4a93e8a2d947b726f95a5367fce9

View file

@ -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})

View file

@ -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
{

@ -1 +1 @@
Subproject commit 31b331c02af7464e436f38e7aed27646e097b1bd
Subproject commit 03365d8c5a43baa22cc6bbd5725db15a3038d035

@ -1 +1 @@
Subproject commit 094d87ca30fb3f03485fac1c8c11f53baad52210
Subproject commit f5f081c28efb9c06e14fa3b4a6d85866d75f7350

@ -1 +1 @@
Subproject commit ff92cf631a7483faee461947538479919e736114
Subproject commit a3574fe6c2dcca40cac5ac61dc95a063b6799249

View file

@ -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 $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/yara> $<BUILD_INTERFACE:${LIBYARA_SOURCE_PATH}/include> $<INSTALL_INTERFACE:include>
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/yara> $<BUILD_INTERFACE:${LIBYARA_SOURCE_PATH}/include>
PRIVATE ${LIBYARA_SOURCE_PATH} ${MBEDTLS_INCLUDE_DIR}
)

View file

@ -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)

View file

@ -16,6 +16,10 @@
#include <unordered_map>
#include <vector>
#if defined(OS_WEB)
#include <jthread.hpp>
#endif
#include <nlohmann/json_fwd.hpp>
using ImGuiDataType = int;

View file

@ -24,9 +24,31 @@ namespace hex {
std::function<void(const std::vector<std::string>&)> 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<SubCommand> 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<typename T>
[[nodiscard]] auto getPluginFunction(const std::string &symbol) {
@ -90,7 +96,10 @@ namespace hex {
static void unload();
static void reload();
static const std::vector<Plugin> &getPlugins();
static void addPlugin(PluginFunctions functions);
static std::vector<Plugin> &getPlugins();
static std::vector<std::fs::path> &getPluginPaths();
};
}

View file

@ -12,6 +12,10 @@
#include <list>
#include <condition_variable>
#if defined(OS_WEB)
#include <jthread.hpp>
#endif
namespace hex {
class TaskHolder;

View file

@ -8,8 +8,6 @@
#include <filesystem>
#include <functional>
#include <nfd.hpp>
#include <wolv/io/fs.hpp>
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<void(const std::string&)> &callback);
bool openFileBrowser(DialogMode mode, const std::vector<nfdfilteritem_t> &validExtensions, const std::function<void(std::fs::path)> &callback, const std::string &defaultPath = {}, bool multiple = false);
bool openFileBrowser(DialogMode mode, const std::vector<ItemFilter> &validExtensions, const std::function<void(std::fs::path)> &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<std::fs::path> getDefaultPaths(ImHexPath path, bool listNonExisting = false);
// temporarily expose these for the migration function
// Temporarily expose these for the migration function
std::vector<std::fs::path> getDataPaths();
std::vector<std::fs::path> appendPath(std::vector<std::fs::path> paths, const std::fs::path &folder);
}

View file

@ -7,8 +7,6 @@
#include <string>
#include <vector>
#include <curl/curl.h>
#include <hex/helpers/logger.hpp>
#include <hex/helpers/fmt.hpp>
@ -19,6 +17,14 @@
#include <mbedtls/ssl.h>
#if defined(OS_WEB)
#include <emscripten/fetch.h>
using curl_off_t = long;
#else
#include <curl/curl.h>
#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<typename T = std::string>
std::future<Result<T>> downloadFile(const std::fs::path &path) {
return std::async(std::launch::async, [this, path] {
std::vector<u8> response;
std::future<Result<T>> 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<T>(response);
});
}
std::future<Result<std::vector<u8>>> downloadFile() {
return std::async(std::launch::async, [this] {
std::vector<u8> response;
curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector);
curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &response);
return this->executeImpl<std::vector<u8>>(response);
});
}
std::future<Result<std::vector<u8>>> downloadFile();
template<typename T = std::string>
std::future<Result<T>> 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<FILE*>(arg);
return fread(buffer, size, nitems, file);
},
[](void *arg, curl_off_t offset, int origin) -> int {
auto file = static_cast<FILE*>(arg);
if (fseek(file, offset, origin) != 0)
return CURL_SEEKFUNC_CANTSEEK;
else
return CURL_SEEKFUNC_OK;
},
[](void *arg) {
auto file = static_cast<FILE*>(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<u8> responseData;
curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector);
curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData);
return this->executeImpl<T>(responseData);
});
}
template<typename T = std::string>
std::future<Result<T>> uploadFile(std::vector<u8> 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<const char *>(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<u8> responseData;
curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector);
curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData);
return this->executeImpl<T>(responseData);
});
}
std::future<Result<T>> uploadFile(const std::fs::path &path, const std::string &mimeName = "filename");
template<typename T = std::string>
std::future<Result<T>> execute() {
return std::async(std::launch::async, [this] {
std::future<Result<T>> uploadFile(std::vector<u8> data, const std::string &mimeName = "filename", const std::fs::path &fileName = "data.bin");
std::vector<u8> responseData;
curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector);
curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData);
template<typename T = std::string>
std::future<Result<T>> execute();
return this->executeImpl<T>(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<typename T>
Result<T> executeImpl(std::vector<u8> &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<T>(statusCode, { data.begin(), data.end() });
}
Result<T> executeImpl(std::vector<u8> &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<std::vector<u8>> m_promise;
std::map<std::string, std::string> m_headers;
u32 m_timeout = 1000;
std::atomic<float> m_progress = 0.0F;
std::atomic<bool> m_canceled = false;
};
}
}
#if defined(OS_WEB)
#include <hex/helpers/http_requests_emscripten.hpp>
#else
#include <hex/helpers/http_requests_native.hpp>
#endif

View file

@ -0,0 +1,72 @@
#pragma once
#include<future>
#include <emscripten/fetch.h>
namespace hex {
template<typename T>
std::future<HttpRequest::Result<T>> HttpRequest::downloadFile(const std::fs::path &path) {
return std::async(std::launch::async, [this, path] {
std::vector<u8> response;
// Execute the request
auto result = this->executeImpl<T>(response);
// Write the result to the file
wolv::io::File file(path, wolv::io::File::Mode::Create);
file.writeBuffer(reinterpret_cast<const u8*>(result.getData().data()), result.getData().size());
return result;
});
}
template<typename T>
std::future<HttpRequest::Result<T>> HttpRequest::uploadFile(const std::fs::path &path, const std::string &mimeName) {
hex::unused(path, mimeName);
throw std::logic_error("Not implemented");
}
template<typename T>
std::future<HttpRequest::Result<T>> HttpRequest::uploadFile(std::vector<u8> data, const std::string &mimeName, const std::fs::path &fileName) {
hex::unused(data, mimeName, fileName);
throw std::logic_error("Not implemented");
}
template<typename T>
std::future<HttpRequest::Result<T>> HttpRequest::execute() {
return std::async(std::launch::async, [this] {
std::vector<u8> responseData;
return this->executeImpl<T>(responseData);
});
}
template<typename T>
HttpRequest::Result<T> HttpRequest::executeImpl(std::vector<u8> &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<const char*> 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<T>(fetch->status, { data.begin(), data.end() });
}
}

View file

@ -0,0 +1,140 @@
#pragma once
#include<string>
#include<future>
#include <curl/curl.h>
namespace hex {
template<typename T>
std::future<HttpRequest::Result<T>> HttpRequest::downloadFile(const std::fs::path &path) {
return std::async(std::launch::async, [this, path] {
std::vector<u8> 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<T>(response);
});
}
template<typename T>
std::future<HttpRequest::Result<T>> 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<FILE*>(arg);
return fread(buffer, size, nitems, file);
},
[](void *arg, curl_off_t offset, int origin) -> int {
auto file = static_cast<FILE*>(arg);
if (fseek(file, offset, origin) != 0)
return CURL_SEEKFUNC_CANTSEEK;
else
return CURL_SEEKFUNC_OK;
},
[](void *arg) {
auto file = static_cast<FILE*>(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<u8> responseData;
curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector);
curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData);
return this->executeImpl<T>(responseData);
});
}
template<typename T>
std::future<HttpRequest::Result<T>> HttpRequest::uploadFile(std::vector<u8> 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<const char *>(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<u8> responseData;
curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector);
curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData);
return this->executeImpl<T>(responseData);
});
}
template<typename T>
std::future<HttpRequest::Result<T>> HttpRequest::execute() {
return std::async(std::launch::async, [this] {
std::vector<u8> responseData;
curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector);
curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &responseData);
return this->executeImpl<T>(responseData);
});
}
template<typename T>
HttpRequest::Result<T> HttpRequest::executeImpl(std::vector<u8> &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<T>(statusCode, { data.begin(), data.end() });
}
}

View file

@ -9,7 +9,14 @@
#include <span>
#include <string>
#include <imgui_impl_opengl3_loader.h>
#if defined(OS_WEB)
#define GLFW_INCLUDE_ES3
#include <GLES3/gl3.h>
#else
#include <imgui_impl_opengl3_loader.h>
#endif
#include <GLFW/glfw3.h>
namespace hex::gl {

View file

@ -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;
};

View file

@ -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())

View file

@ -9,6 +9,13 @@
#include <hex/api/plugin_manager.hpp>
#include <wolv/utils/string.hpp>
#include <wolv/utils/preproc.hpp>
#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

View file

@ -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<MovePerProviderData>(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));
});

View file

@ -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<decltype(args)>(args)...),
ImGui::CalcTextSize("M").x,

View file

@ -8,6 +8,10 @@
#include <filesystem>
#include <thread>
#if defined(OS_WEB)
#include <jthread.hpp>
#include <emscripten.h>
#endif
#include <nlohmann/json.hpp>
@ -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();

View file

@ -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) {

View file

@ -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<InitializePluginFunc>("initializePlugin");
this->m_getPluginNameFunction = getPluginFunction<GetPluginNameFunc>("getPluginName");
this->m_getPluginAuthorFunction = getPluginFunction<GetPluginAuthorFunc>("getPluginAuthor");
this->m_getPluginDescriptionFunction = getPluginFunction<GetPluginDescriptionFunc>("getPluginDescription");
this->m_getCompatibleVersionFunction = getPluginFunction<GetCompatibleVersionFunc>("getCompatibleVersion");
this->m_setImGuiContextFunction = getPluginFunction<SetImGuiContextFunc>("setImGuiContext");
this->m_isBuiltinPluginFunction = getPluginFunction<IsBuiltinPluginFunc>("isBuiltinPlugin");
this->m_getSubCommandsFunction = getPluginFunction<GetSubCommandsFunc>("getSubCommands");
this->m_functions.initializePluginFunction = getPluginFunction<PluginFunctions::InitializePluginFunc>("initializePlugin");
this->m_functions.getPluginNameFunction = getPluginFunction<PluginFunctions::GetPluginNameFunc>("getPluginName");
this->m_functions.getPluginAuthorFunction = getPluginFunction<PluginFunctions::GetPluginAuthorFunc>("getPluginAuthor");
this->m_functions.getPluginDescriptionFunction = getPluginFunction<PluginFunctions::GetPluginDescriptionFunc>("getPluginDescription");
this->m_functions.getCompatibleVersionFunction = getPluginFunction<PluginFunctions::GetCompatibleVersionFunc>("getCompatibleVersion");
this->m_functions.setImGuiContextFunction = getPluginFunction<PluginFunctions::SetImGuiContextFunc>("setImGuiContext");
this->m_functions.isBuiltinPluginFunction = getPluginFunction<PluginFunctions::IsBuiltinPluginFunc>("isBuiltinPlugin");
this->m_functions.getSubCommandsFunction = getPluginFunction<PluginFunctions::GetSubCommandsFunc>("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<intptr_t>(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<SubCommand> 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<std::vector<SubCommand>*>(result);
} else
return { };
@ -171,43 +160,50 @@ namespace hex {
#endif
}
namespace {
std::fs::path s_pluginFolder;
std::vector<Plugin> 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<Plugin> &PluginManager::getPlugins() {
return s_plugins;
void PluginManager::addPlugin(hex::PluginFunctions functions) {
getPlugins().emplace_back(functions);
}
std::vector<Plugin> &PluginManager::getPlugins() {
static std::vector<Plugin> plugins;
return plugins;
}
std::vector<std::fs::path> &PluginManager::getPluginPaths() {
static std::vector<std::fs::path> pluginPaths;
return pluginPaths;
}
}

View file

@ -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

View file

@ -82,7 +82,7 @@ namespace hex::crypt {
template<size_t NumBits> 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<size_t>(BufferSize, input.size() - offset));

View file

@ -13,7 +13,13 @@
#include <shlobj.h>
#elif defined(OS_LINUX)
#include <xdg.hpp>
#include <linux/limits.h>
#include <limits.h>
#endif
#if !defined(OS_WEB)
#include <nfd.hpp>
#else
#include <emscripten.h>
#endif
#include <algorithm>
@ -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<nfdfilteritem_t> &validExtensions, const std::function<void(std::fs::path)> &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<void(std::fs::path)> 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<char8_t*>(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<char8_t*>(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<ItemFilter> &validExtensions, const std::function<void(std::fs::path)> &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<ItemFilter> &validExtensions, const std::function<void(std::fs::path)> &callback, const std::string &defaultPath, bool multiple) {
std::vector<nfdfilteritem_t> 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<char8_t*>(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<char8_t*>(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<std::fs::path> getDataPaths() {
std::vector<std::fs::path> 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
}

View file

@ -1,47 +1,9 @@
#include <hex/helpers/http_requests.hpp>
#include <hex/helpers/crypto.hpp>
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<std::vector<u8>*>(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<HttpRequest *>(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;
}
}

View file

@ -0,0 +1,55 @@
#if defined(OS_WEB)
#include <hex/helpers/http_requests.hpp>
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::Result<std::vector<u8>>> HttpRequest::downloadFile() {
return std::async(std::launch::async, [this] {
std::vector<u8> response;
return this->executeImpl<std::vector<u8>>(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

View file

@ -0,0 +1,105 @@
#if !defined(OS_WEB)
#include <hex/helpers/http_requests.hpp>
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::Result<std::vector<u8>>> HttpRequest::downloadFile() {
return std::async(std::launch::async, [this] {
std::vector<u8> response;
curl_easy_setopt(this->m_curl, CURLOPT_WRITEFUNCTION, writeToVector);
curl_easy_setopt(this->m_curl, CURLOPT_WRITEDATA, &response);
return this->executeImpl<std::vector<u8>>(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<HttpRequest *>(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

View file

@ -5,6 +5,13 @@
#include <wolv/utils/guards.hpp>
#if defined(OS_WEB)
#define GLFW_INCLUDE_ES3
#include <GLES3/gl3.h>
#else
#include <imgui_impl_opengl3_loader.h>
#endif
namespace hex::gl {
Shader::Shader(std::string_view vertexSource, std::string_view fragmentSource) {

View file

@ -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;
}
}

View file

@ -20,6 +20,8 @@
#elif defined(OS_MACOS)
#include <hex/helpers/utils_macos.hpp>
#include <unistd.h>
#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<std::string> getEnvironmentVariable(const std::string &env) {

View file

@ -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<SubCommand> 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<std::string> 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);
}

View file

@ -9,8 +9,6 @@
#include <string>
#include <imgui_impl_opengl3_loader.h>
#include <hex/api/imhex_api.hpp>
#include <fonts/codicons_font.h>

View file

@ -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}/../..

View file

@ -28,6 +28,8 @@ namespace hex {
static void initNative();
void resize(i32 width, i32 height);
private:
void setupNativeWindow();
void beginNativeWindowFrame();

View file

@ -16,7 +16,6 @@
#include <hex/ui/imgui_imhex_extensions.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <imgui_impl_opengl3_loader.h>
#include <fonts/fontawesome_font.h>
#include <GLFW/glfw3.h>
@ -29,6 +28,14 @@
#include <numeric>
#include <random>
#if defined(OS_WEB)
#define GLFW_INCLUDE_ES3
#include <GLES3/gl3.h>
#include <emscripten/html5.h>
#else
#include <imgui_impl_opengl3_loader.h>
#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

View file

@ -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;
}

View file

@ -19,6 +19,11 @@
#include <wolv/io/fs.hpp>
#include <wolv/utils/guards.hpp>
#if defined(OS_WEB)
#include <emscripten.h>
#include <emscripten/html5.h>
#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<WindowSplash*>(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<RequestRestartImHex>([&] {
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<RequestRestartImHex>([&] {
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();
};

View file

@ -0,0 +1,24 @@
#if defined(OS_WEB)
#include<stdexcept>
#include <hex/helpers/intrinsics.hpp>
#include <hex/helpers/logger.hpp>
#include "messaging.hpp"
namespace hex::messaging {
void sendToOtherInstance(const std::string &eventName, const std::vector<u8> &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

View file

@ -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<HWND*>(ret) = hWnd;
return FALSE;
}
}
// continue iteration
// Continue iteration
return TRUE;
}, reinterpret_cast<LPARAM>(&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

View file

@ -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() {

View file

@ -0,0 +1,71 @@
#include "window.hpp"
#if defined(OS_WEB)
#include <emscripten.h>
#include <emscripten/html5.h>
// 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

View file

@ -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<Window *>(glfwGetWindowUserPointer(window));
auto win = static_cast<Window *>(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

View file

@ -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<ImU64, 256> &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<ImU64, 256> m_blockValueCounts;
// Variable to hold the result of the chunk based
// Variable to hold the result of the chunk-based
// entropy analysis
std::vector<double> m_xBlockEntropy;
std::vector<double> 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<ImU64, 256> 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<float> m_xBlockTypeDistributions;
// Hold the result of the byte distribution analysis

View file

@ -11,7 +11,7 @@ namespace hex::plugin::builtin {
class PopupFileChooser : public Popup<PopupFileChooser> {
public:
PopupFileChooser(const std::vector<std::fs::path> &files, const std::vector<nfdfilteritem_t> &validExtensions, bool multiple, const std::function<void(std::fs::path)> &callback)
PopupFileChooser(const std::vector<std::fs::path> &files, const std::vector<hex::fs::ItemFilter> &validExtensions, bool multiple, const std::function<void(std::fs::path)> &callback)
: hex::Popup<PopupFileChooser>("hex.builtin.common.choose_file"),
m_indices({ }), m_files(files),
m_openCallback(callback),
@ -80,7 +80,7 @@ namespace hex::plugin::builtin {
std::set<u32> m_indices;
std::vector<std::fs::path> m_files;
std::function<void(std::fs::path)> m_openCallback;
std::vector<nfdfilteritem_t> m_validExtensions;
std::vector<hex::fs::ItemFilter> m_validExtensions;
bool m_multiple = false;
};

View file

@ -1,4 +1,5 @@
#pragma once
#if !defined(OS_WEB)
#include <hex/providers/provider.hpp>
@ -72,4 +73,5 @@ namespace hex::plugin::builtin {
bool m_writable = false;
};
}
}
#endif

View file

@ -19,7 +19,7 @@ namespace hex::plugin::builtin {
void drawContent() override;
private:
ui::PatternDrawer m_patternDrawer;
std::unique_ptr<ui::PatternDrawer> m_patternDrawer;
};
}

View file

@ -156,7 +156,7 @@ namespace hex::plugin::builtin {
bool m_syncPatternSourceCode = false;
bool m_autoLoadPatterns = true;
std::map<prv::Provider*, std::move_only_function<void()>> m_sectionWindowDrawer;
std::map<prv::Provider*, std::function<void()>> m_sectionWindowDrawer;
ui::HexEditor m_sectionHexEditor;
@ -175,7 +175,7 @@ namespace hex::plugin::builtin {
PerProvider<bool> m_shouldAnalyze;
PerProvider<bool> m_breakpointHit;
PerProvider<ui::PatternDrawer> m_debuggerDrawer;
PerProvider<std::unique_ptr<ui::PatternDrawer>> m_debuggerDrawer;
std::atomic<bool> m_resetDebuggerVariables;
int m_debuggerScopeIndex = 0;

View file

@ -20,6 +20,8 @@ namespace hex::plugin::builtin::ui {
this->m_formatters = pl::gen::fmt::createFormatters();
}
virtual ~PatternDrawer() = default;
void draw(const std::vector<std::shared_ptr<pl::ptrn::Pattern>> &patterns, pl::PatternLanguage *runtime = nullptr, float height = 0.0F);
enum class TreeStyle {

View file

@ -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; };
},

View file

@ -9,6 +9,7 @@
#include <cmath>
#include <cstdint>
#include <optional>
#include <numbers>
namespace hex {

View file

@ -9,7 +9,16 @@
#include <imgui.h>
#include <implot.h>
#include <imgui_impl_opengl3_loader.h>
#if defined(OS_WEB)
#define GLFW_INCLUDE_ES3
#include <GLES3/gl3.h>
#else
#include <imgui_impl_opengl3_loader.h>
#endif
#include <GLFW/glfw3.h>
#include <hex/ui/imgui_imhex_extensions.h>
#include <fonts/codicons_font.h>
@ -476,17 +485,17 @@ namespace hex::plugin::builtin {
}
void drawChunkBasedEntropyVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> 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);
}

View file

@ -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<RequestUpdateWindowTitle>(); // request, as this puts us into a project state
// Request, as this puts us into a project state
EventManager::post<RequestUpdateWindowTitle>();
return result;
}

View file

@ -25,7 +25,9 @@ namespace hex::plugin::builtin {
ContentRegistry::Provider::add<FileProvider>(false);
ContentRegistry::Provider::add<NullProvider>(false);
#if !defined(OS_WEB)
ContentRegistry::Provider::add<DiskProvider>();
#endif
ContentRegistry::Provider::add<GDBProvider>();
ContentRegistry::Provider::add<IntelHexProvider>();
ContentRegistry::Provider::add<MotorolaSRECProvider>();
@ -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(

View file

@ -1,3 +1,4 @@
#if !defined(OS_WEB)
#include <hex/helpers/logger.hpp>
#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);
}
}
}
#endif

View file

@ -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<EventProjectOpened>([] {
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);

View file

@ -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;
}

View file

@ -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<long double>(-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<long double>(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<long double>(u128(1) << (ieee754.exponentBits - ieee754.exponentBias));
// exponent is negative
// Exponent is negative
else if (ieee754.exponentBits < ieee754.exponentBias)
ieee754.exponentValue = 1.0 / static_cast<long double>(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<u64>(ieee754.exponentBits)) == static_cast<i64>(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();

View file

@ -1066,7 +1066,7 @@ namespace hex::plugin::builtin {
}
}
PopupFileChooser::open(paths, std::vector<nfdfilteritem_t>{ {"Thingy Table File", "tbl"} }, false,
PopupFileChooser::open(paths, std::vector<hex::fs::ItemFilter>{ {"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);

View file

@ -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);

View file

@ -9,27 +9,29 @@
namespace hex::plugin::builtin {
ViewPatternData::ViewPatternData() : View("hex.builtin.view.pattern_data.name") {
this->m_patternDrawer = std::make_unique<ui::PatternDrawer>();
// Handle tree style setting changes
EventManager::subscribe<EventSettingsChanged>(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<ui::PatternDrawer::TreeStyle>(patternStyle));
this->m_patternDrawer->setTreeStyle(static_cast<ui::PatternDrawer::TreeStyle>(patternStyle));
});
// Reset the pattern drawer when the provider changes
EventManager::subscribe<EventProviderChanged>(this, [this](auto, auto) {
this->m_patternDrawer.reset();
this->m_patternDrawer->reset();
});
EventManager::subscribe<EventPatternEvaluating>(this, [this]{
this->m_patternDrawer.reset();
this->m_patternDrawer->reset();
});
EventManager::subscribe<EventPatternExecuted>(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);
}
}
}

View file

@ -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<MemoryFileProvider>();
auto dataProvider = std::make_shared<MemoryFileProvider>();
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<ui::PatternDrawer>(), &runtime] mutable {
hexEditor.setProvider(dataProvider.get());
hexEditor.draw(480_scaled);
patternDrawer.setSelectionCallback([&](const auto &region) {
patternDrawer->setSelectionCallback([&](const auto &region) {
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<EventProviderOpened>(this, [this](prv::Provider *provider) {
this->m_debuggerDrawer.get(provider) = std::make_unique<ui::PatternDrawer>();
});
EventManager::subscribe<EventProviderClosed>(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<nfdfilteritem_t>{ { "Pattern File", "hexpat" } }, false,
PopupFileChooser::open(paths, std::vector<hex::fs::ItemFilter>{ { "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");

View file

@ -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());

View file

@ -126,7 +126,7 @@ namespace hex::plugin::builtin {
}
}
PopupFileChooser::open(paths, std::vector<nfdfilteritem_t>{ { "Yara File", "yara" }, { "Yara File", "yar" } }, true,
PopupFileChooser::open(paths, std::vector<hex::fs::ItemFilter>{ { "Yara File", "yara" }, { "Yara File", "yar" } }, true,
[&](const auto &path) {
this->m_rules->push_back({ path.filename(), path });
});

View file

@ -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<EventWindowInitialized>([] {
// 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);
}

View file

@ -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 ()

View file

@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.16)
if (WIN32)
include(ImHexPlugin)
add_imhex_plugin(
NAME
windows

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.