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:
parent
a62ede7840
commit
d15bd4771d
79
.github/workflows/build_web.yml
vendored
Normal file
79
.github/workflows/build_web.yml
vendored
Normal 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
|
@ -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)
|
||||
|
@ -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!
|
||||
|
@ -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)
|
||||
|
@ -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
76
dist/web/Dockerfile
vendored
Normal 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
11
dist/web/plugin-bundle.cpp.in
vendored
Normal 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
14
dist/web/serve.py
vendored
Normal 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
75
dist/web/source/enable-threads.js
vendored
Normal 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
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
73
dist/web/source/index.html
vendored
Normal 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
28
dist/web/source/style.css
vendored
Normal 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
68
dist/web/source/wasm-config.js
vendored
Normal 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
2
lib/external/fmt
vendored
@ -1 +1 @@
|
||||
Subproject commit f5e54359df4c26b6230fc61d38aa294581393084
|
||||
Subproject commit f9182893631e4a93e8a2d947b726f95a5367fce9
|
3
lib/external/imgui/CMakeLists.txt
vendored
3
lib/external/imgui/CMakeLists.txt
vendored
@ -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})
|
||||
|
@ -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
|
||||
{
|
||||
|
2
lib/external/libromfs
vendored
2
lib/external/libromfs
vendored
@ -1 +1 @@
|
||||
Subproject commit 31b331c02af7464e436f38e7aed27646e097b1bd
|
||||
Subproject commit 03365d8c5a43baa22cc6bbd5725db15a3038d035
|
2
lib/external/libwolv
vendored
2
lib/external/libwolv
vendored
@ -1 +1 @@
|
||||
Subproject commit 094d87ca30fb3f03485fac1c8c11f53baad52210
|
||||
Subproject commit f5f081c28efb9c06e14fa3b4a6d85866d75f7350
|
2
lib/external/pattern_language
vendored
2
lib/external/pattern_language
vendored
@ -1 +1 @@
|
||||
Subproject commit ff92cf631a7483faee461947538479919e736114
|
||||
Subproject commit a3574fe6c2dcca40cac5ac61dc95a063b6799249
|
4
lib/external/yara/CMakeLists.txt
vendored
4
lib/external/yara/CMakeLists.txt
vendored
@ -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}
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
@ -12,6 +12,10 @@
|
||||
#include <list>
|
||||
#include <condition_variable>
|
||||
|
||||
#if defined(OS_WEB)
|
||||
#include <jthread.hpp>
|
||||
#endif
|
||||
|
||||
namespace hex {
|
||||
|
||||
class TaskHolder;
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
||||
|
@ -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() });
|
||||
}
|
||||
|
||||
}
|
140
lib/libimhex/include/hex/helpers/http_requests_native.hpp
Normal file
140
lib/libimhex/include/hex/helpers/http_requests_native.hpp
Normal 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() });
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
55
lib/libimhex/source/helpers/http_requests_emscripten.cpp
Normal file
55
lib/libimhex/source/helpers/http_requests_emscripten.cpp
Normal 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
|
105
lib/libimhex/source/helpers/http_requests_native.cpp
Normal file
105
lib/libimhex/source/helpers/http_requests_native.cpp
Normal 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
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -9,8 +9,6 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <imgui_impl_opengl3_loader.h>
|
||||
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
|
||||
#include <fonts/codicons_font.h>
|
||||
|
@ -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}/../..
|
||||
|
@ -28,6 +28,8 @@ namespace hex {
|
||||
|
||||
static void initNative();
|
||||
|
||||
void resize(i32 width, i32 height);
|
||||
|
||||
private:
|
||||
void setupNativeWindow();
|
||||
void beginNativeWindowFrame();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
24
main/gui/source/messaging/web.cpp
Normal file
24
main/gui/source/messaging/web.cpp
Normal 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
|
@ -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
|
||||
|
@ -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() {
|
||||
|
71
main/gui/source/window/web_window.cpp
Normal file
71
main/gui/source/window/web_window.cpp
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -19,7 +19,7 @@ namespace hex::plugin::builtin {
|
||||
void drawContent() override;
|
||||
|
||||
private:
|
||||
ui::PatternDrawer m_patternDrawer;
|
||||
std::unique_ptr<ui::PatternDrawer> m_patternDrawer;
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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; };
|
||||
},
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <numbers>
|
||||
|
||||
namespace hex {
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 ®ion) {
|
||||
patternDrawer->setSelectionCallback([&](const auto ®ion) {
|
||||
hexEditor.setSelection(region);
|
||||
});
|
||||
|
||||
@ -568,7 +568,7 @@ namespace hex::plugin::builtin {
|
||||
}();
|
||||
|
||||
if (*this->m_executionDone)
|
||||
patternDrawer.draw(patterns, &runtime, 150_scaled);
|
||||
patternDrawer->draw(patterns, &runtime, 150_scaled);
|
||||
};
|
||||
}
|
||||
|
||||
@ -635,7 +635,7 @@ namespace hex::plugin::builtin {
|
||||
if (this->m_resetDebuggerVariables) {
|
||||
auto pauseLine = evaluator->getPauseLine();
|
||||
|
||||
this->m_debuggerDrawer->reset();
|
||||
(*this->m_debuggerDrawer)->reset();
|
||||
this->m_resetDebuggerVariables = false;
|
||||
this->m_textEditor.SetCursorPosition(TextEditor::Coordinates(pauseLine.value_or(0) - 1, 0));
|
||||
|
||||
@ -644,7 +644,7 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
auto &currScope = evaluator->getScope(-this->m_debuggerScopeIndex);
|
||||
this->m_debuggerDrawer->draw(*currScope.scope, &runtime, size.y - ImGui::GetTextLineHeightWithSpacing() * 4);
|
||||
(*this->m_debuggerDrawer)->draw(*currScope.scope, &runtime, size.y - ImGui::GetTextLineHeightWithSpacing() * 4);
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
@ -791,7 +791,11 @@ namespace hex::plugin::builtin {
|
||||
continue;
|
||||
|
||||
try {
|
||||
runtime.getInternals().preprocessor->preprocess(runtime, file.readString());
|
||||
auto &preprocessor = runtime.getInternals().preprocessor;
|
||||
auto ret = preprocessor->preprocess(runtime, file.readString());
|
||||
if (!ret.has_value()) {
|
||||
log::warn("Failed to preprocess file {} during MIME analysis: {}", entry.path().string(), (*preprocessor->getError()).what());
|
||||
}
|
||||
} catch (pl::core::err::PreprocessorError::Exception &e) {
|
||||
log::warn("Failed to preprocess file {} during MIME analysis: {}", entry.path().string(), e.what());
|
||||
}
|
||||
@ -1065,6 +1069,10 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
});
|
||||
|
||||
EventManager::subscribe<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");
|
||||
|
@ -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());
|
||||
|
@ -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 });
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 ()
|
@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.16)
|
||||
if (WIN32)
|
||||
|
||||
include(ImHexPlugin)
|
||||
|
||||
add_imhex_plugin(
|
||||
NAME
|
||||
windows
|
||||
|
BIN
resources/dist/common/try_online_banner.png
vendored
Normal file
BIN
resources/dist/common/try_online_banner.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
resources/projects/splash_wasm.xcf
Normal file
BIN
resources/projects/splash_wasm.xcf
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user