mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-03-28 15:57:03 -05:00
Compare commits
133 Commits
releases/v
...
python_scr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16b91eb715 | ||
|
|
8b9d09aa97 | ||
|
|
ccb78246ab | ||
|
|
3144d79605 | ||
|
|
e984fde966 | ||
|
|
5d0b474a7e | ||
|
|
d09f2c4f26 | ||
|
|
dd20a16d3a | ||
|
|
7d22b71e86 | ||
|
|
567ccbfc3a | ||
|
|
ca40775678 | ||
|
|
4916e5542a | ||
|
|
ac8ec2b622 | ||
|
|
9b9f7e2a1d | ||
|
|
28ea91e6c3 | ||
|
|
0d58307e82 | ||
|
|
c8ca84ede9 | ||
|
|
ed2939c39e | ||
|
|
d36bd253e8 | ||
|
|
4615dce0a9 | ||
|
|
7ce8aa3638 | ||
|
|
9236b92dc1 | ||
|
|
05ffcab911 | ||
|
|
61b9c0970b | ||
|
|
3b3701135f | ||
|
|
f5987fde5a | ||
|
|
e56b34f174 | ||
|
|
0fb43ccc2b | ||
|
|
48db4df028 | ||
|
|
86a0693081 | ||
|
|
6295c1d0c3 | ||
|
|
35d29c8e30 | ||
|
|
ca78c4c2fc | ||
|
|
f276409cde | ||
|
|
682f7bee72 | ||
|
|
43bec6a636 | ||
|
|
6eb9c750a7 | ||
|
|
31c93c8c5c | ||
|
|
5aa1046541 | ||
|
|
0f4504476a | ||
|
|
3897245a7e | ||
|
|
373db3de95 | ||
|
|
a1437658af | ||
|
|
f4ec69021d | ||
|
|
6012f20fb3 | ||
|
|
95da957f73 | ||
|
|
642722bdb1 | ||
|
|
cbc31f3c18 | ||
|
|
f2309ba079 | ||
|
|
246ed15d6d | ||
|
|
88756c83c7 | ||
|
|
cf320266df | ||
|
|
47e7e80afe | ||
|
|
0d880babfb | ||
|
|
28ba34f1bf | ||
|
|
e786cb8180 | ||
|
|
458584d778 | ||
|
|
2c711ea206 | ||
|
|
7b25be51a5 | ||
|
|
45b05a4846 | ||
|
|
6972736abf | ||
|
|
3798654f92 | ||
|
|
fdf01dfb50 | ||
|
|
876f091244 | ||
|
|
2988561f01 | ||
|
|
fbfc319ac1 | ||
|
|
9b1417f32d | ||
|
|
c727762940 | ||
|
|
e3565d5bcb | ||
|
|
a3f550c585 | ||
|
|
c610d804b1 | ||
|
|
3d592dbc79 | ||
|
|
0186f2f456 | ||
|
|
d817a813b0 | ||
|
|
1d219ba511 | ||
|
|
285afb6d4b | ||
|
|
ca3708df71 | ||
|
|
c2aafb14c2 | ||
|
|
d4d1acb555 | ||
|
|
d1a59f8c1b | ||
|
|
2fd17f97b6 | ||
|
|
45a3bdffe0 | ||
|
|
f050c69ccd | ||
|
|
c31a2551f1 | ||
|
|
90e93492a7 | ||
|
|
54266bf63b | ||
|
|
ba12f7aec9 | ||
|
|
deafb6fe08 | ||
|
|
091be1440a | ||
|
|
030aee17f5 | ||
|
|
bbbf836374 | ||
|
|
f1b91ef360 | ||
|
|
f6c59b456f | ||
|
|
8f3f941600 | ||
|
|
e561f49e80 | ||
|
|
2ff884fd11 | ||
|
|
296af748ee | ||
|
|
0cb10fcc34 | ||
|
|
4a67ea0b29 | ||
|
|
8e94acc98f | ||
|
|
39cda3764b | ||
|
|
97f5175c84 | ||
|
|
78f8e5055e | ||
|
|
735d896260 | ||
|
|
cb7a6596ba | ||
|
|
fdaa56fd86 | ||
|
|
667b940feb | ||
|
|
bb3de7d510 | ||
|
|
7bdde15796 | ||
|
|
c412ba66d8 | ||
|
|
dd62bee264 | ||
|
|
f886eac7b5 | ||
|
|
623079ca40 | ||
|
|
ce9bd796d6 | ||
|
|
d5f323a2cd | ||
|
|
40592a93ac | ||
|
|
dc1a5a860c | ||
|
|
f7b431902d | ||
|
|
686d47a59e | ||
|
|
eaa4688182 | ||
|
|
72645aa800 | ||
|
|
7044fc8004 | ||
|
|
9e8c780d66 | ||
|
|
e1795d687f | ||
|
|
607f7cba8d | ||
|
|
2572e23928 | ||
|
|
60921031bd | ||
|
|
77550d902c | ||
|
|
41935781fb | ||
|
|
47362559ef | ||
|
|
032ef0722d | ||
|
|
6e32f03a6b | ||
|
|
5731dcf135 |
44
.github/workflows/build.yml
vendored
44
.github/workflows/build.yml
vendored
@@ -213,21 +213,45 @@ jobs:
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_OBJC_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_OBJCXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_INSTALL_PREFIX="./install" \
|
||||
-DIMHEX_PATTERNS_PULL_MASTER=ON \
|
||||
-DIMHEX_COMMIT_HASH_LONG="${GITHUB_SHA}" \
|
||||
-DIMHEX_COMMIT_BRANCH="${GITHUB_REF##*/}" \
|
||||
-DCPACK_PACKAGE_FILE_NAME="imhex-${{env.IMHEX_VERSION}}-macOS${{matrix.suffix}}-x86_64" \
|
||||
..
|
||||
|
||||
- name: 🛠️ Build
|
||||
run: cd build && ninja package
|
||||
run: cd build && ninja install
|
||||
|
||||
- name: ✒️ Fix Signature
|
||||
run: |
|
||||
set -x
|
||||
cd build/install
|
||||
mv imhex.app ImHex.app
|
||||
codesign --remove-signature ImHex.app
|
||||
codesign --force --deep --sign - ImHex.app
|
||||
|
||||
- name: 📁 Fix permissions
|
||||
run: |
|
||||
set -x
|
||||
cd build/install
|
||||
chmod -R 755 ImHex.app/
|
||||
|
||||
- name: 📦 Create DMG
|
||||
run: |
|
||||
set -x
|
||||
mkdir bundle
|
||||
mv build/install/ImHex.app bundle
|
||||
cd bundle
|
||||
ln -s /Applications Applications
|
||||
cd ..
|
||||
hdiutil create -volname "ImHex" -srcfolder bundle -ov -format UDZO imhex-${{env.IMHEX_VERSION}}-macOS${{matrix.suffix}}-x86_64.dmg
|
||||
|
||||
- name: ⬆️ Upload DMG
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
if-no-files-found: error
|
||||
name: macOS DMG${{matrix.suffix}} x86_64
|
||||
path: build/*.dmg
|
||||
path: ./*.dmg
|
||||
|
||||
macos-arm64-build:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -287,15 +311,15 @@ jobs:
|
||||
path: out
|
||||
|
||||
- name: 🗑️ Delete artifact
|
||||
uses: geekyeggo/delete-artifact@v4
|
||||
uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: macos_arm64_intermediate
|
||||
|
||||
- name: ✒️ Fix Signature
|
||||
run: |
|
||||
set -x
|
||||
cd out
|
||||
mv imhex.app ImHex.app
|
||||
codesign --remove-signature ImHex.app
|
||||
codesign --force --deep --sign - ImHex.app
|
||||
|
||||
@@ -627,11 +651,11 @@ jobs:
|
||||
- name: ✒️ Modify spec file
|
||||
run: |
|
||||
sed -i \
|
||||
-e 's/Version: [0-9]*\.[0-9]*\.[0-9]*$/Version: ${{env.IMHEX_VERSION}}/g' \
|
||||
-e 's/IMHEX_OFFLINE_BUILD=ON/IMHEX_OFFLINE_BUILD=OFF/g' \
|
||||
-e '/IMHEX_OFFLINE_BUILD=OFF/a -D IMHEX_PATTERNS_PULL_MASTER=ON \\' \
|
||||
-e '/BuildRequires: cmake/a BuildRequires: git-core' \
|
||||
-e '/%files/a %{_datadir}/%{name}/' \
|
||||
-e 's/Version: VERSION$/Version: ${{env.IMHEX_VERSION}}/g' \
|
||||
-e 's/IMHEX_OFFLINE_BUILD=ON/IMHEX_OFFLINE_BUILD=OFF/g' \
|
||||
-e '/IMHEX_OFFLINE_BUILD=OFF/a -D IMHEX_PATTERNS_PULL_MASTER=ON \\' \
|
||||
-e '/BuildRequires: cmake/a BuildRequires: git-core' \
|
||||
-e '/%files/a %{_datadir}/%{name}/' \
|
||||
$GITHUB_WORKSPACE/ImHex/dist/rpm/imhex.spec
|
||||
|
||||
- name: 📜 Fix ccache on EL9
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -90,7 +90,7 @@ jobs:
|
||||
run: tar --exclude-vcs -czvf Full.Sources.tar.gz ImHex
|
||||
|
||||
- name: ⬇️ Download artifacts from latest workflow
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
workflow: build.yml
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
mv "Windows Portable NoGPU x86_64.zip" imhex-${{ env.IMHEX_VERSION }}-Windows-Portable-NoGPU-x86_64.zip
|
||||
|
||||
- name: ⬆️ Upload everything to release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981
|
||||
with:
|
||||
files: '*'
|
||||
|
||||
|
||||
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
@@ -53,6 +53,7 @@ jobs:
|
||||
cd build
|
||||
CC=gcc-12 CXX=g++-12 cmake \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DIMHEX_ENABLE_UNIT_TESTS=ON \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_C_FLAGS="-fuse-ld=lld -fsanitize=address,leak,undefined -fno-sanitize-recover=all" \
|
||||
|
||||
@@ -40,6 +40,9 @@ project(imhex
|
||||
HOMEPAGE_URL "https://imhex.werwolv.net"
|
||||
)
|
||||
|
||||
# Add ImHex sources
|
||||
add_custom_target(imhex_all ALL)
|
||||
|
||||
# Make sure project is configured correctly
|
||||
setDefaultBuiltTypeIfUnset()
|
||||
detectBadClone()
|
||||
@@ -56,17 +59,19 @@ configurePackingResources()
|
||||
setUninstallTarget()
|
||||
addBundledLibraries()
|
||||
|
||||
# Add ImHex sources
|
||||
add_custom_target(imhex_all ALL)
|
||||
|
||||
add_subdirectory(lib/libimhex)
|
||||
add_subdirectory(main)
|
||||
addPluginDirectories()
|
||||
|
||||
# Add unit tests
|
||||
enable_testing()
|
||||
add_subdirectory(tests EXCLUDE_FROM_ALL)
|
||||
if (IMHEX_ENABLE_UNIT_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests EXCLUDE_FROM_ALL)
|
||||
endif ()
|
||||
|
||||
# Configure more resources that will be added to the install package
|
||||
createPackage()
|
||||
generatePDBs()
|
||||
generateSDKDirectory()
|
||||
generateSDKDirectory()
|
||||
|
||||
# Handle package generation
|
||||
createPackage()
|
||||
|
||||
@@ -92,6 +92,8 @@ macro(detectOS)
|
||||
endmacro()
|
||||
|
||||
macro(configurePackingResources)
|
||||
set(LIBRARY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
|
||||
if (WIN32)
|
||||
if (NOT (CMAKE_BUILD_TYPE STREQUAL "Debug"))
|
||||
set(APPLICATION_TYPE WIN32)
|
||||
@@ -145,9 +147,7 @@ macro(configurePackingResources)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(createPackage)
|
||||
set(LIBRARY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
|
||||
macro(addPluginDirectories)
|
||||
file(MAKE_DIRECTORY "plugins")
|
||||
foreach (plugin IN LISTS PLUGINS)
|
||||
add_subdirectory("plugins/${plugin}")
|
||||
@@ -178,9 +178,9 @@ macro(createPackage)
|
||||
add_dependencies(imhex_all ${plugin})
|
||||
endif ()
|
||||
endforeach()
|
||||
endmacro()
|
||||
|
||||
set_target_properties(libimhex PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
macro(createPackage)
|
||||
if (WIN32)
|
||||
# Install binaries directly in the prefix, usually C:\Program Files\ImHex.
|
||||
set(CMAKE_INSTALL_BINDIR ".")
|
||||
@@ -217,7 +217,6 @@ macro(createPackage)
|
||||
endforeach()
|
||||
]])
|
||||
|
||||
install(FILES "$<TARGET_FILE:libimhex>" DESTINATION "${CMAKE_INSTALL_LIBDIR}" PERMISSIONS ${LIBRARY_PERMISSIONS})
|
||||
downloadImHexPatternsFiles("./")
|
||||
elseif(UNIX AND NOT APPLE)
|
||||
|
||||
@@ -228,7 +227,6 @@ macro(createPackage)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE DESTINATION ${CMAKE_INSTALL_PREFIX}/share/licenses/imhex)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dist/imhex.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/icon.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/pixmaps RENAME imhex.png)
|
||||
install(FILES "$<TARGET_FILE:libimhex>" DESTINATION "${CMAKE_INSTALL_LIBDIR}" PERMISSIONS ${LIBRARY_PERMISSIONS})
|
||||
downloadImHexPatternsFiles("./share/imhex")
|
||||
|
||||
# install AppStream file
|
||||
@@ -249,17 +247,15 @@ macro(createPackage)
|
||||
set_property(TARGET main PROPERTY MACOSX_BUNDLE_INFO_PLIST ${MACOSX_BUNDLE_INFO_PLIST})
|
||||
|
||||
# Fix rpath
|
||||
add_custom_command(TARGET imhex_all POST_BUILD COMMAND ${CMAKE_INSTALL_NAME_TOOL} -add_rpath "@executable_path/../Frameworks/" $<TARGET_FILE:main> || true)
|
||||
install(CODE "execute_process(COMMAND ${CMAKE_INSTALL_NAME_TOOL} -add_rpath \"@executable_path/../Frameworks/\" $<TARGET_FILE:main>)")
|
||||
|
||||
add_custom_target(build-time-make-plugins-directory ALL COMMAND ${CMAKE_COMMAND} -E make_directory "${IMHEX_BUNDLE_PATH}/Contents/MacOS/plugins")
|
||||
add_custom_target(build-time-make-resources-directory ALL COMMAND ${CMAKE_COMMAND} -E make_directory "${IMHEX_BUNDLE_PATH}/Contents/Resources")
|
||||
|
||||
downloadImHexPatternsFiles("${IMHEX_BUNDLE_PATH}/Contents/MacOS")
|
||||
downloadImHexPatternsFiles("${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}/Contents/MacOS")
|
||||
|
||||
install(FILES ${IMHEX_ICON} DESTINATION "${IMHEX_BUNDLE_PATH}/Contents/Resources")
|
||||
install(FILES ${IMHEX_ICON} DESTINATION "${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}/Contents/Resources")
|
||||
install(TARGETS main BUNDLE DESTINATION ".")
|
||||
install(FILES $<TARGET_FILE:main> DESTINATION "${IMHEX_BUNDLE_PATH}")
|
||||
install(FILES $<TARGET_FILE:updater> DESTINATION "${IMHEX_BUNDLE_PATH}")
|
||||
|
||||
# Update library references to make the bundle portable
|
||||
postprocess_bundle(imhex_all main)
|
||||
@@ -268,15 +264,17 @@ macro(createPackage)
|
||||
set(CPACK_GENERATOR "DragNDrop")
|
||||
|
||||
set(CPACK_BUNDLE_ICON "${CMAKE_SOURCE_DIR}/resources/dist/macos/AppIcon.icns")
|
||||
set(CPACK_BUNDLE_PLIST "${CMAKE_BINARY_DIR}/${BUNDLE_NAME}/Contents/Info.plist")
|
||||
set(CPACK_BUNDLE_PLIST "${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}/Contents/Info.plist")
|
||||
|
||||
if (IMHEX_RESIGN_BUNDLE)
|
||||
message(STATUS "Resigning bundle...")
|
||||
find_program(CODESIGN_PATH codesign)
|
||||
if (CODESIGN_PATH)
|
||||
add_custom_command(TARGET imhex_all POST_BUILD COMMAND "codesign" ARGS "--force" "--deep" "--sign" "-" "${CMAKE_BINARY_DIR}/${BUNDLE_NAME}")
|
||||
install(CODE "message(STATUS \"Signing bundle '${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}'...\")")
|
||||
install(CODE "execute_process(COMMAND ${CODESIGN_PATH} --force --deep --sign - ${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME} COMMAND_ERROR_IS_FATAL ANY)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(CODE [[ message(STATUS "MacOS Bundle finalized. DO NOT TOUCH IT ANYMORE! ANY MODIFICATIONS WILL BREAK IT FROM NOW ON!") ]])
|
||||
endif()
|
||||
else()
|
||||
install(TARGETS main RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
@@ -660,20 +658,20 @@ macro(addBundledLibraries)
|
||||
if (${Backtrace_FOUND})
|
||||
message(STATUS "Backtrace enabled! Header: ${Backtrace_HEADER}")
|
||||
|
||||
if (Backtrace_HEADER STREQUAL "execinfo.h")
|
||||
if (Backtrace_HEADER STREQUAL "backtrace.h")
|
||||
set(LIBBACKTRACE_LIBRARIES ${Backtrace_LIBRARY})
|
||||
set(LIBBACKTRACE_INCLUDE_DIRS ${Backtrace_INCLUDE_DIR})
|
||||
add_compile_definitions(BACKTRACE_HEADER=\"${Backtrace_HEADER}\")
|
||||
add_compile_definitions(HEX_HAS_EXECINFO)
|
||||
elseif (Backtrace_HEADER STREQUAL "backtrace.h")
|
||||
set(LIBBACKTRACE_LIBRARIES ${Backtrace_LIBRARY})
|
||||
set(LIBBACKTRACE_INCLUDE_DIRS ${Backtrace_INCLUDE_DIR})
|
||||
add_compile_definitions(BACKTRACE_HEADER=\"${Backtrace_HEADER}\")
|
||||
add_compile_definitions(BACKTRACE_HEADER=<${Backtrace_HEADER}>)
|
||||
add_compile_definitions(HEX_HAS_BACKTRACE)
|
||||
endif ()
|
||||
elseif (Backtrace_HEADER STREQUAL "execinfo.h")
|
||||
set(LIBBACKTRACE_LIBRARIES ${Backtrace_LIBRARY})
|
||||
set(LIBBACKTRACE_INCLUDE_DIRS ${Backtrace_INCLUDE_DIR})
|
||||
add_compile_definitions(BACKTRACE_HEADER=<${Backtrace_HEADER}>)
|
||||
add_compile_definitions(HEX_HAS_EXECINFO)
|
||||
endif()
|
||||
endif()
|
||||
endif ()
|
||||
endif ()
|
||||
endif()
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
function(enableUnityBuild TARGET)
|
||||
@@ -739,7 +737,7 @@ function(generateSDKDirectory)
|
||||
if (WIN32)
|
||||
set(SDK_PATH "./sdk")
|
||||
elseif (APPLE)
|
||||
set(SDK_PATH "${BUNDLE_NAME}/Contents/Resources/sdk")
|
||||
set(SDK_PATH "${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}/Contents/Resources/sdk")
|
||||
else()
|
||||
set(SDK_PATH "share/imhex/sdk")
|
||||
endif()
|
||||
|
||||
@@ -14,12 +14,11 @@ if(CMAKE_GENERATOR)
|
||||
# Being called as include(PostprocessBundle), so define a helper function.
|
||||
set(_POSTPROCESS_BUNDLE_MODULE_LOCATION "${CMAKE_CURRENT_LIST_FILE}")
|
||||
function(postprocess_bundle out_target in_target)
|
||||
add_custom_command(TARGET ${out_target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -DBUNDLE_PATH="$<TARGET_FILE_DIR:${in_target}>/../.."
|
||||
-DCODE_SIGN_CERTIFICATE_ID="${CODE_SIGN_CERTIFICATE_ID}"
|
||||
-DEXTRA_BUNDLE_LIBRARY_PATHS="${EXTRA_BUNDLE_LIBRARY_PATHS}"
|
||||
-P "${_POSTPROCESS_BUNDLE_MODULE_LOCATION}"
|
||||
)
|
||||
|
||||
install(CODE "set(BUNDLE_PATH ${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME})")
|
||||
install(CODE "set(CODE_SIGN_CERTIFICATE_ID ${CODE_SIGN_CERTIFICATE_ID})")
|
||||
install(CODE "set(EXTRA_BUNDLE_LIBRARY_PATHS ${EXTRA_BUNDLE_LIBRARY_PATHS})")
|
||||
install(SCRIPT ${_POSTPROCESS_BUNDLE_MODULE_LOCATION})
|
||||
endfunction()
|
||||
return()
|
||||
endif()
|
||||
@@ -42,7 +41,7 @@ file(GLOB_RECURSE plugins "${BUNDLE_PATH}/Contents/MacOS/plugins/*.hexplug")
|
||||
# makes it sometimes break on libraries that do weird things with @rpath. Specify
|
||||
# equivalent search directories until https://gitlab.kitware.com/cmake/cmake/issues/16625
|
||||
# is fixed and in our minimum CMake version.
|
||||
set(extra_dirs "/usr/local/lib" "/lib" "/usr/lib" ${EXTRA_BUNDLE_LIBRARY_PATHS} "${BUNDLE_PATH}/Contents/MacOS/plugins")
|
||||
set(extra_dirs "/usr/local/lib" "/lib" "/usr/lib" ${EXTRA_BUNDLE_LIBRARY_PATHS} "${BUNDLE_PATH}/Contents/MacOS/plugins" "${BUNDLE_PATH}/Contents/Frameworks")
|
||||
message(STATUS "Fixing up application bundle: ${extra_dirs}")
|
||||
|
||||
# BundleUtilities is overly verbose, so disable most of its messages
|
||||
|
||||
13
dist/compiling/docker.md
vendored
13
dist/compiling/docker.md
vendored
@@ -18,11 +18,11 @@ docker buildx build . -f <DOCKERFILE_PATH> --progress plain --build-arg 'JOBS=4'
|
||||
|
||||
where `<DOCKERFILE_PATH>` should be replaced by the wanted Dockerfile base d on the build you want to do:
|
||||
|
||||
| Wanted build | Dockerfile path |
|
||||
|--------------|-----------------------------|
|
||||
| MacOS M1 | dist/macOS/arm64.Dockerfile |
|
||||
| AppImage | dist/appimage/Dockerfile |
|
||||
| Web version | dist/web/Dockerfile |
|
||||
| Wanted build | Dockerfile path | Target |
|
||||
|--------------|-----------------------------|--------|
|
||||
| MacOS M1 | dist/macOS/arm64.Dockerfile | - |
|
||||
| AppImage | dist/appimage/Dockerfile | - |
|
||||
| Web version | dist/web/Dockerfile | raw |
|
||||
|
||||
We'll explain this command in the next section
|
||||
|
||||
@@ -43,6 +43,7 @@ In the command saw earlier:
|
||||
- `.` is the base folder that the Dockerfile will be allowed to see
|
||||
- `-f <path>` is to specify the Dockerfile path
|
||||
- `--progress plain` is to allow you to see the output of instructions
|
||||
- `--build-arg <key>=<value>` is to allow to to specify arguments to the build (like -DKEY=VALUE in CMake)
|
||||
- `--build-arg <key>=<value>` is to allow to specify arguments to the build (like -DKEY=VALUE in CMake)
|
||||
- `--build-context key=<folder>` is to specify folders other than the base folder that the Dockerfile is allowed to see
|
||||
- `--output <path>` is the path to write the output package to. If not specified, Docker will create an image as the output (probably not what you want)
|
||||
- `--target <target>` specifies which docker target to build
|
||||
|
||||
8
dist/compiling/linux.md
vendored
8
dist/compiling/linux.md
vendored
@@ -13,14 +13,8 @@ CC=gcc-12 CXX=g++-12 \
|
||||
cmake -G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="/usr" \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_C_FLAGS="-fuse-ld=lld" \
|
||||
-DCMAKE_CXX_FLAGS="-fuse-ld=lld" \
|
||||
-DCMAKE_OBJC_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_OBJCXX_COMPILER_LAUNCHER=ccache \
|
||||
..
|
||||
make -j 4 install
|
||||
ninja install
|
||||
```
|
||||
|
||||
All paths follow the XDG Base Directories standard, and can thus be modified
|
||||
|
||||
2
dist/compiling/macos.md
vendored
2
dist/compiling/macos.md
vendored
@@ -15,7 +15,7 @@ OBJC=$(brew --prefix llvm)/bin/clang \
|
||||
OBJCXX=$(brew --prefix llvm)/bin/clang++ \
|
||||
cmake -G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=./install \
|
||||
-DCMAKE_INSTALL_PREFIX="./install" \
|
||||
-DIMHEX_GENERATE_PACKAGE=ON \
|
||||
..
|
||||
ninja install
|
||||
|
||||
2
dist/compiling/windows.md
vendored
2
dist/compiling/windows.md
vendored
@@ -14,7 +14,7 @@ mkdir build
|
||||
cd build
|
||||
cmake -G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="$PWD/install" \
|
||||
-DCMAKE_INSTALL_PREFIX="./install" \
|
||||
-DIMHEX_USE_DEFAULT_BUILD_SETTINGS=ON \
|
||||
..
|
||||
ninja install
|
||||
|
||||
2
dist/macOS/arm64.Dockerfile
vendored
2
dist/macOS/arm64.Dockerfile
vendored
@@ -170,4 +170,4 @@ EOF
|
||||
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /mnt/ImHex/build/install/imhex.app ImHex.app
|
||||
COPY --from=build /mnt/ImHex/build/install/imhex.app imhex.app
|
||||
|
||||
6
dist/rpm/imhex.spec
vendored
6
dist/rpm/imhex.spec
vendored
@@ -1,5 +1,5 @@
|
||||
Name: imhex
|
||||
Version: 1.26.2
|
||||
Version: VERSION
|
||||
Release: 0%{?dist}
|
||||
Summary: A hex editor for reverse engineers and programmers
|
||||
|
||||
@@ -95,10 +95,6 @@ CXXFLAGS+=" -std=gnu++2b"
|
||||
%set_build_flags
|
||||
CXXFLAGS+=" -std=gnu++2b"
|
||||
%endif
|
||||
# build binaries required for tests
|
||||
%cmake_build --target unit_tests
|
||||
%ctest --exclude-regex '(Helpers/StoreAPI|Helpers/TipsAPI|Helpers/ContentAPI)'
|
||||
# Helpers/*API exclude tests that require network access
|
||||
|
||||
|
||||
%install
|
||||
|
||||
2
lib/external/libromfs
vendored
2
lib/external/libromfs
vendored
Submodule lib/external/libromfs updated: 61f7e412dd...03adcfdde0
2
lib/external/libwolv
vendored
2
lib/external/libwolv
vendored
Submodule lib/external/libwolv updated: 7806c1939d...6b4a9c7ddd
2
lib/external/pattern_language
vendored
2
lib/external/pattern_language
vendored
Submodule lib/external/pattern_language updated: d4648c4a59...7aa467c8be
@@ -38,6 +38,8 @@ set(LIBIMHEX_SOURCES
|
||||
source/helpers/tar.cpp
|
||||
source/helpers/debugging.cpp
|
||||
|
||||
source/test/tests.cpp
|
||||
|
||||
source/providers/provider.cpp
|
||||
source/providers/memory_provider.cpp
|
||||
source/providers/undo/stack.cpp
|
||||
@@ -145,4 +147,7 @@ target_link_libraries(libimhex ${LIBIMHEX_LIBRARY_TYPE} ${NLOHMANN_JSON_LIBRARIE
|
||||
|
||||
set_property(TARGET libimhex PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE)
|
||||
|
||||
add_dependencies(imhex_all libimhex)
|
||||
add_dependencies(imhex_all libimhex)
|
||||
|
||||
install(FILES "$<TARGET_FILE:libimhex>" DESTINATION "${CMAKE_INSTALL_LIBDIR}" PERMISSIONS ${LIBRARY_PERMISSIONS})
|
||||
set_target_properties(libimhex PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
@@ -931,7 +931,7 @@ namespace hex {
|
||||
|
||||
void addProviderName(const UnlocalizedString &unlocalizedName);
|
||||
|
||||
using ProviderCreationFunction = std::unique_ptr<prv::Provider>(*)();
|
||||
using ProviderCreationFunction = std::function<std::unique_ptr<prv::Provider>()>;
|
||||
void add(const std::string &typeName, ProviderCreationFunction creationFunction);
|
||||
|
||||
const std::vector<std::string>& getEntries();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <hex.hpp>
|
||||
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <string_view>
|
||||
#include <functional>
|
||||
|
||||
@@ -664,6 +664,12 @@ namespace hex {
|
||||
*/
|
||||
std::optional<InitialWindowProperties> getInitialWindowProperties();
|
||||
|
||||
/**
|
||||
* @brief Gets the module handle of libimhex
|
||||
* @return Module handle
|
||||
*/
|
||||
void* getLibImHexModuleHandle();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,7 +46,13 @@ namespace hex {
|
||||
* @brief Get a list of all layouts
|
||||
* @return List of all added layouts
|
||||
*/
|
||||
static std::vector<Layout> getLayouts();
|
||||
static const std::vector<Layout> &getLayouts();
|
||||
|
||||
/**
|
||||
* @brief Removes the layout with the given name
|
||||
* @param name Name of the layout
|
||||
*/
|
||||
static void removeLayout(const std::string &name);
|
||||
|
||||
/**
|
||||
* @brief Handles loading of layouts if needed
|
||||
|
||||
@@ -15,8 +15,10 @@ struct ImGuiContext;
|
||||
namespace hex {
|
||||
|
||||
struct SubCommand {
|
||||
std::string commandKey;
|
||||
std::string commandDesc;
|
||||
std::string commandLong;
|
||||
std::string commandShort;
|
||||
|
||||
std::string commandDescription;
|
||||
std::function<void(const std::vector<std::string>&)> callback;
|
||||
};
|
||||
|
||||
@@ -104,6 +106,10 @@ namespace hex {
|
||||
|
||||
static bool load();
|
||||
static bool load(const std::fs::path &pluginFolder);
|
||||
|
||||
static bool loadLibraries();
|
||||
static bool loadLibraries(const std::fs::path &libraryFolder);
|
||||
|
||||
static void unload();
|
||||
static void reload();
|
||||
static void initializeNewPlugins();
|
||||
@@ -111,6 +117,7 @@ namespace hex {
|
||||
|
||||
static void addPlugin(const std::string &name, PluginFunctions functions);
|
||||
|
||||
static Plugin* getPlugin(const std::string &name);
|
||||
static const std::list<Plugin>& getPlugins();
|
||||
static const std::vector<std::fs::path>& getPluginPaths();
|
||||
static const std::vector<std::fs::path>& getPluginLoadPaths();
|
||||
@@ -121,6 +128,7 @@ namespace hex {
|
||||
static std::list<Plugin>& getPluginsMutable();
|
||||
|
||||
static AutoReset<std::vector<std::fs::path>> s_pluginPaths, s_pluginLoadPaths;
|
||||
static AutoReset<std::vector<uintptr_t>> s_loadedLibraries;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -13,18 +13,22 @@ namespace hex {
|
||||
struct Workspace {
|
||||
std::string layout;
|
||||
std::fs::path path;
|
||||
bool builtin;
|
||||
};
|
||||
|
||||
static void createWorkspace(const std::string &name, const std::string &layout = "");
|
||||
static void switchWorkspace(const std::string &name);
|
||||
|
||||
static void importFromFile(const std::fs::path &path);
|
||||
static bool exportToFile(std::fs::path path = {}, std::string workspaceName = {});
|
||||
static bool exportToFile(std::fs::path path = {}, std::string workspaceName = {}, bool builtin = false);
|
||||
|
||||
static void removeWorkspace(const std::string &name);
|
||||
|
||||
static const auto& getWorkspaces() { return *s_workspaces; }
|
||||
static const auto& getCurrentWorkspace() { return s_currentWorkspace; }
|
||||
|
||||
static void reset();
|
||||
static void reload();
|
||||
|
||||
static void process();
|
||||
|
||||
@@ -32,7 +36,7 @@ namespace hex {
|
||||
WorkspaceManager() = default;
|
||||
|
||||
static AutoReset<std::map<std::string, Workspace>> s_workspaces;
|
||||
static decltype(s_workspaces)::Type::iterator s_currentWorkspace, s_previousWorkspace;
|
||||
static decltype(s_workspaces)::Type::iterator s_currentWorkspace, s_previousWorkspace, s_workspaceToRemove;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <fmt/color.h>
|
||||
|
||||
#include <wolv/io/file.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
|
||||
namespace hex::log {
|
||||
|
||||
@@ -21,6 +22,7 @@ namespace hex::log {
|
||||
|
||||
[[nodiscard]] std::recursive_mutex& getLoggerMutex();
|
||||
[[nodiscard]] bool isLoggingSuspended();
|
||||
[[nodiscard]] bool isDebugLoggingEnabled();
|
||||
|
||||
struct LogEntry {
|
||||
std::string project;
|
||||
@@ -65,13 +67,14 @@ namespace hex::log {
|
||||
|
||||
void suspendLogging();
|
||||
void resumeLogging();
|
||||
void enableDebugLogging();
|
||||
|
||||
[[maybe_unused]] void debug(const std::string &fmt, auto && ... args) {
|
||||
#if defined(DEBUG)
|
||||
if (impl::isDebugLoggingEnabled()) [[unlikely]] {
|
||||
hex::log::impl::print(fg(impl::color::debug()) | fmt::emphasis::bold, "[DEBUG]", fmt, args...);
|
||||
#else
|
||||
} else {
|
||||
impl::addLogEntry(IMHEX_PROJECT_NAME, "[DEBUG]", fmt::format(fmt::runtime(fmt), args...));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]] void info(const std::string &fmt, auto && ... args) {
|
||||
@@ -90,7 +93,6 @@ namespace hex::log {
|
||||
hex::log::impl::print(fg(impl::color::fatal()) | fmt::emphasis::bold, "[FATAL]", fmt, args...);
|
||||
}
|
||||
|
||||
|
||||
[[maybe_unused]] void print(const std::string &fmt, auto && ... args) {
|
||||
std::scoped_lock lock(impl::getLoggerMutex());
|
||||
|
||||
|
||||
@@ -331,4 +331,14 @@ namespace hex {
|
||||
|
||||
[[nodiscard]] std::string formatSystemError(i32 error);
|
||||
|
||||
/**
|
||||
* Gets the shared library handle for a given pointer
|
||||
* @param symbol Pointer to any function or variable in the shared library
|
||||
* @return The module handle
|
||||
* @warning Important! Calling this function on functions defined in other modules will return the handle of the current module!
|
||||
* This is because you're not actually passing a pointer to the function in the other module but rather a pointer to a thunk
|
||||
* that is defined in the current module.
|
||||
*/
|
||||
[[nodiscard]] void* getContainingModule(void* symbol);
|
||||
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@ namespace hex::prv {
|
||||
[[nodiscard]] u64 getActualSize() const override { return m_data.size(); }
|
||||
|
||||
void resizeRaw(u64 newSize) override;
|
||||
void insertRaw(u64 offset, u64 size) override;
|
||||
void removeRaw(u64 offset, u64 size) override;
|
||||
|
||||
[[nodiscard]] std::string getName() const override { return m_name; }
|
||||
|
||||
|
||||
@@ -161,13 +161,13 @@ namespace hex::prv {
|
||||
*/
|
||||
[[nodiscard]] virtual std::string getName() const = 0;
|
||||
|
||||
void resize(u64 newSize);
|
||||
bool resize(u64 newSize);
|
||||
void insert(u64 offset, u64 size);
|
||||
void remove(u64 offset, u64 size);
|
||||
|
||||
virtual void resizeRaw(u64 newSize) { hex::unused(newSize); }
|
||||
virtual void insertRaw(u64 offset, u64 size) { hex::unused(offset, size); }
|
||||
virtual void removeRaw(u64 offset, u64 size) { hex::unused(offset, size); }
|
||||
virtual void insertRaw(u64 offset, u64 size);
|
||||
virtual void removeRaw(u64 offset, u64 size);
|
||||
|
||||
virtual void save();
|
||||
virtual void saveAs(const std::fs::path &path);
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace hex::prv::undo {
|
||||
|
||||
void groupOperations(u32 count, const UnlocalizedString &unlocalizedName);
|
||||
void apply(const Stack &otherStack);
|
||||
void reapply();
|
||||
|
||||
[[nodiscard]] bool canUndo() const;
|
||||
[[nodiscard]] bool canRedo() const;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
#include <hex/api/plugin_manager.hpp>
|
||||
|
||||
#include <wolv/utils/preproc.hpp>
|
||||
|
||||
@@ -28,16 +29,20 @@
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define INIT_PLUGIN(name) \
|
||||
if (!hex::test::initPluginImpl(name)) TEST_FAIL();
|
||||
|
||||
namespace hex::test {
|
||||
|
||||
using Function = int(*)();
|
||||
struct Test {
|
||||
std::function<int()> function;
|
||||
Function function;
|
||||
bool shouldFail;
|
||||
};
|
||||
|
||||
class Tests {
|
||||
public:
|
||||
static auto addTest(const std::string &name, const std::function<int()> &func, bool shouldFail) noexcept {
|
||||
static auto addTest(const std::string &name, Function func, bool shouldFail) noexcept {
|
||||
s_tests.insert({
|
||||
name, {func, shouldFail}
|
||||
});
|
||||
@@ -50,7 +55,7 @@ namespace hex::test {
|
||||
}
|
||||
|
||||
private:
|
||||
static inline std::map<std::string, Test> s_tests;
|
||||
static std::map<std::string, Test> s_tests;
|
||||
};
|
||||
|
||||
template<class F>
|
||||
@@ -86,4 +91,5 @@ namespace hex::test {
|
||||
return TestSequence<F>(executor.getName(), std::forward<F>(f), executor.shouldFail());
|
||||
}
|
||||
|
||||
bool initPluginImpl(std::string name);
|
||||
}
|
||||
@@ -75,7 +75,11 @@ namespace hex {
|
||||
|
||||
static AutoReset<std::optional<ProviderRegion>> s_currentSelection;
|
||||
void setCurrentSelection(const std::optional<ProviderRegion> ®ion) {
|
||||
*s_currentSelection = region;
|
||||
if (region == Region::Invalid()) {
|
||||
clearSelection();
|
||||
} else {
|
||||
*s_currentSelection = region;
|
||||
}
|
||||
}
|
||||
|
||||
static PerProvider<std::optional<Region>> s_hoveredRegion;
|
||||
@@ -599,6 +603,11 @@ namespace hex {
|
||||
return impl::s_initialWindowProperties;
|
||||
}
|
||||
|
||||
void* getLibImHexModuleHandle() {
|
||||
return hex::getContainingModule((void*)&getLibImHexModuleHandle);
|
||||
}
|
||||
|
||||
|
||||
const std::map<std::string, std::string>& getInitArguments() {
|
||||
return *impl::s_initArguments;
|
||||
}
|
||||
|
||||
@@ -64,10 +64,25 @@ namespace hex {
|
||||
}
|
||||
|
||||
|
||||
std::vector<LayoutManager::Layout> LayoutManager::getLayouts() {
|
||||
const std::vector<LayoutManager::Layout>& LayoutManager::getLayouts() {
|
||||
return s_layouts;
|
||||
}
|
||||
|
||||
void LayoutManager::removeLayout(const std::string& name) {
|
||||
for (const auto &layout : *s_layouts) {
|
||||
if (layout.name == name) {
|
||||
if (wolv::io::File(layout.path, wolv::io::File::Mode::Write).remove()) {
|
||||
log::info("Removed layout '{}'", name);
|
||||
LayoutManager::reload();
|
||||
} else {
|
||||
log::error("Failed to remove layout '{}'", name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LayoutManager::closeAllViews() {
|
||||
for (const auto &[name, view] : ContentRegistry::Views::impl::getEntries())
|
||||
view->getWindowOpenState() = false;
|
||||
|
||||
@@ -18,24 +18,50 @@
|
||||
|
||||
namespace hex {
|
||||
|
||||
static uintptr_t loadLibrary(const std::fs::path &path) {
|
||||
#if defined(OS_WINDOWS)
|
||||
auto handle = uintptr_t(LoadLibraryW(path.c_str()));
|
||||
|
||||
if (handle == uintptr_t(INVALID_HANDLE_VALUE) || handle == 0) {
|
||||
log::error("Loading library '{}' failed: {} {}!", wolv::util::toUTF8String(path.filename()), ::GetLastError(), hex::formatSystemError(::GetLastError()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return handle;
|
||||
#else
|
||||
auto handle = uintptr_t(dlopen(wolv::util::toUTF8String(path).c_str(), RTLD_LAZY));
|
||||
|
||||
if (handle == 0) {
|
||||
log::error("Loading library '{}' failed: {}!", wolv::util::toUTF8String(path.filename()), dlerror());
|
||||
return 0;
|
||||
}
|
||||
|
||||
return handle;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void unloadLibrary(uintptr_t handle, const std::fs::path &path) {
|
||||
#if defined(OS_WINDOWS)
|
||||
if (handle != 0) {
|
||||
if (FreeLibrary(HMODULE(handle)) == FALSE) {
|
||||
log::error("Error when unloading library '{}': {}!", wolv::util::toUTF8String(path.filename()), hex::formatSystemError(::GetLastError()));
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (handle != 0) {
|
||||
if (dlclose(reinterpret_cast<void*>(handle)) != 0) {
|
||||
log::error("Error when unloading library '{}': {}!", path.filename().string(), dlerror());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Plugin::Plugin(const std::fs::path &path) : m_path(path) {
|
||||
log::info("Loading plugin '{}'", wolv::util::toUTF8String(path.filename()));
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
m_handle = uintptr_t(LoadLibraryW(path.c_str()));
|
||||
|
||||
if (m_handle == uintptr_t(INVALID_HANDLE_VALUE) || m_handle == 0) {
|
||||
log::error("Loading plugin '{}' failed: {} {}!", wolv::util::toUTF8String(path.filename()), ::GetLastError(), hex::formatSystemError(::GetLastError()));
|
||||
return;
|
||||
}
|
||||
#else
|
||||
m_handle = uintptr_t(dlopen(wolv::util::toUTF8String(path).c_str(), RTLD_LAZY));
|
||||
|
||||
if (m_handle == 0) {
|
||||
log::error("Loading plugin '{}' failed: {}!", wolv::util::toUTF8String(path.filename()), dlerror());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
m_handle = loadLibrary(path);
|
||||
if (m_handle == 0)
|
||||
return;
|
||||
|
||||
const auto fileName = path.stem().string();
|
||||
|
||||
@@ -89,15 +115,7 @@ namespace hex {
|
||||
log::info("Trying to unload plugin '{}'", getPluginName());
|
||||
}
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
if (m_handle != 0)
|
||||
if (FreeLibrary(HMODULE(m_handle)) == FALSE) {
|
||||
log::error("Error when unloading plugin '{}': {}!", wolv::util::toUTF8String(m_path.filename()), hex::formatSystemError(::GetLastError()));
|
||||
}
|
||||
#else
|
||||
if (m_handle != 0)
|
||||
dlclose(reinterpret_cast<void*>(m_handle));
|
||||
#endif
|
||||
unloadLibrary(m_handle, m_path);
|
||||
}
|
||||
|
||||
bool Plugin::initializePlugin() const {
|
||||
@@ -284,6 +302,35 @@ namespace hex {
|
||||
return true;
|
||||
}
|
||||
|
||||
AutoReset<std::vector<uintptr_t>> PluginManager::s_loadedLibraries;
|
||||
|
||||
bool PluginManager::loadLibraries() {
|
||||
bool success = true;
|
||||
for (const auto &loadPath : fs::getDefaultPaths(fs::ImHexPath::Libraries))
|
||||
success = PluginManager::loadLibraries(loadPath) && success;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool PluginManager::loadLibraries(const std::fs::path& libraryFolder) {
|
||||
bool success = true;
|
||||
for (const auto &entry : std::fs::directory_iterator(libraryFolder)) {
|
||||
if (!(entry.path().extension() == ".dll" || entry.path().extension() == ".so" || entry.path().extension() == ".dylib"))
|
||||
continue;
|
||||
|
||||
auto handle = loadLibrary(entry);
|
||||
if (handle == 0) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
PluginManager::s_loadedLibraries->push_back(handle);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void PluginManager::initializeNewPlugins() {
|
||||
for (const auto &plugin : getPlugins()) {
|
||||
if (!plugin.isLoaded())
|
||||
@@ -304,6 +351,11 @@ namespace hex {
|
||||
plugins.pop_back();
|
||||
}
|
||||
|
||||
while (!s_loadedLibraries->empty()) {
|
||||
unloadLibrary(s_loadedLibraries->back(), "");
|
||||
s_loadedLibraries->pop_back();
|
||||
}
|
||||
|
||||
getPluginsMutable() = std::move(savedPlugins);
|
||||
}
|
||||
|
||||
@@ -321,6 +373,15 @@ namespace hex {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
Plugin* PluginManager::getPlugin(const std::string &name) {
|
||||
for (auto &plugin : getPluginsMutable()) {
|
||||
if (plugin.getPluginName() == name)
|
||||
return &plugin;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::vector<std::fs::path>& PluginManager::getPluginPaths() {
|
||||
return s_pluginPaths;
|
||||
}
|
||||
@@ -335,5 +396,4 @@ namespace hex {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -9,22 +9,28 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wolv/utils/string.hpp>
|
||||
|
||||
namespace hex {
|
||||
|
||||
AutoReset<std::map<std::string, WorkspaceManager::Workspace>> WorkspaceManager::s_workspaces;
|
||||
decltype(WorkspaceManager::s_workspaces)::Type::iterator WorkspaceManager::s_currentWorkspace = s_workspaces->end();
|
||||
decltype(WorkspaceManager::s_workspaces)::Type::iterator WorkspaceManager::s_previousWorkspace = s_workspaces->end();
|
||||
decltype(WorkspaceManager::s_workspaces)::Type::iterator WorkspaceManager::s_workspaceToRemove = s_workspaces->end();
|
||||
|
||||
void WorkspaceManager::createWorkspace(const std::string& name, const std::string &layout) {
|
||||
s_currentWorkspace = s_workspaces->insert_or_assign(name, Workspace {
|
||||
.layout = layout.empty() ? LayoutManager::saveToString() : layout,
|
||||
.path = {}
|
||||
.layout = layout.empty() ? LayoutManager::saveToString() : layout,
|
||||
.path = {},
|
||||
.builtin = false
|
||||
}).first;
|
||||
|
||||
for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Workspaces)) {
|
||||
if (exportToFile(path / (name + ".hexws")))
|
||||
for (const auto &workspaceFolder : fs::getDefaultPaths(fs::ImHexPath::Workspaces)) {
|
||||
const auto workspacePath = workspaceFolder / (name + ".hexws");
|
||||
if (exportToFile(workspacePath)) {
|
||||
s_currentWorkspace->second.path = workspacePath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +43,9 @@ namespace hex {
|
||||
}
|
||||
|
||||
void WorkspaceManager::importFromFile(const std::fs::path& path) {
|
||||
if (std::ranges::any_of(*s_workspaces, [path](const auto &pair) { return pair.second.path == path; }))
|
||||
return;
|
||||
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Read);
|
||||
if (!file.isValid()) {
|
||||
log::error("Failed to load workspace from file '{}'", path.string());
|
||||
@@ -50,10 +59,12 @@ namespace hex {
|
||||
|
||||
const std::string name = json["name"];
|
||||
std::string layout = json["layout"];
|
||||
const bool builtin = json.value("builtin", false);
|
||||
|
||||
(*s_workspaces)[name] = Workspace {
|
||||
.layout = std::move(layout),
|
||||
.path = path
|
||||
.path = path,
|
||||
.builtin = builtin
|
||||
};
|
||||
} catch (nlohmann::json::exception &e) {
|
||||
log::error("Failed to load workspace from file '{}': {}", path.string(), e.what());
|
||||
@@ -61,7 +72,7 @@ namespace hex {
|
||||
}
|
||||
}
|
||||
|
||||
bool WorkspaceManager::exportToFile(std::fs::path path, std::string workspaceName) {
|
||||
bool WorkspaceManager::exportToFile(std::fs::path path, std::string workspaceName, bool builtin) {
|
||||
if (path.empty()) {
|
||||
if (s_currentWorkspace == s_workspaces->end())
|
||||
return false;
|
||||
@@ -80,22 +91,46 @@ namespace hex {
|
||||
nlohmann::json json;
|
||||
json["name"] = workspaceName;
|
||||
json["layout"] = LayoutManager::saveToString();
|
||||
json["builtin"] = builtin;
|
||||
|
||||
file.writeString(json.dump(4));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WorkspaceManager::removeWorkspace(const std::string& name) {
|
||||
for (const auto &[workspaceName, workspace] : *s_workspaces) {
|
||||
if (workspaceName == name) {
|
||||
log::info("{}", wolv::util::toUTF8String(workspace.path));
|
||||
if (wolv::io::File(workspace.path, wolv::io::File::Mode::Write).remove()) {
|
||||
log::info("Removed workspace '{}'", name);
|
||||
|
||||
switchWorkspace(s_workspaces->begin()->first);
|
||||
s_workspaces->erase(workspaceName);
|
||||
} else {
|
||||
log::error("Failed to remove workspace '{}'", name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WorkspaceManager::process() {
|
||||
if (s_previousWorkspace != s_currentWorkspace) {
|
||||
log::info("Updating workspace");
|
||||
if (s_previousWorkspace != s_workspaces->end())
|
||||
exportToFile(s_previousWorkspace->second.path, s_previousWorkspace->first);
|
||||
exportToFile(s_previousWorkspace->second.path, s_previousWorkspace->first, s_previousWorkspace->second.builtin);
|
||||
|
||||
LayoutManager::closeAllViews();
|
||||
ImGui::LoadIniSettingsFromMemory(s_currentWorkspace->second.layout.c_str());
|
||||
|
||||
s_previousWorkspace = s_currentWorkspace;
|
||||
|
||||
if (s_workspaceToRemove != s_workspaces->end()) {
|
||||
s_workspaces->erase(s_workspaceToRemove);
|
||||
s_workspaceToRemove = s_workspaces->end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +141,24 @@ namespace hex {
|
||||
s_previousWorkspace = s_workspaces->end();
|
||||
}
|
||||
|
||||
void WorkspaceManager::reload() {
|
||||
WorkspaceManager::reset();
|
||||
|
||||
for (const auto &defaultPath : fs::getDefaultPaths(fs::ImHexPath::Workspaces)) {
|
||||
for (const auto &entry : std::fs::directory_iterator(defaultPath)) {
|
||||
if (!entry.is_regular_file())
|
||||
continue;
|
||||
|
||||
const auto &path = entry.path();
|
||||
if (path.extension() != ".hexws")
|
||||
continue;
|
||||
|
||||
WorkspaceManager::importFromFile(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <hex/helpers/logger.hpp>
|
||||
|
||||
#include <hex/api/event_manager.hpp>
|
||||
#include <hex/helpers/fs.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
|
||||
@@ -20,6 +22,7 @@ namespace hex::log {
|
||||
bool s_colorOutputEnabled = false;
|
||||
std::recursive_mutex s_loggerMutex;
|
||||
bool s_loggingSuspended = false;
|
||||
bool s_debugLoggingEnabled = false;
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +34,10 @@ namespace hex::log {
|
||||
s_loggingSuspended = false;
|
||||
}
|
||||
|
||||
void enableDebugLogging() {
|
||||
s_debugLoggingEnabled = true;
|
||||
}
|
||||
|
||||
namespace impl {
|
||||
|
||||
std::recursive_mutex& getLoggerMutex() {
|
||||
@@ -41,6 +48,14 @@ namespace hex::log {
|
||||
return s_loggingSuspended;
|
||||
}
|
||||
|
||||
bool isDebugLoggingEnabled() {
|
||||
#if defined(DEBUG)
|
||||
return true;
|
||||
#else
|
||||
return s_debugLoggingEnabled;
|
||||
#endif
|
||||
}
|
||||
|
||||
FILE *getDestination() {
|
||||
if (s_loggerFile.isValid())
|
||||
return s_loggerFile.getHandle();
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
#include <wolv/utils/guards.hpp>
|
||||
#elif defined(OS_LINUX)
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <hex/helpers/utils_linux.hpp>
|
||||
#elif defined(OS_MACOS)
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <hex/helpers/utils_macos.hpp>
|
||||
#elif defined(OS_WEB)
|
||||
#include "emscripten.h"
|
||||
@@ -784,4 +786,24 @@ namespace hex {
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void* getContainingModule(void* symbol) {
|
||||
#if defined(OS_WINDOWS)
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
if (VirtualQuery(symbol, &mbi, sizeof(mbi)))
|
||||
return mbi.AllocationBase;
|
||||
|
||||
return nullptr;
|
||||
#elif !defined(OS_WEB)
|
||||
Dl_info info = {};
|
||||
if (dladdr(symbol, &info) == 0)
|
||||
return nullptr;
|
||||
|
||||
return dlopen(info.dli_fname, RTLD_LAZY);
|
||||
#else
|
||||
hex::unused(symbol);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,41 +31,4 @@ namespace hex::prv {
|
||||
m_data.resize(newSize);
|
||||
}
|
||||
|
||||
void MemoryProvider::insertRaw(u64 offset, u64 size) {
|
||||
auto oldSize = this->getActualSize();
|
||||
this->resizeRaw(oldSize + size);
|
||||
|
||||
std::vector<u8> buffer(0x1000);
|
||||
const std::vector<u8> zeroBuffer(0x1000);
|
||||
|
||||
auto position = oldSize;
|
||||
while (position > offset) {
|
||||
const auto readSize = std::min<size_t>(position - offset, buffer.size());
|
||||
|
||||
position -= readSize;
|
||||
|
||||
this->readRaw(position, buffer.data(), readSize);
|
||||
this->writeRaw(position, zeroBuffer.data(), readSize);
|
||||
this->writeRaw(position + size, buffer.data(), readSize);
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryProvider::removeRaw(u64 offset, u64 size) {
|
||||
auto oldSize = this->getActualSize();
|
||||
std::vector<u8> buffer(0x1000);
|
||||
|
||||
const auto newSize = oldSize - size;
|
||||
auto position = offset;
|
||||
while (position < newSize) {
|
||||
const auto readSize = std::min<size_t>(newSize - position, buffer.size());
|
||||
|
||||
this->readRaw(position + size, buffer.data(), readSize);
|
||||
this->writeRaw(position, buffer.data(), readSize);
|
||||
|
||||
position += readSize;
|
||||
}
|
||||
|
||||
this->resizeRaw(oldSize - size);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -74,7 +74,11 @@ namespace hex::prv {
|
||||
}
|
||||
}
|
||||
|
||||
void Provider::resize(u64 newSize) {
|
||||
bool Provider::resize(u64 newSize) {
|
||||
if (newSize >> 63) {
|
||||
log::error("new provider size is very large ({}). Is it a negative number ?", newSize);
|
||||
return false;
|
||||
}
|
||||
i64 difference = newSize - this->getActualSize();
|
||||
|
||||
if (difference > 0)
|
||||
@@ -83,6 +87,7 @@ namespace hex::prv {
|
||||
EventProviderDataRemoved::post(this, this->getActualSize() + difference, -difference);
|
||||
|
||||
this->markDirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Provider::insert(u64 offset, u64 size) {
|
||||
@@ -97,6 +102,52 @@ namespace hex::prv {
|
||||
this->markDirty();
|
||||
}
|
||||
|
||||
void Provider::insertRaw(u64 offset, u64 size) {
|
||||
auto oldSize = this->getActualSize();
|
||||
this->resizeRaw(oldSize + size);
|
||||
|
||||
std::vector<u8> buffer(0x1000);
|
||||
const std::vector<u8> zeroBuffer(0x1000);
|
||||
|
||||
auto position = oldSize;
|
||||
while (position > offset) {
|
||||
const auto readSize = std::min<size_t>(position - offset, buffer.size());
|
||||
|
||||
position -= readSize;
|
||||
|
||||
this->readRaw(position, buffer.data(), readSize);
|
||||
this->writeRaw(position, zeroBuffer.data(), readSize);
|
||||
this->writeRaw(position + size, buffer.data(), readSize);
|
||||
}
|
||||
}
|
||||
|
||||
void Provider::removeRaw(u64 offset, u64 size) {
|
||||
if (offset > this->getActualSize() || size == 0)
|
||||
return;
|
||||
|
||||
if ((offset + size) > this->getActualSize())
|
||||
size = this->getActualSize() - offset;
|
||||
|
||||
auto oldSize = this->getActualSize();
|
||||
|
||||
std::vector<u8> buffer(0x1000);
|
||||
|
||||
const auto newSize = oldSize - size;
|
||||
auto position = offset;
|
||||
while (position < newSize) {
|
||||
const auto readSize = std::min<size_t>(newSize - position, buffer.size());
|
||||
|
||||
this->readRaw(position + size, buffer.data(), readSize);
|
||||
this->writeRaw(position, buffer.data(), readSize);
|
||||
|
||||
position += readSize;
|
||||
}
|
||||
|
||||
this->resizeRaw(newSize);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Provider::applyOverlays(u64 offset, void *buffer, size_t size) const {
|
||||
for (auto &overlay : m_overlays) {
|
||||
auto overlayOffset = overlay->getAddress();
|
||||
|
||||
@@ -93,6 +93,13 @@ namespace hex::prv::undo {
|
||||
}
|
||||
}
|
||||
|
||||
void Stack::reapply() {
|
||||
for (const auto &operation : m_undoStack) {
|
||||
operation->redo(m_provider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool Stack::add(std::unique_ptr<Operation> &&operation) {
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace hex::subcommands {
|
||||
std::optional<SubCommand> findSubCommand(const std::string &arg) {
|
||||
for (auto &plugin : PluginManager::getPlugins()) {
|
||||
for (auto &subCommand : plugin.getSubCommands()) {
|
||||
if (hex::format("--{}", subCommand.commandKey) == arg) {
|
||||
if (hex::format("--{}", subCommand.commandLong) == arg || hex::format("-{}", subCommand.commandShort) == arg) {
|
||||
return subCommand;
|
||||
}
|
||||
}
|
||||
@@ -112,8 +112,8 @@ namespace hex::subcommands {
|
||||
|
||||
std::vector<std::string> args;
|
||||
|
||||
for (const auto &arg_view : std::views::split(string, char(0x00))) {
|
||||
std::string arg(arg_view.data(), arg_view.size());
|
||||
for (const auto &argument : std::views::split(string, char(0x00))) {
|
||||
std::string arg(argument.data(), argument.size());
|
||||
args.push_back(arg);
|
||||
}
|
||||
|
||||
|
||||
23
lib/libimhex/source/test/tests.cpp
Normal file
23
lib/libimhex/source/test/tests.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <hex/test/tests.hpp>
|
||||
|
||||
namespace hex::test {
|
||||
std::map<std::string, Test> Tests::s_tests;
|
||||
|
||||
bool initPluginImpl(std::string name) {
|
||||
if (name != "Built-in") {
|
||||
if(!initPluginImpl("Built-in")) return false;
|
||||
}
|
||||
|
||||
hex::Plugin *plugin = hex::PluginManager::getPlugin(name);
|
||||
if (plugin == nullptr) {
|
||||
hex::log::fatal("Plugin '{}' was not found !", name);
|
||||
return false;
|
||||
}else if (!plugin->initializePlugin()) {
|
||||
hex::log::fatal("Failed to initialize plugin '{}' !", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
hex::log::info("Initialized plugin '{}' successfully", name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <imgui_internal.h>
|
||||
#include <implot.h>
|
||||
#include <implot_internal.h>
|
||||
#include <cimgui.h>
|
||||
#include <opengl_support.h>
|
||||
|
||||
#undef IMGUI_DEFINE_MATH_OPERATORS
|
||||
|
||||
3
lib/third_party/imgui/CMakeLists.txt
vendored
3
lib/third_party/imgui/CMakeLists.txt
vendored
@@ -6,9 +6,10 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
add_library(imgui_all_includes INTERFACE)
|
||||
|
||||
add_subdirectory(imgui)
|
||||
add_subdirectory(cimgui)
|
||||
add_subdirectory(implot)
|
||||
add_subdirectory(imnodes)
|
||||
add_subdirectory(custom)
|
||||
add_subdirectory(ColorTextEditor)
|
||||
|
||||
set(IMGUI_LIBRARIES imgui_imgui imgui_implot imgui_imnodes imgui_custom imgui_color_text_editor PARENT_SCOPE)
|
||||
set(IMGUI_LIBRARIES imgui_imgui imgui_cimgui imgui_implot imgui_imnodes imgui_custom imgui_color_text_editor PARENT_SCOPE)
|
||||
@@ -175,7 +175,7 @@ public:
|
||||
bool mCaseSensitive;
|
||||
|
||||
LanguageDefinition()
|
||||
: mGlobalDocComment("/*!"), mDocComment("/**"), mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true)
|
||||
: mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -604,7 +604,7 @@ ImU32 TextEditor::GetGlyphColor(const Glyph &aGlyph) const {
|
||||
return mPalette[(int)PaletteIndex::Comment];
|
||||
if (aGlyph.mMultiLineComment)
|
||||
return mPalette[(int)PaletteIndex::MultiLineComment];
|
||||
if (aGlyph.mDeactivated && !aGlyph.mPreprocessor)
|
||||
if (aGlyph.mDeactivated)
|
||||
return mPalette[(int)PaletteIndex::PreprocessorDeactivated];
|
||||
auto const color = mPalette[(int)aGlyph.mColorIndex];
|
||||
if (aGlyph.mPreprocessor) {
|
||||
@@ -619,10 +619,20 @@ ImU32 TextEditor::GetGlyphColor(const Glyph &aGlyph) const {
|
||||
}
|
||||
|
||||
void TextEditor::HandleKeyboardInputs() {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
auto shift = io.KeyShift;
|
||||
auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
|
||||
auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
auto shift = io.KeyShift;
|
||||
auto left = ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow));
|
||||
auto right = ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow));
|
||||
auto up = ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow));
|
||||
auto down = ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow));
|
||||
auto ctrl = io.ConfigMacOSXBehaviors ? io.KeyAlt : io.KeyCtrl;
|
||||
auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
|
||||
auto home = io.ConfigMacOSXBehaviors ? io.KeySuper && left : ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home));
|
||||
auto end = io.ConfigMacOSXBehaviors ? io.KeySuper && right : ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End));
|
||||
auto top = io.ConfigMacOSXBehaviors ? io.KeySuper && up : ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home));
|
||||
auto bottom = io.ConfigMacOSXBehaviors ? io.KeySuper && down : ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End));
|
||||
auto pageUp = io.ConfigMacOSXBehaviors ? ctrl && up : ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp));
|
||||
auto pageDown = io.ConfigMacOSXBehaviors ? ctrl && down : ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown));
|
||||
|
||||
if (ImGui::IsWindowFocused()) {
|
||||
if (ImGui::IsWindowHovered())
|
||||
@@ -638,25 +648,25 @@ void TextEditor::HandleKeyboardInputs() {
|
||||
Undo();
|
||||
else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Y)))
|
||||
Redo();
|
||||
else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)))
|
||||
else if (!ctrl && !alt && up)
|
||||
MoveUp(1, shift);
|
||||
else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)))
|
||||
else if (!ctrl && !alt && down)
|
||||
MoveDown(1, shift);
|
||||
else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)))
|
||||
else if (!alt && left)
|
||||
MoveLeft(1, shift, ctrl);
|
||||
else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)))
|
||||
else if (!alt && right)
|
||||
MoveRight(1, shift, ctrl);
|
||||
else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp)))
|
||||
else if (!alt && pageUp)
|
||||
MoveUp(GetPageSize() - 4, shift);
|
||||
else if (!alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown)))
|
||||
else if (!alt && pageDown)
|
||||
MoveDown(GetPageSize() - 4, shift);
|
||||
else if (!alt && ctrl && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
|
||||
else if (!alt && top)
|
||||
MoveTop(shift);
|
||||
else if (ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
|
||||
else if (!alt && bottom)
|
||||
MoveBottom(shift);
|
||||
else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)))
|
||||
else if (!ctrl && !alt && home)
|
||||
MoveHome(shift);
|
||||
else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)))
|
||||
else if (!ctrl && !alt && end)
|
||||
MoveEnd(shift);
|
||||
else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)))
|
||||
Delete();
|
||||
@@ -722,7 +732,7 @@ void TextEditor::HandleKeyboardInputs() {
|
||||
void TextEditor::HandleMouseInputs() {
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
auto shift = io.KeyShift;
|
||||
auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
|
||||
auto ctrl = io.ConfigMacOSXBehaviors ? io.KeyAlt : io.KeyCtrl;
|
||||
auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt;
|
||||
|
||||
if (ImGui::IsWindowHovered()) {
|
||||
@@ -905,7 +915,10 @@ void TextEditor::Render() {
|
||||
}
|
||||
|
||||
if (mState.mCursorPosition.mLine == lineNo && mShowCursor) {
|
||||
auto focused = ImGui::IsWindowFocused();
|
||||
bool focused = false;
|
||||
ImGuiViewport *viewport = ImGui::GetWindowViewport();
|
||||
if (viewport->PlatformUserData != NULL && ImGui::GetPlatformIO().Platform_GetWindowFocus(viewport))
|
||||
focused = ImGui::IsWindowFocused();
|
||||
|
||||
// Highlight the current line (where the cursor is)
|
||||
if (!HasSelection()) {
|
||||
@@ -2505,6 +2518,7 @@ void TextEditor::ColorizeInternal() {
|
||||
auto firstChar = true; // there is no other non-whitespace characters in the line before
|
||||
auto currentLine = 0;
|
||||
auto currentIndex = 0;
|
||||
auto commentLength = 0;
|
||||
auto &startStr = mLanguageDefinition.mCommentStart;
|
||||
auto &singleStartStr = mLanguageDefinition.mSingleLineComment;
|
||||
auto &docStartStr = mLanguageDefinition.mDocComment;
|
||||
@@ -2516,6 +2530,14 @@ void TextEditor::ColorizeInternal() {
|
||||
while (currentLine < endLine || currentIndex < endIndex) {
|
||||
auto &line = mLines[currentLine];
|
||||
|
||||
auto setGlyphFlags = [&](int index) {
|
||||
line[index].mMultiLineComment = withinComment;
|
||||
line[index].mComment = withinSingleLineComment;
|
||||
line[index].mDocComment = withinDocComment;
|
||||
line[index].mGlobalDocComment = withinGlobalDocComment;
|
||||
line[index].mDeactivated = withinNotDef;
|
||||
};
|
||||
|
||||
if (currentIndex == 0) {
|
||||
withinSingleLineComment = false;
|
||||
withinPreproc = false;
|
||||
@@ -2532,24 +2554,12 @@ void TextEditor::ColorizeInternal() {
|
||||
bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
|
||||
|
||||
if (withinString) {
|
||||
line[currentIndex].mMultiLineComment = withinComment;
|
||||
line[currentIndex].mComment = withinSingleLineComment;
|
||||
line[currentIndex].mDocComment = withinDocComment;
|
||||
line[currentIndex].mGlobalDocComment = withinGlobalDocComment;
|
||||
line[currentIndex].mDeactivated = withinNotDef;
|
||||
if (c == '\"') {
|
||||
if (currentIndex > 2 && line[currentIndex - 1].mChar == '\\' && line[currentIndex - 2].mChar != '\\') {
|
||||
currentIndex += 1;
|
||||
if (currentIndex < (int)line.size()) {
|
||||
line[currentIndex].mMultiLineComment = withinComment;
|
||||
line[currentIndex].mComment = withinSingleLineComment;
|
||||
line[currentIndex].mDocComment = withinDocComment;
|
||||
line[currentIndex].mGlobalDocComment = withinGlobalDocComment;
|
||||
line[currentIndex].mDeactivated = withinNotDef;
|
||||
}
|
||||
} else
|
||||
withinString = false;
|
||||
}
|
||||
setGlyphFlags(currentIndex);
|
||||
if (c == '\\') {
|
||||
currentIndex++;
|
||||
setGlyphFlags(currentIndex);
|
||||
} else if (c == '\"')
|
||||
withinString = false;
|
||||
} else {
|
||||
if (firstChar && c == mLanguageDefinition.mPreprocChar) {
|
||||
withinPreproc = true;
|
||||
@@ -2594,7 +2604,6 @@ void TextEditor::ColorizeInternal() {
|
||||
}
|
||||
if (!withinNotDef) {
|
||||
bool isConditionMet = std::find(mDefines.begin(),mDefines.end(),identifier) != mDefines.end();
|
||||
withinNotDef = !isConditionMet;
|
||||
ifDefs.push_back(isConditionMet);
|
||||
} else
|
||||
ifDefs.push_back(false);
|
||||
@@ -2608,7 +2617,6 @@ void TextEditor::ColorizeInternal() {
|
||||
}
|
||||
if (!withinNotDef) {
|
||||
bool isConditionMet = std::find(mDefines.begin(),mDefines.end(),identifier) == mDefines.end();
|
||||
withinNotDef = !isConditionMet;
|
||||
ifDefs.push_back(isConditionMet);
|
||||
} else
|
||||
ifDefs.push_back(false);
|
||||
@@ -2616,20 +2624,17 @@ void TextEditor::ColorizeInternal() {
|
||||
}
|
||||
} else {
|
||||
if (directive == "endif") {
|
||||
if (ifDefs.size() > 1)
|
||||
if (ifDefs.size() > 1) {
|
||||
ifDefs.pop_back();
|
||||
withinNotDef = !ifDefs.back();
|
||||
withinNotDef = !ifDefs.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '\"') {
|
||||
withinString = true;
|
||||
line[currentIndex].mMultiLineComment = withinComment;
|
||||
line[currentIndex].mComment = withinSingleLineComment;
|
||||
line[currentIndex].mDocComment = withinDocComment;
|
||||
line[currentIndex].mGlobalDocComment = withinGlobalDocComment;
|
||||
line[currentIndex].mDeactivated = withinNotDef;
|
||||
setGlyphFlags(currentIndex);
|
||||
} else {
|
||||
auto pred = [](const char &a, const Glyph &b) { return a == b.mChar; };
|
||||
|
||||
@@ -2653,29 +2658,30 @@ void TextEditor::ColorizeInternal() {
|
||||
if (isGlobalDocComment || isDocComment || isComment) {
|
||||
commentStartLine = currentLine;
|
||||
commentStartIndex = currentIndex;
|
||||
if (isGlobalDocComment)
|
||||
if (isGlobalDocComment) {
|
||||
withinGlobalDocComment = true;
|
||||
else if (isDocComment)
|
||||
commentLength = 3;
|
||||
} else if (isDocComment) {
|
||||
withinDocComment = true;
|
||||
else
|
||||
commentLength = 3;
|
||||
} else {
|
||||
withinComment = true;
|
||||
commentLength = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex));
|
||||
}
|
||||
line[currentIndex].mGlobalDocComment = withinGlobalDocComment;
|
||||
line[currentIndex].mDocComment = withinDocComment;
|
||||
line[currentIndex].mMultiLineComment = withinComment;
|
||||
line[currentIndex].mComment = withinSingleLineComment;
|
||||
line[currentIndex].mDeactivated = withinNotDef;
|
||||
setGlyphFlags(currentIndex);
|
||||
|
||||
auto &endStr = mLanguageDefinition.mCommentEnd;
|
||||
if (compareBack(endStr, line)) {
|
||||
withinComment = false;
|
||||
withinDocComment = false;
|
||||
withinGlobalDocComment = false;
|
||||
commentStartLine = endLine;
|
||||
commentStartIndex = endIndex;
|
||||
if (compareBack(endStr, line) && ((commentStartLine != currentLine) || (commentStartIndex + commentLength < currentIndex))) {
|
||||
withinComment = false;
|
||||
withinDocComment = false;
|
||||
withinGlobalDocComment = false;
|
||||
commentStartLine = endLine;
|
||||
commentStartIndex = endIndex;
|
||||
commentLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2684,6 +2690,7 @@ void TextEditor::ColorizeInternal() {
|
||||
|
||||
currentIndex += UTF8CharLength(c);
|
||||
if (currentIndex >= (int)line.size()) {
|
||||
withinNotDef = !ifDefs.back();
|
||||
currentIndex = 0;
|
||||
++currentLine;
|
||||
}
|
||||
|
||||
19
lib/third_party/imgui/cimgui/CMakeLists.txt
vendored
Normal file
19
lib/third_party/imgui/cimgui/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(imgui_cimgui)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if (NOT IMHEX_EXTERNAL_PLUGIN_BUILD)
|
||||
add_library(imgui_cimgui OBJECT
|
||||
source/cimgui.cpp
|
||||
)
|
||||
|
||||
target_include_directories(imgui_cimgui PUBLIC
|
||||
include
|
||||
)
|
||||
|
||||
target_link_libraries(imgui_cimgui PRIVATE imgui_includes)
|
||||
add_dependencies(imhex_all imgui_cimgui)
|
||||
endif()
|
||||
|
||||
4788
lib/third_party/imgui/cimgui/include/cimgui.h
vendored
Normal file
4788
lib/third_party/imgui/cimgui/include/cimgui.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5760
lib/third_party/imgui/cimgui/source/cimgui.cpp
vendored
Normal file
5760
lib/third_party/imgui/cimgui/source/cimgui.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -65,7 +65,7 @@ namespace hex::crash {
|
||||
static void printStackTrace() {
|
||||
for (const auto &stackFrame : stacktrace::getStackTrace()) {
|
||||
if (stackFrame.line == 0)
|
||||
log::fatal(" {}", stackFrame.function);
|
||||
log::fatal(" ({}) | {}", stackFrame.file, stackFrame.function);
|
||||
else
|
||||
log::fatal(" ({}:{}) | {}", stackFrame.file, stackFrame.line, stackFrame.function);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace hex::init {
|
||||
|
||||
|
||||
// Load all plugins but don't initialize them
|
||||
PluginManager::loadLibraries();
|
||||
for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Plugins)) {
|
||||
PluginManager::load(dir);
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ namespace hex::init {
|
||||
PluginManager::addLoadPath(dir);
|
||||
}
|
||||
|
||||
PluginManager::loadLibraries();
|
||||
PluginManager::load();
|
||||
#endif
|
||||
|
||||
|
||||
@@ -2,12 +2,26 @@
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <llvm/Demangle/Demangle.h>
|
||||
|
||||
namespace {
|
||||
|
||||
[[maybe_unused]] std::string tryDemangle(const std::string &symbolName) {
|
||||
if (auto variant1 = llvm::demangle(symbolName); variant1 != symbolName)
|
||||
return variant1;
|
||||
|
||||
if (auto variant2 = llvm::demangle(std::string("_") + symbolName); variant2 != std::string("_") + symbolName)
|
||||
return variant2;
|
||||
|
||||
return symbolName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <llvm/Demangle/Demangle.h>
|
||||
|
||||
namespace hex::stacktrace {
|
||||
|
||||
@@ -80,14 +94,7 @@
|
||||
fileName = "??";
|
||||
}
|
||||
|
||||
std::string demangledName;
|
||||
if (auto variant1 = llvm::demangle(symbolName); variant1 != symbolName)
|
||||
demangledName = variant1;
|
||||
else if (auto variant2 = llvm::demangle(std::string("_") + symbolName); variant2 != std::string("_") + symbolName)
|
||||
demangledName = variant2;
|
||||
else
|
||||
demangledName = symbolName;
|
||||
|
||||
auto demangledName = tryDemangle(symbolName);
|
||||
stackTrace.push_back(StackFrame { fileName, demangledName, lineNumber });
|
||||
}
|
||||
|
||||
@@ -103,9 +110,8 @@
|
||||
#if __has_include(BACKTRACE_HEADER)
|
||||
|
||||
#include BACKTRACE_HEADER
|
||||
#include <llvm/Demangle/Demangle.h>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace hex::stacktrace {
|
||||
|
||||
@@ -116,17 +122,24 @@
|
||||
std::vector<StackFrame> getStackTrace() {
|
||||
static std::vector<StackFrame> result;
|
||||
|
||||
std::array<void*, 128> addresses;
|
||||
size_t count = backtrace(addresses.data(), addresses.size());
|
||||
auto functions = backtrace_symbols(addresses.data(), count);
|
||||
std::array<void*, 128> addresses = {};
|
||||
const size_t count = backtrace(addresses.data(), addresses.size());
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
result.push_back(StackFrame { "", functions[i], 0 });
|
||||
Dl_info info;
|
||||
for (size_t i = 0; i < count; i += 1) {
|
||||
dladdr(addresses[i], &info);
|
||||
|
||||
auto fileName = info.dli_fname != nullptr ? std::fs::path(info.dli_fname).filename().string() : "??";
|
||||
auto demangledName = info.dli_sname != nullptr ? tryDemangle(info.dli_sname) : "??";
|
||||
|
||||
result.push_back(StackFrame { std::move(fileName), std::move(demangledName), 0 });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#elif defined(HEX_HAS_BACKTRACE)
|
||||
@@ -134,7 +147,6 @@
|
||||
#if __has_include(BACKTRACE_HEADER)
|
||||
|
||||
#include BACKTRACE_HEADER
|
||||
#include <llvm/Demangle/Demangle.h>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
@@ -161,7 +173,7 @@
|
||||
if (function == nullptr)
|
||||
function = "??";
|
||||
|
||||
result.push_back(StackFrame { std::fs::path(fileName).filename().string(), llvm::demangle(function), u32(lineNumber) });
|
||||
result.push_back(StackFrame { std::fs::path(fileName).filename().string(), tryDemangle(function), u32(lineNumber) });
|
||||
|
||||
return 0;
|
||||
}, nullptr, nullptr);
|
||||
|
||||
@@ -186,12 +186,11 @@ namespace hex {
|
||||
// Determine if the application should be in long sleep mode
|
||||
bool shouldLongSleep = !m_unlockFrameRate;
|
||||
|
||||
// Wait 5 frames before actually enabling the long sleep mode to make animations not stutter
|
||||
static i32 lockTimeout = 0;
|
||||
static double lockTimeout = 0;
|
||||
if (!shouldLongSleep) {
|
||||
lockTimeout = m_lastFrameTime * 10'000;
|
||||
lockTimeout = 0.05;
|
||||
} else if (lockTimeout > 0) {
|
||||
lockTimeout -= 1;
|
||||
lockTimeout -= m_lastFrameTime;
|
||||
}
|
||||
|
||||
if (shouldLongSleep && lockTimeout > 0)
|
||||
@@ -236,14 +235,28 @@ namespace hex {
|
||||
|
||||
// Limit frame rate
|
||||
// If the target FPS are below 15, use the monitor refresh rate, if it's above 200, don't limit the frame rate
|
||||
const auto targetFPS = ImHexApi::System::getTargetFPS();
|
||||
if (targetFPS < 15) {
|
||||
glfwSwapInterval(1);
|
||||
} else if (targetFPS > 200) {
|
||||
glfwSwapInterval(0);
|
||||
auto targetFPS = ImHexApi::System::getTargetFPS();
|
||||
if (targetFPS >= 200) {
|
||||
// Let it rip
|
||||
} else {
|
||||
// If the target frame rate is below 15, use the current monitor's refresh rate
|
||||
if (targetFPS < 15) {
|
||||
// Fall back to 60 FPS if the monitor refresh rate cannot be determined
|
||||
targetFPS = 60;
|
||||
|
||||
if (auto monitor = glfwGetWindowMonitor(m_window); monitor != nullptr) {
|
||||
if (auto videoMode = glfwGetVideoMode(monitor); videoMode != nullptr) {
|
||||
targetFPS = videoMode->refreshRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep if we're not in long sleep mode
|
||||
if (!shouldLongSleep) {
|
||||
glfwSwapInterval(0);
|
||||
// If anything goes wrong with these checks, make sure that we're sleeping for at least 1ms
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
|
||||
// Sleep for the remaining time if the frame rate is above the target frame rate
|
||||
const auto frameTime = glfwGetTime() - m_lastStartFrameTime;
|
||||
const auto targetFrameTime = 1.0 / targetFPS;
|
||||
if (frameTime < targetFrameTime) {
|
||||
@@ -717,7 +730,9 @@ namespace hex {
|
||||
glfwSetWindowOpacity(m_window, 1.0F);
|
||||
|
||||
glfwMakeContextCurrent(m_window);
|
||||
glfwSwapInterval(1);
|
||||
|
||||
// Disable VSync. Not like any graphics driver actually cares
|
||||
glfwSwapInterval(0);
|
||||
|
||||
// Center window
|
||||
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
||||
@@ -969,6 +984,8 @@ namespace hex {
|
||||
void Window::exitImGui() {
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
|
||||
ImNodes::DestroyContext();
|
||||
ImPlot::DestroyContext();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
@@ -2,11 +2,6 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include(ImHexPlugin)
|
||||
|
||||
find_file(DEFAULT_MAGIC_FILE_PATH magic.mgc HINTS ${LIBMAGIC_INCLUDE_DIR}/../share/misc)
|
||||
if (DEFAULT_MAGIC_FILE_PATH)
|
||||
add_romfs_resource(${DEFAULT_MAGIC_FILE_PATH} always_auto_extract/magic/magic.mgc)
|
||||
endif ()
|
||||
|
||||
add_imhex_plugin(
|
||||
NAME
|
||||
builtin
|
||||
@@ -114,8 +109,6 @@ add_imhex_plugin(
|
||||
source/content/views/view_achievements.cpp
|
||||
source/content/views/view_highlight_rules.cpp
|
||||
source/content/views/view_tutorials.cpp
|
||||
|
||||
source/content/helpers/notification.cpp
|
||||
INCLUDES
|
||||
include
|
||||
|
||||
@@ -128,6 +121,11 @@ add_imhex_plugin(
|
||||
LLVMDemangle
|
||||
)
|
||||
|
||||
find_file(DEFAULT_MAGIC_FILE_PATH magic.mgc HINTS ${LIBMAGIC_INCLUDE_DIR}/../share/misc)
|
||||
if (DEFAULT_MAGIC_FILE_PATH)
|
||||
add_romfs_resource(${DEFAULT_MAGIC_FILE_PATH} always_auto_extract/magic/magic.mgc)
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(builtin PRIVATE setupapi)
|
||||
endif ()
|
||||
@@ -9,6 +9,7 @@ namespace hex::plugin::builtin {
|
||||
void handleHelpCommand(const std::vector<std::string> &args);
|
||||
void handlePluginsCommand(const std::vector<std::string> &args);
|
||||
void handleLanguageCommand(const std::vector<std::string> &args);
|
||||
void handleVerboseCommand(const std::vector<std::string> &args);
|
||||
|
||||
void handleOpenCommand(const std::vector<std::string> &args);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <implot_internal.h>
|
||||
#include <random>
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
@@ -747,6 +748,20 @@ namespace hex {
|
||||
};
|
||||
|
||||
class DiagramByteTypesDistribution {
|
||||
private:
|
||||
struct AnnotationRegion {
|
||||
UnlocalizedString unlocalizedName;
|
||||
Region region;
|
||||
ImColor color;
|
||||
};
|
||||
|
||||
struct Tag {
|
||||
UnlocalizedString unlocalizedName;
|
||||
ImU64 value;
|
||||
ImAxis axis;
|
||||
ImGuiCol color;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit DiagramByteTypesDistribution(u64 blockSize = 256, size_t sampleSize = 0x1000) : m_blockSize(blockSize), m_sampleSize(sampleSize){ }
|
||||
|
||||
@@ -771,10 +786,44 @@ namespace hex {
|
||||
"isupper", "islower", "isdigit", "isxdigit"
|
||||
};
|
||||
|
||||
|
||||
for (u32 i = 0; i < Names.size(); i++) {
|
||||
ImPlot::PlotLine(Names[i], m_xBlockTypeDistributions.data(), m_yBlockTypeDistributionsSampled[i].data(), m_xBlockTypeDistributions.size());
|
||||
}
|
||||
|
||||
if (m_showAnnotations) {
|
||||
u32 id = 1;
|
||||
for (const auto &annotation : m_annotationRegions) {
|
||||
const auto ®ion = annotation.region;
|
||||
double xMin = region.getStartAddress();
|
||||
double xMax = region.getEndAddress();
|
||||
double yMin = 0.0F;
|
||||
double yMax = 100.0F;
|
||||
|
||||
ImPlot::DragRect(id, &xMin, &yMin, &xMax, &yMax, annotation.color, ImPlotDragToolFlags_NoFit | ImPlotDragToolFlags_NoInputs);
|
||||
|
||||
const auto min = ImPlot::PlotToPixels(xMin, yMax);
|
||||
const auto max = ImPlot::PlotToPixels(xMax, yMin);
|
||||
const auto mousePos = ImPlot::PixelsToPlot(ImGui::GetMousePos());
|
||||
if (ImGui::IsMouseHoveringRect(min, max)) {
|
||||
ImPlot::Annotation(xMin + (xMax - xMin) / 2, mousePos.y, annotation.color, ImVec2(), false, "%s", Lang(annotation.unlocalizedName).get().c_str());
|
||||
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
ImHexApi::HexEditor::setSelection(annotation.region);
|
||||
}
|
||||
}
|
||||
|
||||
id += 1;
|
||||
}
|
||||
|
||||
for (const auto &tag : m_tags) {
|
||||
if (tag.axis == ImAxis_X1)
|
||||
ImPlot::TagX(tag.value, ImGui::GetStyleColorVec4(tag.color), "%s", Lang(tag.unlocalizedName).get().c_str());
|
||||
else if (tag.axis == ImAxis_Y1)
|
||||
ImPlot::TagY(tag.value, ImGui::GetStyleColorVec4(tag.color), "%s", Lang(tag.unlocalizedName).get().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (updateHandle) {
|
||||
@@ -871,6 +920,12 @@ namespace hex {
|
||||
for (size_t i = 0; i < typeDist.size(); i++)
|
||||
m_yBlockTypeDistributions[i].push_back(typeDist[i] * 100);
|
||||
|
||||
if (m_yBlockTypeDistributions[2].back() + m_yBlockTypeDistributions[4].back() >= 95) {
|
||||
this->addRegion("hex.ui.diagram.byte_type_distribution.plain_text", Region { m_byteCount, m_blockSize }, 0x80FF00FF);
|
||||
} else if (std::ranges::any_of(m_blockValueCounts, [&](auto count) { return count >= m_blockSize * 0.95F; })) {
|
||||
this->addRegion("hex.ui.diagram.byte_type_distribution.similar_bytes", Region { m_byteCount, m_blockSize }, 0x8000FF00);
|
||||
}
|
||||
|
||||
m_blockCount += 1;
|
||||
m_blockValueCounts = { 0 };
|
||||
}
|
||||
@@ -898,6 +953,10 @@ namespace hex {
|
||||
m_handlePosition = filePosition;
|
||||
}
|
||||
|
||||
void enableAnnotations(bool enabled) {
|
||||
m_showAnnotations = enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::array<float, 12> calculateTypeDistribution(const std::array<ImU64, 256> &valueCounts, size_t blockSize) {
|
||||
std::array<ImU64, 12> counts = {};
|
||||
@@ -988,6 +1047,23 @@ namespace hex {
|
||||
m_xBlockTypeDistributions.push_back(m_endAddress);
|
||||
}
|
||||
|
||||
void addRegion(const UnlocalizedString &name, Region region, ImColor color) {
|
||||
const auto existingRegion = std::ranges::find_if(m_annotationRegions, [this, ®ion](const AnnotationRegion &annotation) {
|
||||
auto difference = i64(region.getEndAddress()) - i64(annotation.region.getEndAddress());
|
||||
return difference > 0 && difference < i64(m_blockSize * 32);
|
||||
});
|
||||
|
||||
if (existingRegion != m_annotationRegions.end()) {
|
||||
existingRegion->region.size += region.size;
|
||||
} else {
|
||||
m_annotationRegions.push_back({ name, region, color });
|
||||
}
|
||||
}
|
||||
|
||||
void addTag(const UnlocalizedString &name, u64 value, ImAxis axis, ImGuiCol color) {
|
||||
m_tags.push_back({ name, value, axis, color });
|
||||
}
|
||||
|
||||
private:
|
||||
// Variables used to store the parameters to process
|
||||
|
||||
@@ -1024,5 +1100,10 @@ namespace hex {
|
||||
// Hold the result of the byte distribution analysis
|
||||
std::array<std::vector<float>, 12> m_yBlockTypeDistributions, m_yBlockTypeDistributionsSampled;
|
||||
std::atomic<bool> m_processing = false;
|
||||
|
||||
std::vector<AnnotationRegion> m_annotationRegions;
|
||||
std::vector<Tag> m_tags;
|
||||
|
||||
bool m_showAnnotations = true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void showError(const std::string& message);
|
||||
|
||||
void showWarning(const std::string& message);
|
||||
}
|
||||
@@ -11,6 +11,8 @@ namespace hex::plugin::builtin {
|
||||
|
||||
class FileProvider : public hex::prv::Provider {
|
||||
public:
|
||||
constexpr static u64 MaxMemoryFileSize = 128 * 1024 * 1024;
|
||||
|
||||
FileProvider() = default;
|
||||
~FileProvider() override = default;
|
||||
|
||||
@@ -21,8 +23,6 @@ namespace hex::plugin::builtin {
|
||||
[[nodiscard]] bool isSavable() const override;
|
||||
|
||||
void resizeRaw(u64 newSize) override;
|
||||
void insertRaw(u64 offset, u64 size) override;
|
||||
void removeRaw(u64 offset, u64 size) override;
|
||||
|
||||
void readRaw(u64 offset, void *buffer, size_t size) override;
|
||||
void writeRaw(u64 offset, const void *buffer, size_t size) override;
|
||||
@@ -57,12 +57,18 @@ namespace hex::plugin::builtin {
|
||||
|
||||
private:
|
||||
void convertToMemoryFile();
|
||||
void handleFileChange();
|
||||
|
||||
protected:
|
||||
std::fs::path m_path;
|
||||
wolv::io::File m_file;
|
||||
size_t m_fileSize = 0;
|
||||
|
||||
wolv::io::ChangeTracker m_changeTracker;
|
||||
std::vector<u8> m_data;
|
||||
bool m_loadedIntoMemory = false;
|
||||
bool m_ignoreNextChangeEvent = false;
|
||||
|
||||
std::optional<struct stat> m_fileStats;
|
||||
|
||||
bool m_readable = false, m_writable = false;
|
||||
|
||||
@@ -24,8 +24,6 @@ namespace hex::plugin::builtin {
|
||||
[[nodiscard]] u64 getActualSize() const override { return m_data.size(); }
|
||||
|
||||
void resizeRaw(u64 newSize) override;
|
||||
void insertRaw(u64 offset, u64 size) override;
|
||||
void removeRaw(u64 offset, u64 size) override;
|
||||
|
||||
void save() override;
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ namespace hex::plugin::builtin {
|
||||
using OccurrenceTree = wolv::container::IntervalTree<Occurrence>;
|
||||
|
||||
PerProvider<std::vector<Occurrence>> m_foundOccurrences, m_sortedOccurrences;
|
||||
PerProvider<Occurrence*> m_lastSelectedOccurrence;
|
||||
PerProvider<OccurrenceTree> m_occurrenceTree;
|
||||
PerProvider<std::string> m_currFilter;
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <functional>
|
||||
|
||||
#include <TextEditor.h>
|
||||
#include "popups/popup_file_chooser.hpp"
|
||||
#include "hex/api/achievement_manager.hpp"
|
||||
|
||||
namespace pl::ptrn { class Pattern; }
|
||||
|
||||
@@ -170,7 +172,7 @@ namespace hex::plugin::builtin {
|
||||
u32 color;
|
||||
};
|
||||
|
||||
std::unique_ptr<pl::PatternLanguage> m_editorRuntime;
|
||||
std::unique_ptr<pl::PatternLanguage> m_editorRuntime;
|
||||
|
||||
PerProvider<std::vector<std::fs::path>> m_possiblePatternFiles;
|
||||
bool m_runAutomatically = false;
|
||||
@@ -255,6 +257,39 @@ namespace hex::plugin::builtin {
|
||||
void registerMenuItems();
|
||||
void registerHandlers();
|
||||
|
||||
std::function<void()> importPatternFile = [&] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
const auto basePaths = fs::getDefaultPaths(fs::ImHexPath::Patterns);
|
||||
std::vector<std::fs::path> paths;
|
||||
|
||||
for (const auto &imhexPath : basePaths) {
|
||||
if (!wolv::io::fs::exists(imhexPath)) continue;
|
||||
|
||||
std::error_code error;
|
||||
for (auto &entry : std::fs::recursive_directory_iterator(imhexPath, error)) {
|
||||
if (entry.is_regular_file() && entry.path().extension() == ".hexpat")
|
||||
paths.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
ui::PopupFileChooser::open(
|
||||
basePaths, 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");
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
std::function<void()> exportPatternFile = [&] {
|
||||
fs::openFileBrowser(
|
||||
fs::DialogMode::Save, { {"Pattern", "hexpat"} },
|
||||
[this](const auto &path) {
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Create);
|
||||
file.writeString(wolv::util::trim(m_textEditor.GetText()));
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
void appendEditorText(const std::string &text);
|
||||
void appendVariable(const std::string &type);
|
||||
void appendArray(const std::string &type, size_t size);
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -135,6 +135,7 @@
|
||||
"hex.builtin.menu.file.export.ips32": "IPS32 Patch",
|
||||
"hex.builtin.menu.file.export.bookmark": "Bookmark",
|
||||
"hex.builtin.menu.file.export.pattern": "Pattern File",
|
||||
"hex.builtin.menu.file.export.pattern_file": "Export pattern File",
|
||||
"hex.builtin.menu.file.export.data_processor": "Data Processor Workspace",
|
||||
"hex.builtin.menu.file.export.popup.create": "Cannot export data. Failed to create file!",
|
||||
"hex.builtin.menu.file.export.report": "Report",
|
||||
@@ -147,6 +148,7 @@
|
||||
"hex.builtin.menu.file.import.modified_file": "Modified File",
|
||||
"hex.builtin.menu.file.import.bookmark": "Bookmark",
|
||||
"hex.builtin.menu.file.import.pattern": "Pattern File",
|
||||
"hex.builtin.menu.file.import.pattern_file": "Import pattern File",
|
||||
"hex.builtin.menu.file.import.data_processor": "Data Processor Workspace",
|
||||
"hex.builtin.menu.file.import.custom_encoding": "Custom Encoding File",
|
||||
"hex.builtin.menu.file.import.modified_file.popup.invalid_size": "File selected do not have the same size as the current file. Both sizes must match",
|
||||
@@ -402,6 +404,8 @@
|
||||
"hex.builtin.provider.file.size": "Size",
|
||||
"hex.builtin.provider.file.menu.open_file": "Open file externally",
|
||||
"hex.builtin.provider.file.menu.open_folder": "Open containing folder",
|
||||
"hex.builtin.provider.file.too_large": "This file is too large to be loaded into memory. Opening it anyways will result in modifications to be written directly to the file. Would you like to open it as Read-Only instead?",
|
||||
"hex.builtin.provider.file.reload_changes": "The file has been modified by an external source. Do you want to reload it?",
|
||||
"hex.builtin.provider.gdb": "GDB Server Provider",
|
||||
"hex.builtin.provider.gdb.ip": "IP Address",
|
||||
"hex.builtin.provider.gdb.name": "GDB Server <{0}:{1}>",
|
||||
@@ -500,7 +504,7 @@
|
||||
"hex.builtin.setting.toolbar.icons": "Toolbar Icons",
|
||||
"hex.builtin.shortcut.next_provider": "Select next provider",
|
||||
"hex.builtin.shortcut.prev_provider": "Select previous provider",
|
||||
"hex.builtin.title_bar_button.debug_build": "Debug build\nShift+Click to crash using exception\nCtrl+Click to crash using signal",
|
||||
"hex.builtin.title_bar_button.debug_build": "Debug build\n\nSHIFT + Click to open Debug Menu",
|
||||
"hex.builtin.title_bar_button.feedback": "Leave Feedback",
|
||||
"hex.builtin.tools.ascii_table": "ASCII table",
|
||||
"hex.builtin.tools.ascii_table.octal": "Show octal",
|
||||
@@ -844,6 +848,7 @@
|
||||
"hex.builtin.information_section.info_analysis.highest_entropy": "Highest block entropy",
|
||||
"hex.builtin.information_section.info_analysis.lowest_entropy": "Lowest block entropy",
|
||||
"hex.builtin.information_section.info_analysis": "Information analysis",
|
||||
"hex.builtin.information_section.info_analysis.show_annotations": "Show annotations",
|
||||
"hex.builtin.information_section.relationship_analysis": "Byte Relationship",
|
||||
"hex.builtin.information_section.relationship_analysis.sample_size": "Sample size",
|
||||
"hex.builtin.information_section.relationship_analysis.brightness": "Brightness",
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
#include <hex/helpers/magic.hpp>
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
#include <hex/helpers/literals.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <romfs/romfs.hpp>
|
||||
|
||||
#include <hex/api/plugin_manager.hpp>
|
||||
#include <hex/api/task_manager.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/subcommands/subcommands.hpp>
|
||||
|
||||
#include <wolv/utils/string.hpp>
|
||||
@@ -47,16 +47,30 @@ namespace hex::plugin::builtin {
|
||||
"Available subcommands:\n"
|
||||
);
|
||||
|
||||
size_t longestCommand = 0;
|
||||
size_t longestLongCommand = 0, longestShortCommand = 0;
|
||||
for (const auto &plugin : PluginManager::getPlugins()) {
|
||||
for (const auto &subCommand : plugin.getSubCommands()) {
|
||||
longestCommand = std::max(longestCommand, subCommand.commandKey.size());
|
||||
longestLongCommand = std::max(longestLongCommand, subCommand.commandLong.size());
|
||||
longestShortCommand = std::max(longestShortCommand, subCommand.commandShort.size());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &plugin : PluginManager::getPlugins()) {
|
||||
for (const auto &subCommand : plugin.getSubCommands()) {
|
||||
hex::log::println(" --{}{: <{}} {}", subCommand.commandKey, "", longestCommand - subCommand.commandKey.size(), subCommand.commandDesc);
|
||||
hex::log::println(" "
|
||||
"{}"
|
||||
"{: <{}}"
|
||||
"{}"
|
||||
"{}"
|
||||
"{: <{}}"
|
||||
"{}",
|
||||
subCommand.commandShort.empty() ? " " : "-",
|
||||
subCommand.commandShort, longestShortCommand,
|
||||
subCommand.commandShort.empty() ? " " : ", ",
|
||||
subCommand.commandLong.empty() ? " " : "--",
|
||||
subCommand.commandLong, longestLongCommand + 5,
|
||||
subCommand.commandDescription
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +153,10 @@ namespace hex::plugin::builtin {
|
||||
ImHexApi::System::impl::addInitArgument("language", args[0]);
|
||||
}
|
||||
|
||||
void handleVerboseCommand(const std::vector<std::string> &) {
|
||||
hex::log::enableDebugLogging();
|
||||
}
|
||||
|
||||
void handleHashCommand(const std::vector<std::string> &args) {
|
||||
if (args.size() != 2) {
|
||||
hex::log::println("usage: imhex --hash <algorithm> <file>");
|
||||
|
||||
@@ -164,6 +164,8 @@ namespace hex::plugin::builtin {
|
||||
m_chunkBasedEntropy.reset(m_inputChunkSize, region.getStartAddress(), region.getEndAddress(),
|
||||
provider->getBaseAddress(), provider->getActualSize());
|
||||
|
||||
m_byteTypesDistribution.enableAnnotations(m_showAnnotations);
|
||||
|
||||
// Create a handle to the file
|
||||
auto reader = prv::ProviderReader(provider);
|
||||
reader.seek(region.getStartAddress());
|
||||
@@ -194,6 +196,7 @@ namespace hex::plugin::builtin {
|
||||
|
||||
void drawSettings() override {
|
||||
ImGuiExt::InputHexadecimal("hex.builtin.information_section.info_analysis.block_size"_lang, &m_inputChunkSize);
|
||||
ImGui::Checkbox("hex.builtin.information_section.info_analysis.show_annotations"_lang, &m_showAnnotations);
|
||||
}
|
||||
|
||||
void drawContent() override {
|
||||
@@ -319,12 +322,14 @@ namespace hex::plugin::builtin {
|
||||
void load(const nlohmann::json &data) override {
|
||||
InformationSection::load(data);
|
||||
|
||||
m_inputChunkSize = data.value("block_size", 0);
|
||||
m_inputChunkSize = data.value("block_size", 0);
|
||||
m_showAnnotations = data.value("annotations", true);
|
||||
}
|
||||
|
||||
nlohmann::json store() override {
|
||||
auto result = InformationSection::store();
|
||||
result["block_size"] = m_inputChunkSize;
|
||||
result["block_size"] = m_inputChunkSize;
|
||||
result["annotations"] = m_showAnnotations;
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -341,6 +346,8 @@ namespace hex::plugin::builtin {
|
||||
u64 m_lowestBlockEntropyAddress = 0x00;
|
||||
double m_plainTextCharacterPercentage = -1.0;
|
||||
|
||||
bool m_showAnnotations = true;
|
||||
|
||||
DiagramByteDistribution m_byteDistribution;
|
||||
DiagramByteTypesDistribution m_byteTypesDistribution;
|
||||
DiagramChunkBasedEntropyAnalysis m_chunkBasedEntropy;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#include <hex/helpers/logger.hpp>
|
||||
|
||||
#include <toasts/toast_notification.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void showError(const std::string& message){
|
||||
ui::ToastError::open(message);
|
||||
log::error(message);
|
||||
}
|
||||
|
||||
void showWarning(const std::string& message){
|
||||
ui::ToastWarning::open(message);
|
||||
log::warn(message);
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,6 @@ using namespace wolv::literals;
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
static bool s_demoWindowOpen = false;
|
||||
|
||||
namespace {
|
||||
|
||||
bool noRunningTasks() {
|
||||
@@ -187,7 +185,7 @@ namespace hex::plugin::builtin {
|
||||
|
||||
void exportSelectionToFile() {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [](const auto &path) {
|
||||
TaskManager::createTask("hex.ui.common.processing", TaskManager::NoProgress, [path](auto &) {
|
||||
TaskManager::createTask("hex.ui.common.processing", TaskManager::NoProgress, [path](auto &task) {
|
||||
wolv::io::File outputFile(path, wolv::io::File::Mode::Create);
|
||||
if (!outputFile.isValid()) {
|
||||
TaskManager::doLater([] {
|
||||
@@ -205,6 +203,7 @@ namespace hex::plugin::builtin {
|
||||
provider->read(address, bytes.data(), bytes.size());
|
||||
|
||||
outputFile.writeVector(bytes);
|
||||
task.update();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -559,13 +558,6 @@ namespace hex::plugin::builtin {
|
||||
state = !state;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(DEBUG)
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::MenuItem("hex.builtin.menu.view.demo"_lang, "", &s_demoWindowOpen);
|
||||
ImGui::MenuItem("hex.builtin.menu.view.debug"_lang, "", &hex::dbg::impl::getDebugWindowState());
|
||||
#endif
|
||||
});
|
||||
|
||||
}
|
||||
@@ -573,8 +565,6 @@ namespace hex::plugin::builtin {
|
||||
static void createLayoutMenu() {
|
||||
LayoutManager::reload();
|
||||
|
||||
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.workspace", 4000);
|
||||
|
||||
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout" }, ICON_VS_LAYOUT, 1050, []{}, ImHexApi::Provider::isValid);
|
||||
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.workspace", "hex.builtin.menu.workspace.layout", "hex.builtin.menu.workspace.layout.save" }, 1100, Shortcut::None, [] {
|
||||
@@ -600,12 +590,12 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
}
|
||||
|
||||
bool shift = ImGui::GetIO().KeyShift;
|
||||
bool shiftPressed = ImGui::GetIO().KeyShift;
|
||||
for (auto &[name, path] : LayoutManager::getLayouts()) {
|
||||
if (ImGui::MenuItem(hex::format("{}{}", name, shift ? " " ICON_VS_X : "").c_str(), "", false, ImHexApi::Provider::isValid())) {
|
||||
if (shift) {
|
||||
wolv::io::fs::remove(path);
|
||||
LayoutManager::reload();
|
||||
if (ImGui::MenuItem(hex::format("{}{}", name, shiftPressed ? " " ICON_VS_X : "").c_str(), "", false, ImHexApi::Provider::isValid())) {
|
||||
if (shiftPressed) {
|
||||
LayoutManager::removeLayout(name);
|
||||
break;
|
||||
} else {
|
||||
LayoutManager::load(path);
|
||||
}
|
||||
@@ -615,6 +605,8 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
static void createWorkspaceMenu() {
|
||||
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.workspace", 4000);
|
||||
|
||||
createLayoutMenu();
|
||||
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.workspace" }, 3000);
|
||||
@@ -627,11 +619,19 @@ namespace hex::plugin::builtin {
|
||||
|
||||
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.workspace" }, 3200, [] {
|
||||
const auto &workspaces = WorkspaceManager::getWorkspaces();
|
||||
|
||||
bool shiftPressed = ImGui::GetIO().KeyShift;
|
||||
for (auto it = workspaces.begin(); it != workspaces.end(); ++it) {
|
||||
const auto &[name, workspace] = *it;
|
||||
|
||||
if (ImGui::MenuItem(name.c_str(), "", it == WorkspaceManager::getCurrentWorkspace(), ImHexApi::Provider::isValid())) {
|
||||
WorkspaceManager::switchWorkspace(name);
|
||||
bool canRemove = shiftPressed && !workspace.builtin;
|
||||
if (ImGui::MenuItem(hex::format("{}{}", name, canRemove ? " " ICON_VS_X : "").c_str(), "", it == WorkspaceManager::getCurrentWorkspace(), ImHexApi::Provider::isValid())) {
|
||||
if (canRemove) {
|
||||
WorkspaceManager::removeWorkspace(name);
|
||||
break;
|
||||
} else {
|
||||
WorkspaceManager::switchWorkspace(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -653,13 +653,6 @@ namespace hex::plugin::builtin {
|
||||
createWorkspaceMenu();
|
||||
createExtrasMenu();
|
||||
createHelpMenu();
|
||||
|
||||
(void)EventFrameEnd::subscribe([] {
|
||||
if (s_demoWindowOpen) {
|
||||
ImGui::ShowDemoWindow(&s_demoWindowOpen);
|
||||
ImPlot::ShowDemoWindow(&s_demoWindowOpen);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,8 +13,7 @@
|
||||
#include <hex/providers/provider.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
|
||||
#include <content/helpers/notification.hpp>
|
||||
#include <toasts/toast_notification.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
@@ -23,26 +22,29 @@ namespace hex::plugin::builtin {
|
||||
|
||||
bool load(const std::fs::path &filePath) {
|
||||
if (!wolv::io::fs::exists(filePath) || !wolv::io::fs::isRegularFile(filePath)) {
|
||||
showError(hex::format("hex.builtin.popup.error.project.load"_lang,
|
||||
ui::ToastError::open(hex::format("hex.builtin.popup.error.project.load"_lang,
|
||||
hex::format("hex.builtin.popup.error.project.load.file_not_found"_lang,
|
||||
wolv::util::toUTF8String(filePath)
|
||||
)));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Tar tar(filePath, Tar::Mode::Read);
|
||||
if (!tar.isValid()) {
|
||||
showError(hex::format("hex.builtin.popup.error.project.load"_lang,
|
||||
ui::ToastError::open(hex::format("hex.builtin.popup.error.project.load"_lang,
|
||||
hex::format("hex.builtin.popup.error.project.load.invalid_tar"_lang,
|
||||
tar.getOpenErrorString()
|
||||
)));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tar.contains(MetadataPath)) {
|
||||
showError(hex::format("hex.builtin.popup.error.project.load"_lang,
|
||||
ui::ToastError::open(hex::format("hex.builtin.popup.error.project.load"_lang,
|
||||
hex::format("hex.builtin.popup.error.project.load.invalid_magic"_lang)
|
||||
));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -50,9 +52,10 @@ namespace hex::plugin::builtin {
|
||||
const auto metadataContent = tar.readVector(MetadataPath);
|
||||
|
||||
if (!std::string(metadataContent.begin(), metadataContent.end()).starts_with(MetadataHeaderMagic)) {
|
||||
showError(hex::format("hex.builtin.popup.error.project.load"_lang,
|
||||
ui::ToastError::open(hex::format("hex.builtin.popup.error.project.load"_lang,
|
||||
hex::format("hex.builtin.popup.error.project.load.invalid_magic"_lang)
|
||||
));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -176,4 +179,4 @@ namespace hex::plugin::builtin {
|
||||
void registerProjectHandlers() {
|
||||
hex::ProjectFile::setProjectFunctions(load, store);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
#include <content/providers/process_memory_provider.hpp>
|
||||
#include <content/providers/base64_provider.hpp>
|
||||
#include <popups/popup_notification.hpp>
|
||||
#include "content/helpers/notification.hpp"
|
||||
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <hex/api/task_manager.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <toasts/toast_notification.hpp>
|
||||
|
||||
#include <wolv/utils/guards.hpp>
|
||||
|
||||
@@ -70,9 +70,11 @@ namespace hex::plugin::builtin {
|
||||
if (newProvider == nullptr) {
|
||||
// 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)
|
||||
));
|
||||
ui::ToastError::open(
|
||||
hex::format("hex.builtin.popup.error.project.load"_lang,
|
||||
hex::format("hex.builtin.popup.error.project.load.create_provider"_lang, providerType)
|
||||
)
|
||||
);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
@@ -94,29 +96,27 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
}
|
||||
|
||||
std::string warningMsg;
|
||||
std::string warningMessage;
|
||||
for (const auto &warning : providerWarnings){
|
||||
ImHexApi::Provider::remove(warning.first);
|
||||
warningMsg.append(
|
||||
warningMessage.append(
|
||||
hex::format("\n - {} : {}", warning.first->getName(), warning.second));
|
||||
}
|
||||
|
||||
// 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,
|
||||
hex::format("hex.builtin.popup.error.project.load.no_providers"_lang)) + warningMsg);
|
||||
if (ImHexApi::Provider::getProviders().empty()) {
|
||||
ui::ToastError::open(hex::format("{}{}", "hex.builtin.popup.error.project.load"_lang, "hex.builtin.popup.error.project.load.no_providers"_lang, warningMessage));
|
||||
|
||||
return false;
|
||||
} else {
|
||||
|
||||
// Else, if are warnings, still display them
|
||||
if (warningMsg.empty()) {
|
||||
} else {
|
||||
// Else, if there are warnings, still display them
|
||||
if (warningMessage.empty()) {
|
||||
return true;
|
||||
} else {
|
||||
showWarning(
|
||||
hex::format("hex.builtin.popup.error.project.load.some_providers_failed"_lang, warningMsg));
|
||||
ui::ToastWarning::open(hex::format("hex.builtin.popup.error.project.load.some_providers_failed"_lang, warningMessage));
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <cstring>
|
||||
#include <popups/popup_question.hpp>
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
#include <windows.h>
|
||||
@@ -42,25 +43,38 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
bool FileProvider::isSavable() const {
|
||||
return m_undoRedoStack.canUndo();
|
||||
return m_loadedIntoMemory;
|
||||
}
|
||||
|
||||
void FileProvider::readRaw(u64 offset, void *buffer, size_t size) {
|
||||
if (m_fileSize == 0 || (offset + size) > m_fileSize || buffer == nullptr || size == 0)
|
||||
return;
|
||||
|
||||
m_file.readBufferAtomic(offset, static_cast<u8*>(buffer), size);
|
||||
if (m_loadedIntoMemory)
|
||||
std::memcpy(buffer, m_data.data() + offset, size);
|
||||
else
|
||||
m_file.readBufferAtomic(offset, static_cast<u8*>(buffer), size);
|
||||
}
|
||||
|
||||
void FileProvider::writeRaw(u64 offset, const void *buffer, size_t size) {
|
||||
if ((offset + size) > this->getActualSize() || buffer == nullptr || size == 0)
|
||||
return;
|
||||
|
||||
m_file.writeBufferAtomic(offset, static_cast<const u8*>(buffer), size);
|
||||
if (m_loadedIntoMemory)
|
||||
std::memcpy(m_data.data() + offset, buffer, size);
|
||||
else
|
||||
m_file.writeBufferAtomic(offset, static_cast<const u8*>(buffer), size);
|
||||
}
|
||||
|
||||
void FileProvider::save() {
|
||||
m_file.flush();
|
||||
if (m_loadedIntoMemory) {
|
||||
m_ignoreNextChangeEvent = true;
|
||||
m_file.open();
|
||||
m_file.writeVectorAtomic(0x00, m_data);
|
||||
m_file.setSize(m_data.size());
|
||||
} else {
|
||||
m_file.flush();
|
||||
}
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
FILETIME ft;
|
||||
@@ -75,6 +89,9 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_loadedIntoMemory)
|
||||
m_file.close();
|
||||
|
||||
Provider::save();
|
||||
}
|
||||
|
||||
@@ -86,54 +103,14 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
void FileProvider::resizeRaw(u64 newSize) {
|
||||
m_file.setSize(newSize);
|
||||
if (m_loadedIntoMemory)
|
||||
m_data.resize(newSize);
|
||||
else
|
||||
m_file.setSize(newSize);
|
||||
|
||||
m_fileSize = newSize;
|
||||
}
|
||||
|
||||
void FileProvider::insertRaw(u64 offset, u64 size) {
|
||||
auto oldSize = this->getActualSize();
|
||||
this->resizeRaw(oldSize + size);
|
||||
|
||||
std::vector<u8> buffer(0x1000);
|
||||
const std::vector<u8> zeroBuffer(0x1000);
|
||||
|
||||
auto position = oldSize;
|
||||
while (position > offset) {
|
||||
const auto readSize = std::min<size_t>(position - offset, buffer.size());
|
||||
|
||||
position -= readSize;
|
||||
|
||||
this->readRaw(position, buffer.data(), readSize);
|
||||
this->writeRaw(position, zeroBuffer.data(), readSize);
|
||||
this->writeRaw(position + size, buffer.data(), readSize);
|
||||
}
|
||||
}
|
||||
|
||||
void FileProvider::removeRaw(u64 offset, u64 size) {
|
||||
if (offset > this->getActualSize() || size == 0)
|
||||
return;
|
||||
|
||||
if ((offset + size) > this->getActualSize())
|
||||
size = this->getActualSize() - offset;
|
||||
|
||||
auto oldSize = this->getActualSize();
|
||||
|
||||
std::vector<u8> buffer(0x1000);
|
||||
|
||||
const auto newSize = oldSize - size;
|
||||
auto position = offset;
|
||||
while (position < newSize) {
|
||||
const auto readSize = std::min<size_t>(newSize - position, buffer.size());
|
||||
|
||||
this->readRaw(position + size, buffer.data(), readSize);
|
||||
this->writeRaw(position, buffer.data(), readSize);
|
||||
|
||||
position += readSize;
|
||||
}
|
||||
|
||||
this->resizeRaw(newSize);
|
||||
}
|
||||
|
||||
u64 FileProvider::getActualSize() const {
|
||||
return m_fileSize;
|
||||
}
|
||||
@@ -246,12 +223,36 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
}
|
||||
|
||||
if (m_writable) {
|
||||
if (m_fileSize < MaxMemoryFileSize) {
|
||||
m_data = m_file.readVectorAtomic(0x00, m_fileSize);
|
||||
if (!m_data.empty()) {
|
||||
m_changeTracker = wolv::io::ChangeTracker(m_file);
|
||||
m_changeTracker.startTracking([this]{ this->handleFileChange(); });
|
||||
m_file.close();
|
||||
m_loadedIntoMemory = true;
|
||||
}
|
||||
} else {
|
||||
m_writable = false;
|
||||
ui::PopupQuestion::open("hex.builtin.provider.file.too_large"_lang,
|
||||
[this] {
|
||||
m_writable = false;
|
||||
},
|
||||
[this] {
|
||||
m_writable = true;
|
||||
RequestUpdateWindowTitle::post();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileProvider::close() {
|
||||
m_file.close();
|
||||
m_data.clear();
|
||||
s_openedFiles.erase(this);
|
||||
m_changeTracker.stopTracking();
|
||||
}
|
||||
|
||||
void FileProvider::loadSettings(const nlohmann::json &settings) {
|
||||
@@ -332,4 +333,19 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
}
|
||||
|
||||
void FileProvider::handleFileChange() {
|
||||
if (m_ignoreNextChangeEvent) {
|
||||
m_ignoreNextChangeEvent = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ui::PopupQuestion::open("hex.builtin.provider.file.reload_changes"_lang, [this] {
|
||||
this->close();
|
||||
(void)this->open();
|
||||
getUndoStack().reapply();
|
||||
},
|
||||
[]{});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -69,43 +69,6 @@ namespace hex::plugin::builtin {
|
||||
m_data.resize(newSize);
|
||||
}
|
||||
|
||||
void MemoryFileProvider::insertRaw(u64 offset, u64 size) {
|
||||
auto oldSize = this->getActualSize();
|
||||
this->resizeRaw(oldSize + size);
|
||||
|
||||
std::vector<u8> buffer(0x1000);
|
||||
const std::vector<u8> zeroBuffer(0x1000);
|
||||
|
||||
auto position = oldSize;
|
||||
while (position > offset) {
|
||||
const auto readSize = std::min<size_t>(position - offset, buffer.size());
|
||||
|
||||
position -= readSize;
|
||||
|
||||
this->readRaw(position, buffer.data(), readSize);
|
||||
this->writeRaw(position, zeroBuffer.data(), readSize);
|
||||
this->writeRaw(position + size, buffer.data(), readSize);
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryFileProvider::removeRaw(u64 offset, u64 size) {
|
||||
auto oldSize = this->getActualSize();
|
||||
std::vector<u8> buffer(0x1000);
|
||||
|
||||
const auto newSize = oldSize - size;
|
||||
auto position = offset;
|
||||
while (position < newSize) {
|
||||
const auto readSize = std::min<size_t>(newSize - position, buffer.size());
|
||||
|
||||
this->readRaw(position + size, buffer.data(), readSize);
|
||||
this->writeRaw(position, buffer.data(), readSize);
|
||||
|
||||
position += readSize;
|
||||
}
|
||||
|
||||
this->resizeRaw(oldSize - size);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string MemoryFileProvider::getName() const {
|
||||
if (m_name.empty())
|
||||
return Lang("hex.builtin.provider.mem_file.unsaved");
|
||||
|
||||
@@ -17,21 +17,15 @@
|
||||
|
||||
#include <toasts/toast_notification.hpp>
|
||||
|
||||
#include <csignal>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void addTitleBarButtons() {
|
||||
#if defined(DEBUG)
|
||||
ContentRegistry::Interface::addTitleBarButton(ICON_VS_DEBUG, "hex.builtin.title_bar_button.debug_build", []{
|
||||
if (ImGui::GetIO().KeyCtrl) {
|
||||
// Explicitly trigger a segfault by writing to an invalid memory location
|
||||
// Used for debugging crashes
|
||||
*reinterpret_cast<u8 *>(0x10) = 0x10;
|
||||
std::unreachable();
|
||||
} else if (ImGui::GetIO().KeyShift) {
|
||||
// Explicitly trigger an abort by throwing an uncaught exception
|
||||
// Used for debugging exception errors
|
||||
throw std::runtime_error("Debug Error");
|
||||
std::unreachable();
|
||||
if (ImGui::GetIO().KeyShift) {
|
||||
RequestOpenPopup::post("DebugMenu");
|
||||
} else {
|
||||
hex::openWebpage("https://imhex.werwolv.net/debug");
|
||||
}
|
||||
@@ -55,6 +49,86 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(DEBUG)
|
||||
|
||||
static void drawDebugPopup() {
|
||||
static bool showImGuiDemo = false;
|
||||
static bool showImPlotDemo = false;
|
||||
|
||||
ImGui::SetNextWindowSize(scaled({ 300, 150 }), ImGuiCond_Always);
|
||||
if (ImGui::BeginPopup("DebugMenu")) {
|
||||
if (ImGui::BeginTabBar("DebugTabBar")) {
|
||||
if (ImGui::BeginTabItem("ImHex")) {
|
||||
if (ImGui::BeginChild("Scrolling", ImGui::GetContentRegionAvail())) {
|
||||
ImGui::Checkbox("Show Debug Variables", &dbg::impl::getDebugWindowState());
|
||||
|
||||
ImGuiExt::Header("Information");
|
||||
ImGuiExt::TextFormatted("Running Tasks: {0}", TaskManager::getRunningTaskCount());
|
||||
ImGuiExt::TextFormatted("Running Background Tasks: {0}", TaskManager::getRunningBackgroundTaskCount());
|
||||
ImGuiExt::TextFormatted("Last Frame Time: {0:.3f}ms", ImHexApi::System::getLastFrameTime() * 1000.0F);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("ImGui")) {
|
||||
if (ImGui::BeginChild("Scrolling", ImGui::GetContentRegionAvail())) {
|
||||
auto ctx = ImGui::GetCurrentContext();
|
||||
ImGui::Checkbox("Show ImGui Demo", &showImGuiDemo);
|
||||
ImGui::Checkbox("Show ImPlot Demo", &showImPlotDemo);
|
||||
|
||||
if (ImGui::Button("Trigger Breakpoint in Item") || ctx->DebugItemPickerActive)
|
||||
ImGui::DebugStartItemPicker();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Crashes")) {
|
||||
if (ImGui::BeginChild("Scrolling", ImGui::GetContentRegionAvail())) {
|
||||
if (ImGui::Button("Throw Exception")) {
|
||||
TaskManager::doLater([] {
|
||||
throw std::runtime_error("Test exception");
|
||||
});
|
||||
}
|
||||
if (ImGui::Button("Access Invalid Memory")) {
|
||||
TaskManager::doLater([] {
|
||||
*reinterpret_cast<u32*>(0x10) = 0x10;
|
||||
std::unreachable();
|
||||
});
|
||||
}
|
||||
if (ImGui::Button("Raise SIGSEGV")) {
|
||||
TaskManager::doLater([] {
|
||||
raise(SIGSEGV);
|
||||
});
|
||||
}
|
||||
if (ImGui::Button("Corrupt Memory")) {
|
||||
TaskManager::doLater([] {
|
||||
auto bytes = new u8[0xFFFFF];
|
||||
|
||||
delete[] bytes;
|
||||
delete[] bytes;
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (showImGuiDemo)
|
||||
ImGui::ShowDemoWindow(&showImGuiDemo);
|
||||
if (showImPlotDemo)
|
||||
ImPlot::ShowDemoWindow(&showImPlotDemo);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static bool s_drawDragDropOverlay = false;
|
||||
static void drawDragNDropOverlay() {
|
||||
if (!s_drawDragDropOverlay)
|
||||
@@ -99,6 +173,10 @@ namespace hex::plugin::builtin {
|
||||
EventFrameEnd::subscribe(drawGlobalPopups);
|
||||
EventFrameEnd::subscribe(drawDragNDropOverlay);
|
||||
|
||||
#if defined(DEBUG)
|
||||
EventFrameEnd::subscribe(drawDebugPopup);
|
||||
#endif
|
||||
|
||||
EventFileDragged::subscribe([](bool entered) {
|
||||
s_drawDragDropOverlay = entered;
|
||||
});
|
||||
@@ -230,6 +308,16 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ContentRegistry::Interface::addFooterItem([] {
|
||||
if (auto selection = ImHexApi::HexEditor::getSelection(); selection.has_value()) {
|
||||
ImGuiExt::TextFormattedSelectable("0x{:02X} - 0x{:02X} ({} bytes)",
|
||||
selection->getStartAddress(),
|
||||
selection->getEndAddress(),
|
||||
selection->getSize()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void drawProviderContextMenu(prv::Provider *provider) {
|
||||
|
||||
@@ -275,6 +275,7 @@ namespace hex::plugin::builtin {
|
||||
constexpr static std::array Contributors = {
|
||||
Contributor { "iTrooz", "A huge amount of help maintaining ImHex and the CI", "https://github.com/iTrooz", true },
|
||||
Contributor { "jumanji144", "A ton of help with the Pattern Language, API and usage stats", "https://github.com/jumanji144", true },
|
||||
Contributor { "AxCut", "A ton of great pattern language improvements and help with the issue tracker", "https://github.com/paxcut", false },
|
||||
Contributor { "Mary", "Porting ImHex to macOS originally", "https://github.com/marysaka", false },
|
||||
Contributor { "Roblabla", "Adding the MSI Windows installer", "https://github.com/roblabla", false },
|
||||
Contributor { "jam1garner", "Adding support for Rust plugins", "https://github.com/jam1garner", false },
|
||||
|
||||
@@ -531,6 +531,7 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
m_sortedOccurrences.get(provider) = m_foundOccurrences.get(provider);
|
||||
m_lastSelectedOccurrence = nullptr;
|
||||
|
||||
for (const auto &occurrence : m_foundOccurrences.get(provider))
|
||||
m_occurrenceTree->insert({ occurrence.region.getStartAddress(), occurrence.region.getEndAddress() }, occurrence);
|
||||
@@ -895,6 +896,7 @@ namespace hex::plugin::builtin {
|
||||
m_foundOccurrences->clear();
|
||||
m_sortedOccurrences->clear();
|
||||
m_occurrenceTree->clear();
|
||||
m_lastSelectedOccurrence = nullptr;
|
||||
|
||||
EventHighlightingChanged::post();
|
||||
}
|
||||
@@ -1007,7 +1009,11 @@ namespace hex::plugin::builtin {
|
||||
ImGuiExt::TextFormatted("{}", value);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Selectable("##line", foundItem.selected, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
if (ImGui::GetIO().KeyCtrl) {
|
||||
if (ImGui::GetIO().KeyShift && m_lastSelectedOccurrence != nullptr) {
|
||||
for (auto start = std::min(&foundItem, m_lastSelectedOccurrence.get(provider)); start <= std::max(&foundItem, m_lastSelectedOccurrence.get(provider)); start += 1)
|
||||
start->selected = true;
|
||||
|
||||
} else if (ImGui::GetIO().KeyCtrl) {
|
||||
foundItem.selected = !foundItem.selected;
|
||||
} else {
|
||||
for (auto &occurrence : *m_sortedOccurrences)
|
||||
@@ -1015,6 +1021,8 @@ namespace hex::plugin::builtin {
|
||||
foundItem.selected = true;
|
||||
ImHexApi::HexEditor::setSelection(foundItem.region.getStartAddress(), foundItem.region.getSize());
|
||||
}
|
||||
|
||||
m_lastSelectedOccurrence = &foundItem;
|
||||
}
|
||||
drawContextMenu(foundItem, value);
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ namespace hex::plugin::builtin {
|
||||
return std::nullopt;
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
if (!provider->isSavable())
|
||||
return std::nullopt;
|
||||
|
||||
offset -= provider->getBaseAddress();
|
||||
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <pl/patterns/pattern.hpp>
|
||||
#include <pl/core/preprocessor.hpp>
|
||||
#include <pl/core/parser.hpp>
|
||||
#include <pl/core/ast/ast_node_variable_decl.hpp>
|
||||
#include <pl/core/ast/ast_node_type_decl.hpp>
|
||||
#include <pl/core/ast/ast_node_builtin_type.hpp>
|
||||
|
||||
#include <hex/helpers/fs.hpp>
|
||||
@@ -22,11 +20,9 @@
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#include <popups/popup_file_chooser.hpp>
|
||||
#include <popups/popup_question.hpp>
|
||||
#include <toasts/toast_notification.hpp>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <chrono>
|
||||
|
||||
#include <wolv/io/file.hpp>
|
||||
@@ -34,6 +30,8 @@
|
||||
#include <wolv/utils/guards.hpp>
|
||||
#include <wolv/utils/lock.hpp>
|
||||
|
||||
#include <content/global_actions.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
using namespace hex::literals;
|
||||
@@ -88,6 +86,9 @@ namespace hex::plugin::builtin {
|
||||
langDef.mAutoIndentation = true;
|
||||
langDef.mPreprocChar = '#';
|
||||
|
||||
langDef.mGlobalDocComment = "/*!";
|
||||
langDef.mDocComment = "/**";
|
||||
|
||||
langDef.mName = "Pattern Language";
|
||||
|
||||
initialized = true;
|
||||
@@ -236,6 +237,14 @@ namespace hex::plugin::builtin {
|
||||
bool clickedMenuFind = false;
|
||||
bool clickedMenuReplace = false;
|
||||
if (ImGui::BeginPopup("##pattern_editor_context_menu")) {
|
||||
// no shortcut for this
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.import.pattern_file"_lang, nullptr, false))
|
||||
importPatternFile();
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.export.pattern_file"_lang, nullptr, false))
|
||||
exportPatternFile();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
const bool hasSelection = m_textEditor.HasSelection();
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.cut"_lang, Shortcut(CTRLCMD + Keys::X).toString().c_str(), false, hasSelection)) {
|
||||
m_textEditor.Cut();
|
||||
@@ -306,6 +315,12 @@ namespace hex::plugin::builtin {
|
||||
m_replaceMode = true;
|
||||
openFindPopup = true;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_S, false) && ImGui::GetIO().KeyAlt)
|
||||
hex::plugin::builtin::saveProject();
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_O, false) && ImGui::GetIO().KeyAlt)
|
||||
hex::plugin::builtin::openProject();
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_S, false) && ImGui::GetIO().KeyAlt && ImGui::GetIO().KeyShift)
|
||||
hex::plugin::builtin::saveProjectAs();
|
||||
}
|
||||
|
||||
static std::string findWord;
|
||||
@@ -685,6 +700,8 @@ namespace hex::plugin::builtin {
|
||||
if (altCPressed)
|
||||
matchCase = !matchCase;
|
||||
findReplaceHandler->SetMatchCase(&m_textEditor,matchCase);
|
||||
position = findReplaceHandler->FindPosition(&m_textEditor,m_textEditor.GetCursorPosition(), true);
|
||||
count = findReplaceHandler->GetMatches().size();
|
||||
updateCount = true;
|
||||
requestFocusFind = true;
|
||||
}
|
||||
@@ -1285,7 +1302,8 @@ namespace hex::plugin::builtin {
|
||||
|
||||
if (!m_lastCompileError->empty()) {
|
||||
for (const auto &error : *m_lastCompileError) {
|
||||
errorMarkers[error.getLocation().line] = processMessage(error.getMessage());
|
||||
if (error.getLocation().source->source == pl::api::Source::DefaultSource)
|
||||
errorMarkers[error.getLocation().line] = processMessage(error.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1686,12 +1704,8 @@ namespace hex::plugin::builtin {
|
||||
|
||||
if (newProvider != nullptr)
|
||||
m_textEditor.SetText(m_sourceCode.get(newProvider));
|
||||
});
|
||||
|
||||
EventProviderClosed::subscribe(this, [this](prv::Provider *) {
|
||||
if (ImHexApi::Provider::getProviders().empty()) {
|
||||
else
|
||||
m_textEditor.SetText("");
|
||||
}
|
||||
});
|
||||
|
||||
RequestAddVirtualFile::subscribe(this, [this](const std::fs::path &path, const std::vector<u8> &data, Region region) {
|
||||
@@ -1736,39 +1750,11 @@ namespace hex::plugin::builtin {
|
||||
void ViewPatternEditor::registerMenuItems() {
|
||||
/* Import Pattern */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.pattern" }, ICON_VS_FILE_CODE, 4050, Shortcut::None,
|
||||
[this] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
const auto basePaths = fs::getDefaultPaths(fs::ImHexPath::Patterns);
|
||||
std::vector<std::fs::path> paths;
|
||||
|
||||
for (const auto &imhexPath : basePaths) {
|
||||
if (!wolv::io::fs::exists(imhexPath)) continue;
|
||||
|
||||
std::error_code error;
|
||||
for (auto &entry : std::fs::recursive_directory_iterator(imhexPath, error)) {
|
||||
if (entry.is_regular_file() && entry.path().extension() == ".hexpat") {
|
||||
paths.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui::PopupFileChooser::open(basePaths, 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");
|
||||
});
|
||||
}, ImHexApi::Provider::isValid);
|
||||
importPatternFile, ImHexApi::Provider::isValid);
|
||||
|
||||
/* Export Pattern */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.pattern" }, ICON_VS_FILE_CODE, 7050, Shortcut::None,
|
||||
[this] {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, { {"Pattern", "hexpat"} },
|
||||
[this](const auto &path) {
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Create);
|
||||
|
||||
file.writeString(wolv::util::trim(m_textEditor.GetText()));
|
||||
});
|
||||
}, [this] {
|
||||
exportPatternFile, [this] {
|
||||
return !wolv::util::trim(m_textEditor.GetText()).empty() && ImHexApi::Provider::isValid();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -150,6 +150,22 @@ namespace hex::plugin::builtin {
|
||||
});
|
||||
}
|
||||
|
||||
void drawWelcomeScreenBackground() {
|
||||
const auto position = ImGui::GetWindowPos();
|
||||
const auto size = ImGui::GetWindowSize();
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
const auto lineDistance = 20_scaled;
|
||||
const auto lineColor = ImGui::GetColorU32(ImGuiCol_Text, 0.03F);
|
||||
|
||||
for (auto x = position.x; x < position.x + size.x + lineDistance; x += lineDistance) {
|
||||
drawList->AddLine({ x, position.y }, { x, position.y + size.y }, lineColor);
|
||||
}
|
||||
for (auto y = position.y; y < position.y + size.y + lineDistance; y += lineDistance) {
|
||||
drawList->AddLine({ position.x, y }, { position.x + size.x, y }, lineColor);
|
||||
}
|
||||
}
|
||||
|
||||
void drawWelcomeScreenContentSimplified() {
|
||||
const ImVec2 backdropSize = scaled({ 350, 350 });
|
||||
ImGui::SetCursorPos((ImGui::GetContentRegionAvail() - backdropSize) / 2);
|
||||
@@ -361,6 +377,9 @@ namespace hex::plugin::builtin {
|
||||
ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID);
|
||||
if (ImGui::Begin("Welcome Screen", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) {
|
||||
ImGui::BringWindowToDisplayBack(ImGui::GetCurrentWindowRead());
|
||||
|
||||
drawWelcomeScreenBackground();
|
||||
|
||||
if (s_simplifiedWelcomeScreen)
|
||||
drawWelcomeScreenContentSimplified();
|
||||
else
|
||||
|
||||
@@ -250,6 +250,8 @@ namespace hex::plugin::builtin {
|
||||
#endif
|
||||
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
ImGui::Dummy({});
|
||||
|
||||
auto window = ImHexApi::System::getMainWindowHandle();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
@@ -7,23 +7,9 @@
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void loadWorkspaces() {
|
||||
WorkspaceManager::reset();
|
||||
|
||||
for (const auto &defaultPath : fs::getDefaultPaths(fs::ImHexPath::Workspaces)) {
|
||||
for (const auto &entry : std::fs::directory_iterator(defaultPath)) {
|
||||
if (!entry.is_regular_file())
|
||||
continue;
|
||||
|
||||
const auto &path = entry.path();
|
||||
if (path.extension() != ".hexws")
|
||||
continue;
|
||||
|
||||
WorkspaceManager::importFromFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
std::string currentWorkspace = ContentRegistry::Settings::read<std::string>("hex.builtin.setting.general", "hex.builtin.setting.general.curr_workspace", "Default");
|
||||
WorkspaceManager::reload();
|
||||
|
||||
auto currentWorkspace = ContentRegistry::Settings::read<std::string>("hex.builtin.setting.general", "hex.builtin.setting.general.curr_workspace", "Default");
|
||||
TaskManager::doLater([currentWorkspace] {
|
||||
WorkspaceManager::switchWorkspace(currentWorkspace);
|
||||
});
|
||||
|
||||
@@ -58,21 +58,22 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
IMHEX_PLUGIN_SUBCOMMANDS() {
|
||||
{ "help", "Print help about this command", hex::plugin::builtin::handleHelpCommand },
|
||||
{ "version", "Print ImHex version", hex::plugin::builtin::handleVersionCommand },
|
||||
{ "plugins", "Lists all plugins that have been installed", hex::plugin::builtin::handlePluginsCommand },
|
||||
{ "language", "Changes the language ImHex uses", hex::plugin::builtin::handleLanguageCommand },
|
||||
{ "help", "h", "Print help about this command", hex::plugin::builtin::handleHelpCommand },
|
||||
{ "version", "", "Print ImHex version", hex::plugin::builtin::handleVersionCommand },
|
||||
{ "plugins", "", "Lists all plugins that have been installed", hex::plugin::builtin::handlePluginsCommand },
|
||||
{ "language", "", "Changes the language ImHex uses", hex::plugin::builtin::handleLanguageCommand },
|
||||
{ "verbose", "v", "Enables verbose debug logging", hex::plugin::builtin::handleVerboseCommand },
|
||||
|
||||
{ "open", "Open files passed as argument. [default]", hex::plugin::builtin::handleOpenCommand },
|
||||
{ "open", "o", "Open files passed as argument. [default]", hex::plugin::builtin::handleOpenCommand },
|
||||
|
||||
{ "calc", "Evaluate a mathematical expression", hex::plugin::builtin::handleCalcCommand },
|
||||
{ "hash", "Calculate the hash of a file", hex::plugin::builtin::handleHashCommand },
|
||||
{ "encode", "Encode a string", hex::plugin::builtin::handleEncodeCommand },
|
||||
{ "decode", "Decode a string", hex::plugin::builtin::handleDecodeCommand },
|
||||
{ "magic", "Identify file types", hex::plugin::builtin::handleMagicCommand },
|
||||
{ "pl", "Interact with the pattern language", hex::plugin::builtin::handlePatternLanguageCommand },
|
||||
{ "hexdump", "Generate a hex dump of the provided file", hex::plugin::builtin::handleHexdumpCommand },
|
||||
{ "demangle", "Demangle a mangled symbol", hex::plugin::builtin::handleDemangleCommand },
|
||||
{ "calc", "", "Evaluate a mathematical expression", hex::plugin::builtin::handleCalcCommand },
|
||||
{ "hash", "", "Calculate the hash of a file", hex::plugin::builtin::handleHashCommand },
|
||||
{ "encode", "", "Encode a string", hex::plugin::builtin::handleEncodeCommand },
|
||||
{ "decode", "", "Decode a string", hex::plugin::builtin::handleDecodeCommand },
|
||||
{ "magic", "", "Identify file types", hex::plugin::builtin::handleMagicCommand },
|
||||
{ "pl", "", "Interact with the pattern language", hex::plugin::builtin::handlePatternLanguageCommand },
|
||||
{ "hexdump", "", "Generate a hex dump of the provided file", hex::plugin::builtin::handleHexdumpCommand },
|
||||
{ "demangle", "", "Demangle a mangled symbol", hex::plugin::builtin::handleDemangleCommand },
|
||||
};
|
||||
|
||||
IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") {
|
||||
|
||||
19
plugins/builtin/tests/CMakeLists.txt
Normal file
19
plugins/builtin/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
project(${IMHEX_PLUGIN_NAME}_tests)
|
||||
|
||||
# Add new tests here #
|
||||
set(AVAILABLE_TESTS
|
||||
Providers/ReadWrite
|
||||
Providers/InvalidResize
|
||||
)
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED
|
||||
source/main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/plugins/builtin/include)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE libimhex)
|
||||
|
||||
foreach (test IN LISTS AVAILABLE_TESTS)
|
||||
add_test(NAME "Plugin_${IMHEX_PLUGIN_NAME}/${test}" COMMAND $<TARGET_FILE:plugins_test> "${test}" WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
endforeach ()
|
||||
43
plugins/builtin/tests/source/main.cpp
Normal file
43
plugins/builtin/tests/source/main.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include <iostream>
|
||||
#include <hex/test/tests.hpp>
|
||||
#include <hex/api/plugin_manager.hpp>
|
||||
#include <content/providers/memory_file_provider.hpp>
|
||||
#include <content/views/view_patches.hpp>
|
||||
#include <hex/api/task_manager.hpp>
|
||||
|
||||
using namespace hex;
|
||||
using namespace hex::plugin::builtin;
|
||||
|
||||
TEST_SEQUENCE("Providers/ReadWrite") {
|
||||
INIT_PLUGIN("Built-in");
|
||||
|
||||
auto &pr = *ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true);
|
||||
|
||||
TEST_ASSERT(pr.getSize() == 0);
|
||||
TEST_ASSERT(!pr.isDirty());
|
||||
|
||||
pr.resize(50);
|
||||
TEST_ASSERT(pr.getSize() == 50);
|
||||
TEST_ASSERT(pr.isDirty());
|
||||
|
||||
char buf[] = "\x99\x99"; // temporary value that should be overwriten
|
||||
pr.read(0, buf, 2);
|
||||
TEST_ASSERT(std::equal(buf, buf+2, "\x00\x00"));
|
||||
|
||||
pr.write(0, "\xFF\xFF", 2);
|
||||
char buf2[] = "\x99\x99"; // temporary value that should be overwriten
|
||||
pr.read(0, buf2, 2);
|
||||
TEST_ASSERT(std::equal(buf2, buf2+2, "\xFF\xFF"));
|
||||
|
||||
TEST_SUCCESS();
|
||||
};
|
||||
|
||||
TEST_SEQUENCE("Providers/InvalidResize") {
|
||||
INIT_PLUGIN("Built-in");
|
||||
|
||||
auto &pr = *ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true);
|
||||
|
||||
|
||||
TEST_ASSERT(!pr.resize(-1));
|
||||
TEST_SUCCESS();
|
||||
};
|
||||
@@ -15,10 +15,10 @@ using namespace hex;
|
||||
using namespace hex::plugin::decompress;
|
||||
|
||||
IMHEX_PLUGIN_FEATURES() {
|
||||
{ "bzip2 Support", IMHEX_FEATURE_ENABLED(BZIP2) },
|
||||
{ "zlib Support", IMHEX_FEATURE_ENABLED(ZLIB) },
|
||||
{ "LZMA Support", IMHEX_FEATURE_ENABLED(LIBLZMA) },
|
||||
{ "zstd Support", IMHEX_FEATURE_ENABLED(ZSTD) },
|
||||
{ "bzip2", IMHEX_FEATURE_ENABLED(BZIP2) },
|
||||
{ "zlib", IMHEX_FEATURE_ENABLED(ZLIB) },
|
||||
{ "LZMA", IMHEX_FEATURE_ENABLED(LIBLZMA) },
|
||||
{ "zstd", IMHEX_FEATURE_ENABLED(ZSTD) },
|
||||
};
|
||||
|
||||
IMHEX_PLUGIN_SETUP("Decompressing", "WerWolv", "Support for decompressing data") {
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include(ImHexPlugin)
|
||||
|
||||
find_package(CoreClrEmbed)
|
||||
|
||||
add_imhex_plugin(
|
||||
NAME
|
||||
script_loader
|
||||
|
||||
SOURCES
|
||||
source/plugin_script_loader.cpp
|
||||
|
||||
INCLUDES
|
||||
include
|
||||
|
||||
LIBRARIES
|
||||
fonts
|
||||
ui
|
||||
)
|
||||
|
||||
if (CoreClrEmbed_FOUND)
|
||||
set(IMHEX_DOTNET_SCRIPT_SUPPORT ON)
|
||||
|
||||
add_library(nethost SHARED IMPORTED)
|
||||
target_include_directories(nethost INTERFACE "${CoreClrEmbed_INCLUDE_DIRS}")
|
||||
get_filename_component(CoreClrEmbed_FOLDER ${CoreClrEmbed_SHARED_LIBRARIES} DIRECTORY)
|
||||
@@ -29,24 +16,70 @@ if (CoreClrEmbed_FOUND)
|
||||
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 ()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(dotnet)
|
||||
find_package(Python3 COMPONENTS Interpreter Development.Embed)
|
||||
if (Python3_FOUND)
|
||||
set(IMHEX_PYTHON_SCRIPT_SUPPORT ON)
|
||||
|
||||
get_target_property(PYTHON_LIBRARY Python3::Python IMPORTED_LOCATION)
|
||||
get_target_property(PYTHON_INCLUDE_DIR Python3::Python INTERFACE_INCLUDE_DIRECTORIES)
|
||||
endif()
|
||||
|
||||
add_subdirectory(support/c)
|
||||
|
||||
|
||||
|
||||
add_imhex_plugin(
|
||||
NAME
|
||||
script_loader
|
||||
|
||||
SOURCES
|
||||
source/plugin_script_loader.cpp
|
||||
INCLUDES
|
||||
include
|
||||
|
||||
LIBRARIES
|
||||
c_api
|
||||
fonts
|
||||
|
||||
FEATURES
|
||||
DOTNET
|
||||
PYTHON
|
||||
)
|
||||
|
||||
|
||||
|
||||
if (IMHEX_DOTNET_SCRIPT_SUPPORT)
|
||||
message(STATUS "Enabling .NET Scripting support!")
|
||||
|
||||
target_link_directories(script_loader PRIVATE ${CoreClrEmbed_FOLDER})
|
||||
target_include_directories(script_loader PRIVATE ${CoreClrEmbed_INCLUDE_DIRS})
|
||||
target_compile_definitions(script_loader PRIVATE IMHEX_DOTNET_SCRIPT_SUPPORT=1)
|
||||
target_sources(script_loader PRIVATE
|
||||
source/loaders/dotnet/dotnet_loader.cpp
|
||||
)
|
||||
|
||||
add_subdirectory(support/dotnet)
|
||||
add_dependencies(script_loader AssemblyLoader)
|
||||
enable_plugin_feature(DOTNET)
|
||||
endif()
|
||||
|
||||
endif ()
|
||||
if (IMHEX_PYTHON_SCRIPT_SUPPORT)
|
||||
message(STATUS "Enabling Python Scripting support!")
|
||||
|
||||
target_compile_definitions(script_loader PRIVATE IMHEX_PYTHON_SCRIPT_SUPPORT=1)
|
||||
target_sources(script_loader PRIVATE
|
||||
source/loaders/python/python_loader.cpp
|
||||
source/loaders/python/library_wrapper.cpp
|
||||
)
|
||||
|
||||
target_include_directories(script_loader PRIVATE ${PYTHON_INCLUDE_DIR})
|
||||
target_compile_definitions(script_loader PRIVATE PYTHON_LIBRARY_PATH="${PYTHON_LIBRARY}")
|
||||
enable_plugin_feature(PYTHON)
|
||||
endif()
|
||||
@@ -1,82 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
namespace ImHex
|
||||
{
|
||||
|
||||
public class EntryPoint
|
||||
{
|
||||
|
||||
public static int ExecuteScript(IntPtr arg, int argLength)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ExecuteScript(Marshal.PtrToStringUTF8(arg, argLength)) ? 0 : 1;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("[.NET Script] Exception in AssemblyLoader: " + e.ToString());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static bool ExecuteScript(string path)
|
||||
{
|
||||
string? basePath = Path.GetDirectoryName(path);
|
||||
if (basePath == null)
|
||||
{
|
||||
Console.WriteLine("[.NET Script] Failed to get base path");
|
||||
return false;
|
||||
}
|
||||
|
||||
AssemblyLoadContext? context = new("ScriptDomain_" + basePath, true);
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(basePath, "*.dll"))
|
||||
{
|
||||
context.LoadFromStream(new MemoryStream(File.ReadAllBytes(file)));
|
||||
}
|
||||
|
||||
var assembly = context.LoadFromStream(new MemoryStream(File.ReadAllBytes(path)));
|
||||
|
||||
var entryPointType = assembly.GetType("Script");
|
||||
if (entryPointType == null)
|
||||
{
|
||||
Console.WriteLine("[.NET Script] Failed to find Script type");
|
||||
return false;
|
||||
}
|
||||
|
||||
var entryPointMethod = entryPointType.GetMethod("Main", BindingFlags.Static | BindingFlags.Public);
|
||||
if (entryPointMethod == null)
|
||||
{
|
||||
Console.WriteLine("[.NET Script] Failed to find ScriptMain method");
|
||||
return false;
|
||||
}
|
||||
|
||||
entryPointMethod.Invoke(null, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("[.NET Script] Exception in AssemblyLoader: " + e.ToString());
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.Unload();
|
||||
context = null;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>bin\Release\net7.0\publish\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,7 +0,0 @@
|
||||
file(READ "${OUTPUT_RUNTIMECONFIG}" FILE_CONTENTS)
|
||||
|
||||
set(VERSION_REGEX [["version": "([0-9]+\.[0-9]+\.[0-9]+)"]])
|
||||
set(REPLACE_VALUE [["version": "7.0.0"]])
|
||||
string(REGEX REPLACE "${VERSION_REGEX}" ${REPLACE_VALUE} FILE_CONTENTS_OUT "${FILE_CONTENTS}")
|
||||
|
||||
file(WRITE "${OUTPUT_RUNTIMECONFIG}" "${FILE_CONTENTS_OUT}")
|
||||
@@ -10,14 +10,15 @@ namespace hex::script::loader {
|
||||
|
||||
class DotNetLoader : public ScriptLoader {
|
||||
public:
|
||||
DotNetLoader() = default;
|
||||
DotNetLoader() : ScriptLoader(".NET") {}
|
||||
~DotNetLoader() override = default;
|
||||
|
||||
bool initialize() override;
|
||||
bool loadAll() override;
|
||||
|
||||
private:
|
||||
std::function<bool(const std::fs::path&)> m_loadAssembly;
|
||||
std::function<int(const std::string &, bool, const std::fs::path&)> m_runMethod;
|
||||
std::function<bool(const std::string &, const std::fs::path&)> m_methodExists;
|
||||
std::fs::path::string_type m_assemblyLoaderPathString;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,30 +3,44 @@
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace hex::script::loader {
|
||||
|
||||
class ScriptLoader;
|
||||
|
||||
struct Script {
|
||||
std::string name;
|
||||
std::function<void()> entryPoint;
|
||||
const ScriptLoader *loader;
|
||||
};
|
||||
|
||||
class ScriptLoader {
|
||||
public:
|
||||
ScriptLoader() = default;
|
||||
ScriptLoader(std::string typeName) : m_typeName(std::move(typeName)) {}
|
||||
virtual ~ScriptLoader() = default;
|
||||
|
||||
virtual bool initialize() = 0;
|
||||
virtual bool loadAll() = 0;
|
||||
|
||||
void addScript(std::string name, std::function<void()> entryPoint) {
|
||||
m_scripts.emplace_back(std::move(name), std::move(entryPoint));
|
||||
m_scripts.emplace_back(std::move(name), std::move(entryPoint), this);
|
||||
}
|
||||
|
||||
const auto& getScripts() const {
|
||||
return m_scripts;
|
||||
}
|
||||
|
||||
const std::string& getTypeName() const {
|
||||
return m_typeName;
|
||||
}
|
||||
|
||||
protected:
|
||||
void clearScripts() {
|
||||
m_scripts.clear();
|
||||
@@ -34,6 +48,49 @@ namespace hex::script::loader {
|
||||
|
||||
private:
|
||||
std::vector<Script> m_scripts;
|
||||
std::string m_typeName;
|
||||
};
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
inline void *loadLibrary(const wchar_t *path) {
|
||||
try {
|
||||
HMODULE h = ::LoadLibraryW(path);
|
||||
return h;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
inline void *loadLibrary(const char *path) {
|
||||
try {
|
||||
auto utf16Path = hex::utf8ToUtf16(path);
|
||||
HMODULE h = ::LoadLibraryW(utf16Path.c_str());
|
||||
return h;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T getExport(void *h, const char *name) {
|
||||
try {
|
||||
FARPROC f = ::GetProcAddress(static_cast<HMODULE>(h), name);
|
||||
return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(f));
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
#else
|
||||
inline void *loadLibrary(const char *path) {
|
||||
void *h = dlopen(path, RTLD_LAZY);
|
||||
return h;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T getExport(void *h, const char *name) {
|
||||
void *f = dlsym(h, name);
|
||||
return reinterpret_cast<T>(f);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <loaders/loader.hpp>
|
||||
|
||||
#include <wolv/io/fs.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace hex::script::loader {
|
||||
|
||||
class PythonLoader : public ScriptLoader {
|
||||
public:
|
||||
PythonLoader() : ScriptLoader("Python") {}
|
||||
~PythonLoader() override = default;
|
||||
|
||||
bool initialize() override;
|
||||
bool loadAll() override;
|
||||
|
||||
private:
|
||||
std::vector<void*> m_loadedModules;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -3,10 +3,8 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
#include <Windows.h>
|
||||
#define STRING(str) L##str
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#define STRING(str) str
|
||||
#endif
|
||||
|
||||
@@ -15,6 +13,8 @@
|
||||
#include <nethost.h>
|
||||
#include <coreclr_delegates.h>
|
||||
#include <hostfxr.h>
|
||||
#include <imgui.h>
|
||||
#include <hex/api/plugin_manager.hpp>
|
||||
|
||||
#include <hex/helpers/fs.hpp>
|
||||
#include <wolv/io/fs.hpp>
|
||||
@@ -22,6 +22,9 @@
|
||||
#include <wolv/utils/string.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
extern "C" void igSetCurrentContext(ImGuiContext* ctx);
|
||||
|
||||
namespace hex::script::loader {
|
||||
|
||||
@@ -29,46 +32,27 @@ namespace hex::script::loader {
|
||||
|
||||
using get_hostfxr_path_fn = int(*)(char_t * buffer, size_t * buffer_size, const get_hostfxr_parameters *parameters);
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
void *loadLibrary(const char_t *path) {
|
||||
try {
|
||||
HMODULE h = ::LoadLibraryW(path);
|
||||
return h;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T getExport(void *h, const char *name) {
|
||||
try {
|
||||
FARPROC f = ::GetProcAddress(static_cast<HMODULE>(h), name);
|
||||
return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(f));
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
#else
|
||||
void *loadLibrary(const char_t *path) {
|
||||
void *h = dlopen(path, RTLD_LAZY);
|
||||
return h;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T getExport(void *h, const char *name) {
|
||||
void *f = dlsym(h, name);
|
||||
return reinterpret_cast<T>(f);
|
||||
}
|
||||
#endif
|
||||
|
||||
hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config = nullptr;
|
||||
hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr;
|
||||
hostfxr_close_fn hostfxr_close = nullptr;
|
||||
hostfxr_set_runtime_property_value_fn hostfxr_set_runtime_property_value = nullptr;
|
||||
hostfxr_set_error_writer_fn hostfxr_set_error_writer = nullptr;
|
||||
|
||||
void* pInvokeOverride(const char *libraryName, const char *symbolName) {
|
||||
auto library = std::string_view(libraryName);
|
||||
if (library == "cimgui") {
|
||||
return getExport<void*>(ImHexApi::System::getLibImHexModuleHandle(), symbolName);
|
||||
} else if (library == "ImHex") {
|
||||
return getExport<void*>(hex::getContainingModule((void*)&pInvokeOverride), symbolName);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool loadHostfxr() {
|
||||
#if defined(OS_WINDOWS)
|
||||
auto netHostLibrary = loadLibrary(L"nethost.dll");
|
||||
#elif defined(OS_LINUX)
|
||||
#elif defined(OS_LINUX)
|
||||
auto netHostLibrary = loadLibrary("libnethost.so");
|
||||
#elif defined(OS_MACOS)
|
||||
void *netHostLibrary = nullptr;
|
||||
@@ -88,10 +72,12 @@ namespace hex::script::loader {
|
||||
|
||||
auto get_hostfxr_path_ptr = getExport<get_hostfxr_path_fn>(netHostLibrary, "get_hostfxr_path");
|
||||
|
||||
std::array<char_t, 300> buffer = { 0 };
|
||||
std::array<char_t, 300> buffer = { };
|
||||
size_t bufferSize = buffer.size();
|
||||
if (get_hostfxr_path_ptr(buffer.data(), &bufferSize, nullptr) != 0) {
|
||||
log::error("Could not get hostfxr path!");
|
||||
|
||||
auto result = get_hostfxr_path_ptr(buffer.data(), &bufferSize, nullptr);
|
||||
if (result != 0) {
|
||||
log::error(hex::format("Could not get hostfxr path! 0x{:X}", result));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -108,13 +94,26 @@ namespace hex::script::loader {
|
||||
= getExport<hostfxr_get_runtime_delegate_fn>(hostfxrLibrary, "hostfxr_get_runtime_delegate");
|
||||
hostfxr_close
|
||||
= getExport<hostfxr_close_fn>(hostfxrLibrary, "hostfxr_close");
|
||||
|
||||
hostfxr_set_runtime_property_value
|
||||
= getExport<hostfxr_set_runtime_property_value_fn>(hostfxrLibrary, "hostfxr_set_runtime_property_value");
|
||||
hostfxr_set_error_writer
|
||||
= getExport<hostfxr_set_error_writer_fn>(hostfxrLibrary, "hostfxr_set_error_writer");
|
||||
}
|
||||
|
||||
hostfxr_set_error_writer([] HOSTFXR_CALLTYPE (const char_t *message) {
|
||||
#if defined(OS_WINDOWS)
|
||||
log::error("{}", utf16ToUtf8(message));
|
||||
#else
|
||||
log::error("{}", message);
|
||||
#endif
|
||||
});
|
||||
|
||||
return
|
||||
hostfxr_initialize_for_runtime_config != nullptr &&
|
||||
hostfxr_get_runtime_delegate != nullptr &&
|
||||
hostfxr_close != nullptr;
|
||||
hostfxr_close != nullptr &&
|
||||
hostfxr_set_runtime_property_value != nullptr &&
|
||||
hostfxr_set_error_writer != nullptr;
|
||||
}
|
||||
|
||||
load_assembly_and_get_function_pointer_fn getLoadAssemblyFunction(const std::fs::path &path) {
|
||||
@@ -128,9 +127,15 @@ namespace hex::script::loader {
|
||||
};
|
||||
|
||||
if (result > 2 || ctx == nullptr) {
|
||||
throw std::runtime_error(hex::format("Failed to initialize command line {:X}", result));
|
||||
throw std::runtime_error(hex::format("Failed to initialize command line 0x{:X}", result));
|
||||
}
|
||||
|
||||
#if defined (OS_WINDOWS)
|
||||
hostfxr_set_runtime_property_value(ctx, STRING("PINVOKE_OVERRIDE"), utf8ToUtf16(hex::format("{}", (void*)pInvokeOverride)).c_str());
|
||||
#else
|
||||
hostfxr_set_runtime_property_value(ctx, STRING("PINVOKE_OVERRIDE"), hex::format("{}", (void*)pInvokeOverride).c_str());
|
||||
#endif
|
||||
|
||||
result = hostfxr_get_runtime_delegate(
|
||||
ctx,
|
||||
hostfxr_delegate_type::hdt_load_assembly_and_get_function_pointer,
|
||||
@@ -138,7 +143,7 @@ namespace hex::script::loader {
|
||||
);
|
||||
|
||||
if (result != 0 || loadAssemblyFunction == nullptr) {
|
||||
throw std::runtime_error("Failed to get load_assembly_and_get_function_pointer delegate");
|
||||
throw std::runtime_error(hex::format("Failed to get load_assembly_and_get_function_pointer delegate 0x{:X}", result));
|
||||
}
|
||||
|
||||
return loadAssemblyFunction;
|
||||
@@ -175,12 +180,23 @@ namespace hex::script::loader {
|
||||
);
|
||||
|
||||
if (result != 0 || entryPoint == nullptr) {
|
||||
log::error("Failed to load assembly loader '{}'", assemblyLoader.string());
|
||||
log::error("Failed to load assembly loader '{}'! 0x{:X}", assemblyLoader.string(), result);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_loadAssembly = [entryPoint](const std::fs::path &path) -> bool {
|
||||
auto string = wolv::util::toUTF8String(path);
|
||||
m_runMethod = [entryPoint](const std::string &methodName, bool keepLoaded, const std::fs::path &path) -> int {
|
||||
auto pathString = wolv::util::toUTF8String(path);
|
||||
|
||||
auto string = hex::format("{}||{}||{}", keepLoaded ? "LOAD" : "EXEC", methodName, pathString);
|
||||
auto result = entryPoint(string.data(), string.size());
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
m_methodExists = [entryPoint](const std::string &methodName, const std::fs::path &path) -> bool {
|
||||
auto pathString = wolv::util::toUTF8String(path);
|
||||
|
||||
auto string = hex::format("CHECK||{}||{}", methodName, pathString);
|
||||
auto result = entryPoint(string.data(), string.size());
|
||||
|
||||
return result == 0;
|
||||
@@ -211,13 +227,19 @@ namespace hex::script::loader {
|
||||
if (!std::fs::exists(scriptPath))
|
||||
continue;
|
||||
|
||||
this->addScript(entry.path().stem().string(), [this, scriptPath] {
|
||||
hex::unused(m_loadAssembly(scriptPath));
|
||||
});
|
||||
if (m_methodExists("Main", scriptPath)) {
|
||||
this->addScript(entry.path().stem().string(), [this, scriptPath] {
|
||||
hex::unused(m_runMethod("Main", false, scriptPath));
|
||||
});
|
||||
}
|
||||
|
||||
if (m_methodExists("OnLoad", scriptPath)) {
|
||||
hex::unused(m_runMethod("OnLoad", true, scriptPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
112
plugins/script_loader/source/loaders/python/library_wrapper.cpp
Normal file
112
plugins/script_loader/source/loaders/python/library_wrapper.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#if defined(__declspec)
|
||||
#undef __declspec
|
||||
#define __declspec(x)
|
||||
#endif
|
||||
#include <Python.h>
|
||||
|
||||
#include <loaders/loader.hpp>
|
||||
|
||||
#define FUNCTION_DEFINITION(ret, name, args1, args2) \
|
||||
decltype(name) *name##Func = nullptr; \
|
||||
extern "C" ret name args1 { \
|
||||
return name##Func args2; \
|
||||
}
|
||||
#define INIT_FUNCTION(name) name##Func = hex::script::loader::getExport<decltype(name##Func)>(pythonLibrary, #name)
|
||||
|
||||
FUNCTION_DEFINITION(void, PyPreConfig_InitPythonConfig, (PyPreConfig *config), (config))
|
||||
FUNCTION_DEFINITION(PyStatus, Py_PreInitialize, (const PyPreConfig *src_config), (src_config))
|
||||
FUNCTION_DEFINITION(int, PyStatus_Exception, (PyStatus err), (err))
|
||||
FUNCTION_DEFINITION(void, Py_Initialize, (), ())
|
||||
FUNCTION_DEFINITION(void, Py_Finalize, (), ())
|
||||
FUNCTION_DEFINITION(PyInterpreterState *, PyInterpreterState_Get, (), ())
|
||||
FUNCTION_DEFINITION(PyThreadState *, PyEval_SaveThread, (), ())
|
||||
FUNCTION_DEFINITION(void, PyEval_RestoreThread, (PyThreadState * state), (state))
|
||||
FUNCTION_DEFINITION(void, PyErr_Fetch, (PyObject **ptype, PyObject **pvalue, PyObject **ptraceback), (ptype, pvalue, ptraceback))
|
||||
FUNCTION_DEFINITION(void, PyErr_NormalizeException, (PyObject **ptype, PyObject **pvalue, PyObject **ptraceback), (ptype, pvalue, ptraceback))
|
||||
FUNCTION_DEFINITION(PyObject *, PyUnicode_FromString, (const char *u), (u))
|
||||
FUNCTION_DEFINITION(PyObject *, PyImport_Import, (PyObject *name), (name))
|
||||
FUNCTION_DEFINITION(void, _Py_Dealloc, (PyObject *pobj), (pobj))
|
||||
FUNCTION_DEFINITION(PyObject *, PyModule_GetDict, (PyObject *pobj), (pobj))
|
||||
FUNCTION_DEFINITION(PyObject *, PyDict_GetItemString, (PyObject *dp, const char *key), (dp, key))
|
||||
FUNCTION_DEFINITION(int, PyCallable_Check, (PyObject *dp), (dp))
|
||||
FUNCTION_DEFINITION(PyObject *, PyTuple_New, (Py_ssize_t len), (len))
|
||||
FUNCTION_DEFINITION(int, PyTuple_SetItem, (PyObject *p, Py_ssize_t pos, PyObject *o), (p, pos, o))
|
||||
FUNCTION_DEFINITION(PyObject *, PyObject_CallObject, (PyObject *callable, PyObject *args), (callable, args))
|
||||
FUNCTION_DEFINITION(PyObject *, PyUnicode_Join, (PyObject *separator, PyObject *iterable), (separator, iterable))
|
||||
FUNCTION_DEFINITION(const char *, PyUnicode_AsUTF8, (PyObject *unicode), (unicode))
|
||||
FUNCTION_DEFINITION(void, PyErr_Clear, (), ())
|
||||
FUNCTION_DEFINITION(int, PyModule_AddStringConstant, (PyObject *module, const char *name, const char *value), (module, name, value))
|
||||
FUNCTION_DEFINITION(PyObject *, PyEval_GetBuiltins, (), ())
|
||||
FUNCTION_DEFINITION(int, PyDict_SetItemString, (PyObject *dp, const char *key, PyObject *item), (dp, key, item))
|
||||
FUNCTION_DEFINITION(PyObject *, PyRun_StringFlags, (const char *str, int start, PyObject *globals, PyObject *locals, PyCompilerFlags *flags), (str, start, globals, locals, flags))
|
||||
FUNCTION_DEFINITION(void, PyThreadState_Clear, (PyThreadState *tstate), (tstate))
|
||||
FUNCTION_DEFINITION(void, PyThreadState_DeleteCurrent, (), ())
|
||||
FUNCTION_DEFINITION(PyThreadState *, PyThreadState_New, (PyInterpreterState *interp), (interp))
|
||||
FUNCTION_DEFINITION(PyObject *, PyImport_AddModule, (const char *name), (name))
|
||||
FUNCTION_DEFINITION(PyObject *, PyModule_New, (const char *name), (name))
|
||||
FUNCTION_DEFINITION(PyObject *, PyObject_GetAttrString, (PyObject *pobj, const char *name), (pobj, name))
|
||||
FUNCTION_DEFINITION(int, PyObject_HasAttrString, (PyObject *pobj, const char *name), (pobj, name))
|
||||
FUNCTION_DEFINITION(PyObject*, PySys_GetObject, (const char *name), (name))
|
||||
FUNCTION_DEFINITION(int, PyList_Append, (PyObject *plist, PyObject *pvalue), (plist, pvalue))
|
||||
|
||||
bool initPythonLoader() {
|
||||
void *pythonLibrary = nullptr;
|
||||
for (const std::fs::path &path : { std::fs::path(PYTHON_LIBRARY_PATH), std::fs::path(PYTHON_LIBRARY_PATH).filename() }) {
|
||||
pythonLibrary = hex::script::loader::loadLibrary(path.c_str());
|
||||
if (pythonLibrary != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pythonLibrary == nullptr)
|
||||
return false;
|
||||
|
||||
INIT_FUNCTION(PyPreConfig_InitPythonConfig);
|
||||
INIT_FUNCTION(Py_PreInitialize);
|
||||
INIT_FUNCTION(Py_Initialize);
|
||||
INIT_FUNCTION(Py_Finalize);
|
||||
INIT_FUNCTION(PySys_GetObject);
|
||||
|
||||
INIT_FUNCTION(PyEval_SaveThread);
|
||||
INIT_FUNCTION(PyEval_RestoreThread);
|
||||
INIT_FUNCTION(PyEval_GetBuiltins);
|
||||
INIT_FUNCTION(PyRun_StringFlags);
|
||||
|
||||
INIT_FUNCTION(PyErr_Fetch);
|
||||
INIT_FUNCTION(PyErr_NormalizeException);
|
||||
INIT_FUNCTION(PyErr_Clear);
|
||||
INIT_FUNCTION(PyStatus_Exception);
|
||||
|
||||
INIT_FUNCTION(PyThreadState_Clear);
|
||||
INIT_FUNCTION(PyThreadState_DeleteCurrent);
|
||||
INIT_FUNCTION(PyThreadState_New);
|
||||
INIT_FUNCTION(PyInterpreterState_Get);
|
||||
|
||||
INIT_FUNCTION(PyUnicode_FromString);
|
||||
INIT_FUNCTION(PyUnicode_Join);
|
||||
INIT_FUNCTION(PyUnicode_AsUTF8);
|
||||
|
||||
INIT_FUNCTION(PyTuple_New);
|
||||
INIT_FUNCTION(PyTuple_SetItem);
|
||||
|
||||
INIT_FUNCTION(PyImport_Import);
|
||||
INIT_FUNCTION(PyImport_AddModule);
|
||||
|
||||
INIT_FUNCTION(PyModule_New);
|
||||
INIT_FUNCTION(PyModule_AddStringConstant);
|
||||
INIT_FUNCTION(PyModule_GetDict);
|
||||
|
||||
INIT_FUNCTION(PyDict_GetItemString);
|
||||
INIT_FUNCTION(PyDict_SetItemString);
|
||||
INIT_FUNCTION(PyList_Append);
|
||||
|
||||
INIT_FUNCTION(PyCallable_Check);
|
||||
INIT_FUNCTION(PyObject_CallObject);
|
||||
INIT_FUNCTION(PyObject_GetAttrString);
|
||||
INIT_FUNCTION(PyObject_HasAttrString);
|
||||
|
||||
INIT_FUNCTION(_Py_Dealloc);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
168
plugins/script_loader/source/loaders/python/python_loader.cpp
Normal file
168
plugins/script_loader/source/loaders/python/python_loader.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include <loaders/python/python_loader.hpp>
|
||||
|
||||
#include <hex/helpers/fs.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
#include <wolv/io/file.hpp>
|
||||
#include <wolv/utils/guards.hpp>
|
||||
#include <wolv/utils/string.hpp>
|
||||
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <romfs/romfs.hpp>
|
||||
|
||||
#if defined(__declspec)
|
||||
#undef __declspec
|
||||
#define __declspec(x)
|
||||
#endif
|
||||
#include <Python.h>
|
||||
|
||||
bool initPythonLoader();
|
||||
|
||||
namespace hex::script::loader {
|
||||
|
||||
static PyInterpreterState *mainThreadState;
|
||||
|
||||
bool PythonLoader::initialize() {
|
||||
if (!initPythonLoader())
|
||||
return false;
|
||||
|
||||
PyPreConfig preconfig;
|
||||
PyPreConfig_InitPythonConfig(&preconfig);
|
||||
|
||||
preconfig.utf8_mode = 1;
|
||||
|
||||
auto status = Py_PreInitialize(&preconfig);
|
||||
if (PyStatus_Exception(status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Py_Initialize();
|
||||
mainThreadState = PyInterpreterState_Get();
|
||||
PyEval_SaveThread();
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string getCurrentTraceback() {
|
||||
PyObject *ptype, *pvalue, *ptraceback;
|
||||
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
|
||||
PyErr_NormalizeException(&ptype, &pvalue, &ptraceback);
|
||||
|
||||
PyObject *pModuleName = PyUnicode_FromString("traceback");
|
||||
PyObject *pModule = PyImport_Import(pModuleName);
|
||||
Py_DECREF(pModuleName);
|
||||
|
||||
if (pModule != nullptr) {
|
||||
PyObject *pDict = PyModule_GetDict(pModule);
|
||||
PyObject *pFunc = PyDict_GetItemString(pDict, "format_exception");
|
||||
|
||||
if (pFunc && PyCallable_Check(pFunc)) {
|
||||
PyObject *pArgs = PyTuple_New(3);
|
||||
PyTuple_SetItem(pArgs, 0, ptype);
|
||||
PyTuple_SetItem(pArgs, 1, pvalue);
|
||||
PyTuple_SetItem(pArgs, 2, ptraceback);
|
||||
|
||||
PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
|
||||
Py_DECREF(pArgs);
|
||||
|
||||
if (pResult != NULL) {
|
||||
const char *errorMessage = PyUnicode_AsUTF8(PyUnicode_Join(PyUnicode_FromString(""), pResult));
|
||||
Py_DECREF(pResult);
|
||||
Py_DECREF(pModule);
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
Py_DECREF(pModule);
|
||||
}
|
||||
|
||||
PyErr_Clear();
|
||||
return "";
|
||||
}
|
||||
|
||||
void populateModule(PyObject *pyModule, const std::string &sourceCode) {
|
||||
PyModule_AddStringConstant(pyModule, "__file__", "");
|
||||
|
||||
PyObject *localDict = PyModule_GetDict(pyModule);
|
||||
PyObject *builtins = PyEval_GetBuiltins();
|
||||
|
||||
PyDict_SetItemString(localDict, "__builtins__", builtins);
|
||||
|
||||
PyErr_Clear();
|
||||
PyObject *pyValue = PyRun_String(sourceCode.c_str(), Py_file_input, localDict, localDict);
|
||||
if (pyValue != nullptr) {
|
||||
Py_DECREF(pyValue);
|
||||
} else {
|
||||
log::error("{}", getCurrentTraceback());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool PythonLoader::loadAll() {
|
||||
this->clearScripts();
|
||||
|
||||
for (const auto &imhexPath : hex::fs::getDefaultPaths(hex::fs::ImHexPath::Scripts)) {
|
||||
auto directoryPath = imhexPath / "custom" / "python";
|
||||
if (!wolv::io::fs::exists(directoryPath))
|
||||
wolv::io::fs::createDirectories(directoryPath);
|
||||
|
||||
if (!wolv::io::fs::exists(directoryPath))
|
||||
continue;
|
||||
|
||||
for (const auto &entry : std::fs::directory_iterator(directoryPath)) {
|
||||
if (!entry.is_directory())
|
||||
continue;
|
||||
|
||||
const auto &scriptFolder = entry.path();
|
||||
const auto scriptPath = scriptFolder / "main.py";
|
||||
if (!std::fs::exists(scriptPath))
|
||||
continue;
|
||||
|
||||
auto scriptPathString = wolv::util::toUTF8String(scriptPath);
|
||||
wolv::io::File scriptFile(scriptPathString, wolv::io::File::Mode::Read);
|
||||
if (!scriptFile.isValid())
|
||||
continue;
|
||||
|
||||
PyThreadState* ts = PyThreadState_New(mainThreadState);
|
||||
PyEval_RestoreThread(ts);
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
PyThreadState_Clear(ts);
|
||||
PyThreadState_DeleteCurrent();
|
||||
};
|
||||
|
||||
PyObject* sysPath = PySys_GetObject("path");
|
||||
PyList_Append(sysPath, PyUnicode_FromString(wolv::util::toUTF8String(scriptFolder).c_str()));
|
||||
|
||||
|
||||
PyObject *imhexInternalModule = PyImport_AddModule("__imhex_internal__");
|
||||
PyModule_AddStringConstant(imhexInternalModule, "script_loader_handle", hex::format("{}", reinterpret_cast<intptr_t>(hex::getContainingModule((void*)&getCurrentTraceback))).c_str());
|
||||
|
||||
PyObject *mainModule = PyModule_New(scriptPathString.c_str());
|
||||
populateModule(mainModule, scriptFile.readString());
|
||||
|
||||
if (PyObject_HasAttrString(mainModule, "main")) {
|
||||
this->addScript(entry.path().stem().string(), [mainModule] {
|
||||
PyThreadState* ts = PyThreadState_New(mainThreadState);
|
||||
PyEval_RestoreThread(ts);
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
PyThreadState_Clear(ts);
|
||||
PyThreadState_DeleteCurrent();
|
||||
};
|
||||
|
||||
auto mainFunction = PyObject_GetAttrString(mainModule, "main");
|
||||
PyObject_CallObject(mainFunction, nullptr);
|
||||
Py_DECREF(mainFunction);
|
||||
});
|
||||
}
|
||||
|
||||
m_loadedModules.push_back(mainModule);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
#include <loaders/dotnet/dotnet_loader.hpp>
|
||||
#include <loaders/python/python_loader.hpp>
|
||||
|
||||
#include <romfs/romfs.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
@@ -15,8 +16,11 @@ using namespace hex;
|
||||
using namespace hex::script::loader;
|
||||
|
||||
using ScriptLoaders = std::tuple<
|
||||
#if defined(DOTNET_PLUGINS)
|
||||
DotNetLoader
|
||||
#if defined(IMHEX_DOTNET_SCRIPT_SUPPORT)
|
||||
DotNetLoader,
|
||||
#endif
|
||||
#if defined(IMHEX_PYTHON_SCRIPT_SUPPORT)
|
||||
PythonLoader
|
||||
#endif
|
||||
>;
|
||||
|
||||
@@ -69,10 +73,10 @@ namespace {
|
||||
}
|
||||
|
||||
void addScriptsMenu() {
|
||||
static std::vector<const Script*> scripts;
|
||||
static TaskHolder runnerTask, updaterTask;
|
||||
hex::ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.extras" }, 5000, [] {
|
||||
static bool menuJustOpened = true;
|
||||
static std::vector<const Script*> scripts;
|
||||
|
||||
if (ImGui::BeginMenu("hex.script_loader.menu.run_script"_lang)) {
|
||||
if (menuJustOpened) {
|
||||
@@ -91,9 +95,9 @@ namespace {
|
||||
}
|
||||
|
||||
for (const auto &script : scripts) {
|
||||
const auto &[name, entryPoint] = *script;
|
||||
const auto &[name, entryPoint, loader] = *script;
|
||||
|
||||
if (ImGui::MenuItem(name.c_str())) {
|
||||
if (ImGui::MenuItem(name.c_str(), loader->getTypeName().c_str())) {
|
||||
runnerTask = TaskManager::createTask("Running script...", TaskManager::NoProgress, [entryPoint](auto&) {
|
||||
entryPoint();
|
||||
});
|
||||
@@ -107,10 +111,19 @@ namespace {
|
||||
}, [] {
|
||||
return !runnerTask.isRunning();
|
||||
});
|
||||
|
||||
updaterTask = TaskManager::createBackgroundTask("Updating Scripts...", [] (auto&) {
|
||||
scripts = loadAllScripts();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
IMHEX_PLUGIN_FEATURES() {
|
||||
{ ".NET", IMHEX_FEATURE_ENABLED(DOTNET) },
|
||||
{ "Python", IMHEX_FEATURE_ENABLED(PYTHON) },
|
||||
};
|
||||
|
||||
IMHEX_PLUGIN_SETUP("Script Loader", "WerWolv", "Script Loader plugin") {
|
||||
hex::log::debug("Using romfs: '{}'", romfs::name());
|
||||
for (auto &path : romfs::list("lang"))
|
||||
@@ -119,5 +132,4 @@ IMHEX_PLUGIN_SETUP("Script Loader", "WerWolv", "Script Loader plugin") {
|
||||
if (initializeAllLoaders()) {
|
||||
addScriptsMenu();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#include <script_api.hpp>
|
||||
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
#include <hex/providers/provider.hpp>
|
||||
|
||||
#define VERSION V1
|
||||
|
||||
SCRIPT_API(void readMemory, u64 address, size_t size, void *buffer) {
|
||||
auto provider = hex::ImHexApi::Provider::get();
|
||||
|
||||
if (provider == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
provider->read(address, buffer, size);
|
||||
}
|
||||
|
||||
SCRIPT_API(void writeMemory, u64 address, size_t size, const void *buffer) {
|
||||
auto provider = hex::ImHexApi::Provider::get();
|
||||
|
||||
if (provider == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
provider->write(address, buffer, size);
|
||||
}
|
||||
|
||||
SCRIPT_API(bool getSelection, u64 *start, u64 *end) {
|
||||
if (start == nullptr || end == nullptr)
|
||||
return false;
|
||||
|
||||
if (!hex::ImHexApi::Provider::isValid())
|
||||
return false;
|
||||
|
||||
if (!hex::ImHexApi::HexEditor::isSelectionValid())
|
||||
return false;
|
||||
|
||||
auto selection = hex::ImHexApi::HexEditor::getSelection();
|
||||
|
||||
*start = selection->getStartAddress();
|
||||
*end = selection->getEndAddress();
|
||||
|
||||
return true;
|
||||
}
|
||||
14
plugins/script_loader/support/c/CMakeLists.txt
Normal file
14
plugins/script_loader/support/c/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
project(c_api)
|
||||
|
||||
add_library(c_api OBJECT
|
||||
source/script_api/v1/bookmarks.cpp
|
||||
source/script_api/v1/logger.cpp
|
||||
source/script_api/v1/mem.cpp
|
||||
source/script_api/v1/ui.cpp
|
||||
)
|
||||
|
||||
target_include_directories(c_api PUBLIC
|
||||
include
|
||||
)
|
||||
target_link_libraries(c_api PRIVATE libimhex ui)
|
||||
target_compile_definitions(c_api PRIVATE IMHEX_PROJECT_NAME="Script")
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <script_api.hpp>
|
||||
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
|
||||
#define VERSION V1
|
||||
|
||||
SCRIPT_API(void logPrint, const char *message) {
|
||||
hex::log::print("{}", message);
|
||||
}
|
||||
|
||||
SCRIPT_API(void logPrintln, const char *message) {
|
||||
hex::log::println("{}", message);
|
||||
}
|
||||
|
||||
SCRIPT_API(void logDebug, const char *message) {
|
||||
hex::log::debug("{}", message);
|
||||
}
|
||||
|
||||
SCRIPT_API(void logInfo, const char *message) {
|
||||
hex::log::info("{}", message);
|
||||
}
|
||||
|
||||
SCRIPT_API(void logWarn, const char *message) {
|
||||
hex::log::warn("{}", message);
|
||||
}
|
||||
|
||||
SCRIPT_API(void logError, const char *message) {
|
||||
hex::log::error("{}", message);
|
||||
}
|
||||
|
||||
SCRIPT_API(void logFatal, const char *message) {
|
||||
hex::log::fatal("{}", message);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user