Compare commits

...

236 Commits

Author SHA1 Message Date
WerWolv
810955b1be fix: Start fixing shortcuts on welcome screen. Breaks certain shortcuts still 2026-01-23 00:07:34 +01:00
WerWolv
96a5a5d34c build: Output error message if capstone version is too low 2026-01-22 21:22:42 +01:00
WerWolv
928e0f227a fix: Banner tooltip text not being visible properly 2026-01-19 22:25:51 +01:00
WerWolv
93fc6f2de1 patterns: Update pattern language 2026-01-19 22:25:51 +01:00
iTrooz
cdc260b45c doc: add instructions to use ImHex on macOS without a GPU 2026-01-19 18:00:34 +01:00
WerWolv
0e41813cfc fix: [[format_write]] attribute not working as expected on integer and floating point types 2026-01-18 21:56:09 +01:00
WerWolv
a7e94e31c9 build: Update libwolv 2026-01-18 14:31:21 +01:00
WerWolv
2903a97941 build: Make people Acknowledge the GPLv2 in the Windows installer instead of accepting it 2026-01-18 11:32:35 +01:00
WerWolv
753e1ceff6 build: Allow MSI installer to downgrade installations 2026-01-17 21:47:11 +01:00
WerWolvTranslationBot
06f4d12f10 lang: Translations update from Weblate (#2621)
Translations update from [Weblate](https://weblate.werwolv.net) for
[ImHex/Builtin](https://weblate.werwolv.net/projects/imhex/builtin/).


It also includes following components:

* [ImHex/Diffing](https://weblate.werwolv.net/projects/imhex/diffing/)

* [ImHex/UI](https://weblate.werwolv.net/projects/imhex/ui/)

* [ImHex/Fonts](https://weblate.werwolv.net/projects/imhex/fonts/)

*
[ImHex/Disassembler](https://weblate.werwolv.net/projects/imhex/disassembler/)

* [ImHex/Hashes](https://weblate.werwolv.net/projects/imhex/hashes/)

* [ImHex/Remote](https://weblate.werwolv.net/projects/imhex/remote/)

* [ImHex/Script
Loader](https://weblate.werwolv.net/projects/imhex/script-loader/)

*
[ImHex/Visualizers](https://weblate.werwolv.net/projects/imhex/visualizers/)

* [ImHex/Yara
Rules](https://weblate.werwolv.net/projects/imhex/yara-rules/)

* [ImHex/Windows](https://weblate.werwolv.net/projects/imhex/windows/)



Current translation status:

![Weblate translation
status](https://weblate.werwolv.net/widgets/imhex/-/builtin/horizontal-auto.svg)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Rutar Andriy <RutarAndriy@gmail.com>
Co-authored-by: Yaroslav <mrikso821@gmail.com>
2026-01-17 21:45:41 +01:00
WerWolv
684c2e66fb fix: Ranges are still not available everywhere 2026-01-17 21:13:04 +01:00
WerWolv
4a311ed69f fix: Multiple issues with undo/redo stack handling 2026-01-17 21:03:36 +01:00
WerWolv
220b5f9772 patterns: Update pattern language 2026-01-15 20:47:29 +01:00
WerWolv
42fd5b0fef patterns: Update pattern language 2026-01-15 20:39:04 +01:00
WerWolv
c28492e51d fix: GLFW version check 2026-01-13 18:22:06 +01:00
WerWolv
d8ff84672c fix: Gate Wayland check to good distros that update their dependencies 2026-01-13 18:09:21 +01:00
WerWolv
2af967d788 fix: Window auto-maximizing on Wayland 2026-01-13 18:02:48 +01:00
WerWolv
8cb23e8200 build: Don't build test engine if it's disabled 2026-01-13 18:02:27 +01:00
WerWolvTranslationBot
9a058efc79 lang: Translations update from Weblate (#2553)
Translations update from [Weblate](https://weblate.werwolv.net) for
[ImHex/Builtin](https://weblate.werwolv.net/projects/imhex/builtin/).


It also includes following components:

* [ImHex/Diffing](https://weblate.werwolv.net/projects/imhex/diffing/)

* [ImHex/UI](https://weblate.werwolv.net/projects/imhex/ui/)

*
[ImHex/Disassembler](https://weblate.werwolv.net/projects/imhex/disassembler/)

* [ImHex/Yara
Rules](https://weblate.werwolv.net/projects/imhex/yara-rules/)

* [ImHex/Fonts](https://weblate.werwolv.net/projects/imhex/fonts/)

* [ImHex/Hashes](https://weblate.werwolv.net/projects/imhex/hashes/)

* [ImHex/Remote](https://weblate.werwolv.net/projects/imhex/remote/)

* [ImHex/Script
Loader](https://weblate.werwolv.net/projects/imhex/script-loader/)

*
[ImHex/Visualizers](https://weblate.werwolv.net/projects/imhex/visualizers/)

* [ImHex/Windows](https://weblate.werwolv.net/projects/imhex/windows/)



Current translation status:

![Weblate translation
status](https://weblate.werwolv.net/widgets/imhex/-/builtin/horizontal-auto.svg)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: MrClock <mrclock8163@gmail.com>
2026-01-12 23:26:15 +01:00
WerWolv
06c8cb51e3 web: Handle partial page resizes better 2026-01-12 22:59:18 +01:00
WerWolv
01e8ae1b5c ci: Don't upload docker build record as artifact 2026-01-12 22:59:18 +01:00
WerWolv
b052cb001c web: Make sure canvas gets resized after the first frame 2026-01-12 22:59:18 +01:00
WerWolv
d004eb3048 fix: Occasional crash on Linux when closing task manager 2026-01-12 20:43:01 +01:00
WerWolv
dccd74667e fix: Texture ids not getting reset correctly in destructor 2026-01-12 20:42:23 +01:00
WerWolv
6b6470850f ci: Make sure folder exists before copying files 2026-01-11 10:12:28 +01:00
WerWolv
f09b1aae23 ci: Fix artifact names 2026-01-11 09:52:34 +01:00
WerWolv
68487bc903 web: Fix meta tags and social preview image 2026-01-10 21:43:45 +01:00
WerWolv
ebd78e1526 ci: Update github pages deployment name 2026-01-10 14:28:07 +01:00
WerWolv
6d0dfcfe2e fix: Does WebGL use GLES2 after all? 2026-01-10 14:15:56 +01:00
WerWolv
5b01d23ed7 ci: Make docker image deploy only run after build 2026-01-10 14:12:02 +01:00
WerWolv
d9eb01b526 build: Deploy ImHex-Web as a docker image 2026-01-10 14:09:58 +01:00
WerWolv
9e165ac4a1 impr: Add more broader support for GLES3
Closes #2608
2026-01-10 11:49:58 +01:00
Nik
25ab371a81 git: Fix macOS packaging job name 2026-01-09 21:04:27 +01:00
Nik
d62abaed8d build: Add support for x86 macOS 10.15 and ARM64 macOS 11.0 (#2607)
This PR drops the use of brew for dependency management in favor of
macports so we can support lower macOS versions instead of just the
lowest one supported by Apple

Closes #2586
2026-01-09 17:42:51 +01:00
iTrooz
1fb7a318ed chore: remove jthread added inadvertently 2026-01-09 16:49:43 +01:00
iTrooz
e29ae631fc ci: always take artifacts from master branch in nightly release 2026-01-09 16:43:18 +01:00
WerWolv
3cc0b9294e web: Fix flickering on resize 2026-01-08 21:45:36 +01:00
WerWolv
e80f7fa14f git: Properly define env var for dispatch key 2026-01-08 20:37:35 +01:00
WerWolv
9261fd7190 git: Fix dispatch never running 2026-01-08 20:29:20 +01:00
WerWolv
58c714b350 git: Switch to repository dispatch for updating website 2026-01-08 20:05:41 +01:00
WerWolv
5cd3f5c255 git: Fix permissions again 2026-01-08 19:42:47 +01:00
WerWolv
0d443d7d00 git: Fix permissions on nightly CI 2026-01-08 19:20:13 +01:00
WerWolv
38fecca489 git: Fix nightly CI 2026-01-08 17:53:44 +01:00
WerWolv
f655ea59a8 git: Update new ImHex website during nightly 2026-01-07 23:18:06 +01:00
WerWolv
aa7daa5c54 web: Disable splash screen 2026-01-07 23:09:07 +01:00
WerWolv
f4b2be9334 chore: Cleanup logs slightly 2026-01-07 21:27:41 +01:00
WerWolv
edbd8a811e fix: Just detach stdin/stdout redirect threads as the program will exit immediately afterwards
(cherry picked from commit aeb58b5e9c3eef9f26fde104a2adc2684423b8ac)
2026-01-07 18:32:16 +01:00
Alex
ea9b197d36 build: improve FindMagic.cmake module (#2610)
Now it properly fails if libmagic is not installed in the system.

The error message would look like:

```
CMake Error at cmake/modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find Magic (missing: LIBMAGIC_LIBRARY LIBMAGIC_INCLUDE_DIR)
  (Required is at least version "5.39")
Call Stack (most recent call first):
  cmake/modules/FindPackageHandleStandardArgs.cmake:600 (_FPHSA_FAILURE_MESSAGE)
  cmake/modules/FindMagic.cmake:5 (find_package_handle_standard_args)
  cmake/build_helpers.cmake:971 (find_package)
  CMakeLists.txt:81 (addBundledLibraries)
```

### Problem description

The current `FindMagic.cmake` module doesn't properly report failure
about absent libmagic libs and #includes. This behavior leads to the
compile errors on the build stage (possibly after some time, which will
be simply wasted). This Find module should set the `Magic_FOUND`
variable and handle all required variables( LIBMAGIC_INCLUDE_DIR and
LIBMAGIC_LIBRARY) to present.
But the current module sets the `libmagic_FOUND` variable, so the CMake
assumes that it is found something. This behaviour should also be
handled by `find_package_handle_standard_args()` ("FPHSA"), and it is
actually handled, but as we requested the `Magic` package to be
required, the former function ("FPHSA") didn't stop the configure
process.

### Implementation description
 - Clean up the unused ifs, as "FPHSA" already done it better.
- Use the proper name for "FPHSA" so it could set the proper
`Magic_FOUND` and could make sure that the requested package is
`REQUIRED`.

### Screenshots
Before:
```
-- Could NOT find libmagic (missing: LIBMAGIC_LIBRARY LIBMAGIC_INCLUDE_DIR)
```

<img width="1005" height="24" alt="изображение"
src="https://github.com/user-attachments/assets/082e2842-ff94-4418-8f86-86021c7dd23d"
/>

After:

```
CMake Error at cmake/modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find Magic (missing: LIBMAGIC_LIBRARY LIBMAGIC_INCLUDE_DIR)
  (Required is at least version "5.39")
Call Stack (most recent call first):
  cmake/modules/FindPackageHandleStandardArgs.cmake:600 (_FPHSA_FAILURE_MESSAGE)
  cmake/modules/FindMagic.cmake:5 (find_package_handle_standard_args)
  cmake/build_helpers.cmake:971 (find_package)
  CMakeLists.txt:81 (addBundledLibraries)
```

### Additional things

Closes #2244 .
2026-01-07 17:13:41 +01:00
WerWolv
a0a049a920 fix: ImHex not always exiting when forwarder is killed 2026-01-07 17:12:58 +01:00
WerWolv
550fe8e4aa impr: Add MCP Client information to footer icon tooltip 2026-01-07 17:12:40 +01:00
WerWolv
2064aea3b6 fix: ImHex processes getting stuck in the background
Fixes #2611
2026-01-07 17:12:17 +01:00
WerWolv
3411bc4577 fix: Unused variable 2026-01-07 11:13:52 +01:00
WerWolv
6eedb469e9 fix: Notifications on Web build causing exceptions 2026-01-07 09:45:30 +01:00
WerWolv
684373b88b fix: Properly forward stdin to main process from forwarder
(cherry picked from commit 1fc857cf7d2a9a525dec5fcda0727a39046b6c86)
2026-01-07 09:45:30 +01:00
iTrooz
08aa03bab6 chore: remove unused steps from nightly release CI 2026-01-06 21:05:52 +01:00
iTrooz
62bc953d53 ci: remove unused permissions to webassembly-build job 2026-01-06 09:28:20 +01:00
iTrooz
5263f81487 ci: do not give pages write permission to webassembly-build job 2026-01-06 09:23:30 +01:00
iTrooz
8272ebf68f ci: use gh CLI to download artifact instead of third party action 2026-01-06 09:13:47 +01:00
paxcut
90d8e03f2c Fixes by @AkiSakurai for bug thats caused crashing ImHex when creating recursive inheritances. (#2612)
The previous code I approved was wrong and caused ImHex to grow until
computer froze. This PR is a copy of the original fix in PR #2546 by
@AkiSakurai
2026-01-05 16:57:04 -07:00
iTrooz
0d34edc7f6 ci: replace artifact deletion action with custom script 2026-01-05 16:36:30 +01:00
Aki Sakurai
63fd61e245 Fix crash in Pattern Language syntax highlighting for self-inheriting structs (#2546)
``` rust
struct Rec : Rec {
};
```

Prevent infinite recursion in appendInheritances by erasing processed
inheritance entries during traversal, and safely iterate over
m_inheritances in appendInheritances to avoid
modification-during-iteration issues.

Co-authored-by: paxcut <53811119+paxcut@users.noreply.github.com>
2026-01-05 03:11:21 -07:00
iTrooz
58fe8a22a9 ci: use hendrikmuhs/ccache-action@v1, not @main 2026-01-05 08:21:21 +01:00
WerWolv
03dd20c263 web: Fix canvas size issues when zooming page 2026-01-04 11:07:17 +01:00
WerWolv
712a125be7 fix: listxattrs signature being different between Linux and macOS 2026-01-03 23:52:42 +01:00
WerWolv
49e14e6e7c build: Update dependencies 2026-01-03 22:44:46 +01:00
WerWolv
e8a6e102c3 feat: Add xatts to file info display 2026-01-03 22:43:34 +01:00
WerWolv
b2cc09852d fix: Weirdly worded pattern editor help message 2026-01-03 22:40:45 +01:00
WerWolv
60627b8325 impr: Only add pattern editor help text to first opened data source 2026-01-03 22:38:36 +01:00
WerWolv
8fc2d6b225 impr: Disable exception wrapping as it causes a lot of slowdowns 2026-01-03 20:41:17 +01:00
WerWolv
051cdfa305 fix: Error message rendering of visualizers 2026-01-03 13:27:52 +01:00
WerWolv
bfa807ca8b feat: Add data inspector option to reverse bit order 2026-01-03 12:04:51 +01:00
WerWolv
598914a67a fix: Windows notifications filling up tray area 2026-01-03 11:04:33 +01:00
WerWolv
3274649b77 fix: Task finished notification showing up regardless of focus state on Windows 2026-01-03 10:48:46 +01:00
paxcut
5756105347 fix: Text highlighter crash (#2595)
The assumption that the number of lines of colors will be equal to the
number of lines in the input signal is incorrect. As issue #2594 shows,
erroneous input can cause the lexer to end processing the input file
prematurely thus being unable to create tokens past the line where the
error occurred which in turn implies that no colors can be found beyond
those lines.

To fix the crash (the underlying problem is user caused and can't be
fixed) is to use the size of the vectors containing the first token
index of each line since that size must be equal to the number of lines
stored in token sequence.

Fixes #2594
2026-01-02 16:04:35 +01:00
paxcut
ed583d8bd1 fix: Division by zero in sound visualizer. (#2593)
Can happen if input is less than 2400*channels number of points. The fix
is detecting it and throwing an error.
2026-01-02 16:03:52 +01:00
WerWolv
731cf10207 fix: Goto setting hex editor scroll position to imprecise value for large addresses
#2599
2026-01-02 15:36:32 +01:00
iTrooz
cb898ce8cf ci: remove Fedora 41, 42 builds and AlmaLinux 9 2026-01-01 18:27:34 +01:00
iTrooz
892334e31a ci: update workflow to manually download cache key 2026-01-01 16:34:52 +01:00
iTrooz
a16e16853f ci: add workflow to manually download cache key 2026-01-01 02:08:52 +01:00
WerWolv
5cec83b3ef web: Render at higher resolution 2025-12-30 19:38:38 +01:00
WerWolv
a628784c6d fix: Empty entry showing up in main menu bar 2025-12-30 19:12:17 +01:00
WerWolv
fd8b70fb12 fix: Settings not always applying correctly 2025-12-30 19:12:00 +01:00
WerWolv
3dad5a43a1 web: Resize canvas right away 2025-12-30 19:11:33 +01:00
WerWolv
89dea86b3b web: Fix canvas being too big 2025-12-30 18:28:26 +01:00
WerWolv
42da24e31d patterns: Update pattern language
Fixes #2589
2025-12-30 14:51:10 +01:00
WerWolv
5b9b5d4f1f fix: Another use of ssize_t 2025-12-30 14:35:56 +01:00
paxcut
5332a26294 fixing isEmpty again. (#2591)
hopefully the last time.
2025-12-30 06:08:18 -07:00
WerWolv
54d9f8ec5c fix: Build on MSVC, properly handle empty files 2025-12-30 10:30:14 +01:00
WerWolv
73a17308cc feat: Allow opening special files with no specific size 2025-12-30 09:34:45 +01:00
paxcut
b835c48a0c fix: Unable to delete empty lines in text editor (#2588)
fixes for issue #2587
2025-12-29 20:01:26 +01:00
WerWolv
40b604c6e4 fix: ImGui-internal shortcuts not working correctly with native macOS menu bar enabled
(cherry picked from commit f0f6a22391)
2025-12-29 19:32:25 +01:00
WerWolv
1fd3580f97 web: Let ImHex Web run more standalone
(cherry picked from commit f76ea2a677)
2025-12-29 19:32:25 +01:00
WerWolv
e28f3b75a4 fix: Make sure updater properly exists after launching update process 2025-12-29 13:35:44 +01:00
WerWolv
53153ca3e0 fix: Merge messup 2025-12-29 13:27:25 +01:00
WerWolv
a496b14a0f impr: Add setting for task-finish notifications 2025-12-28 18:17:38 +01:00
WerWolv
da6e7240d6 fix: Race condition causing old patterns to stick around in the pattern drawer 2025-12-28 17:46:22 +01:00
WerWolv
89981c6994 git: Add x86_64 to the remaining builds that don't have that specification 2025-12-28 12:52:09 +01:00
WerWolv
fe22a43e09 git: Remove Ubuntu 25.04, add Ubuntu 25.10 and Debian 13 2025-12-28 12:33:53 +01:00
WerWolv
3e1a797ea7 impr: Replace Patreon links with Ko-Fi 2025-12-27 17:36:26 +01:00
WerWolv
b23ce7ba18 impr: Replace achievement icons with font icons 2025-12-27 12:00:58 +01:00
WerWolv
6165f891ca fix: Get rid of std::common_type in settings API 2025-12-27 10:31:28 +01:00
WerWolv
7df4b1157c impr: Make sure all data is received by MCP bridge 2025-12-27 10:21:38 +01:00
paxcut
64cbd5fc8d Make the syntax highlighter more thread safety aware (#2585)
By creating copies of the required inputs on the main thread just before
the task is spawned. A;so if task is still running when new data can be
copied then the task is interrupted thus avoiding concurrency without
mutexes. Atomics are used to signal state information used to determine
what and when to spawn.

Also includes update to pattern editor library and some fixes to syntax
highlighting error when custom types defined inside namespaces were used
inside the namespaces without the full qualified name and other small
changes mostly to improve the current style.
2025-12-26 20:21:19 -07:00
WerWolv
ba7e789a80 feat: Add support for executing patterns using MCP 2025-12-26 22:33:05 +01:00
WerWolv
d6d70ca076 impr: Don't show crash popup when sending ^C to command line 2025-12-26 22:33:05 +01:00
WerWolv
88c37bb7d9 impr: Handle MCP error when no instance of ImHex is running better 2025-12-26 22:33:05 +01:00
WerWolv
59b4f4efce impr: Add abstraction for common variable-saved-as-setting code pattern 2025-12-26 22:33:05 +01:00
Jacques Pienaar
ed1f120b0b build: Remove --no-lock flag from macOS build instructions (#2584)
### Problem description
-no-lock CLI options has been removed
https://github.com/Homebrew/homebrew-bundle/pull/1509.

### Implementation description
Remove flag from invocation.
2025-12-25 21:24:49 +01:00
WerWolv
bf461abfec fix: Invalid toolbar items appearing in the toolbar 2025-12-25 20:12:31 +01:00
WerWolv
d42665db88 fix: Crash on exit 2025-12-25 20:00:17 +01:00
WerWolv
33c4dc3347 impr: Use settings icon as everywhere else for hex editor settings 2025-12-25 20:00:17 +01:00
WerWolv
6d976fb785 fix: Pattern Editor content getting reset to default comment when opening new data source 2025-12-25 20:00:17 +01:00
iTrooz
39b43cec2d fix: correctly compute nameSpace in TextHighlighter::getVariableTypeName() 2025-12-25 18:48:00 +01:00
WerWolv
691b56b4ac impr: Show OS toast popup when a task finishes while ImHex is not focused 2025-12-25 15:34:56 +01:00
WerWolv
bf1f613052 patterns: Update pattern language 2025-12-24 20:20:30 +01:00
WerWolv
721ed9f2a2 patterns: Update pattern language 2025-12-24 17:00:37 +01:00
WerWolv
f760b1ba83 impr: Add help text to MCP server setting 2025-12-24 14:00:26 +01:00
WerWolv
1dba144fca fix: Various warnings related to AutoReset variables 2025-12-23 23:29:59 +01:00
WerWolv
ebc1b531ff fix: Pattern children not getting sorted properly 2025-12-23 23:29:35 +01:00
WerWolv
5ecf122686 fix: Hidden patterns getting used for pattern parent highlighting 2025-12-23 22:38:00 +01:00
WerWolv
56ed18882c patterns: Update pattern language 2025-12-23 22:36:44 +01:00
WerWolv
86c555d053 fix: Theme view not being scrollable anymore 2025-12-23 15:56:54 +01:00
WerWolv
49ec30899e feat: Give macOS window a Liquid Glass look when the background is set to transparent 2025-12-23 15:56:45 +01:00
WerWolv
d4a2b617bd fix: Another MSVC build error 2025-12-23 11:12:20 +01:00
WerWolv
8e7bd4b98a fix: File provider not erroring out correctly when selecting a folder to open 2025-12-23 10:51:52 +01:00
WerWolv
ea359285e0 build: Fix run configuration 2025-12-22 23:08:36 +01:00
WerWolv
d4bfa5d284 build: Generate a macOS bundle by default 2025-12-22 22:57:33 +01:00
WerWolv
4433006842 fix: MSVC build 2025-12-22 18:09:06 +01:00
WerWolv
646ebcdd00 feat: Add create and open options to macOS dock icon context menu 2025-12-22 16:20:23 +01:00
WerWolv
8b53b36b20 fix: Exit code getting in forwarder not working 2025-12-22 12:53:39 +01:00
WerWolv
64db392699 patterns: Update pattern language 2025-12-22 11:12:22 +01:00
WerWolv
bff78704cc fix: Use of non-atomic variables 2025-12-22 11:12:22 +01:00
iTrooz
40651e8dfd chore: update my email 2025-12-21 20:55:57 +01:00
iTrooz
e5d9d9ec9e chore: apply more complicated lints (#2576)
<!--
Please provide as much information as possible about what your PR aims
to do.
PRs with no description will most likely be closed until more
information is provided.
If you're planing on changing fundamental behaviour or add big new
features, please open a GitHub Issue first before starting to work on
it.
If it's not something big and you still want to contact us about it,
feel free to do so !
-->

### Problem description
<!-- Describe the bug that you fixed/feature request that you
implemented, or link to an existing issue describing it -->

### Implementation description
<!-- Explain what you did to correct the problem -->

### Screenshots
<!-- If your change is visual, take a screenshot showing it. Ideally,
make before/after sceenshots -->

### Additional things
<!-- Anything else you would like to say -->
2025-12-21 20:55:50 +01:00
WerWolv
4e628826c3 fix: Forwarder not correctly returning return code on Windows 2025-12-21 20:53:07 +01:00
WerWolv
018bedb2bd patterns: Update pattern language 2025-12-21 20:52:46 +01:00
WerWolv
33e315709a git: Fix AUR release CI not having repo available anymore
(cherry picked from commit 34bc55a648)
2025-12-21 20:10:03 +01:00
WerWolv
8b14a4775b build: Disable ARM msi signing because it doesn't work
(cherry picked from commit 76cf877115)
2025-12-21 20:10:03 +01:00
WerWolv
fa8fdb0170 build: Don't fail release CI if tag is empty
(cherry picked from commit 8e4ccef52f)
2025-12-21 20:10:03 +01:00
WerWolv
f856e16917 fix: Duplicate artifact name in release CI
(cherry picked from commit b78a1024c1)
2025-12-21 20:10:03 +01:00
WerWolv
3b5271ab77 patterns: Update pattern language 2025-12-21 13:02:42 +01:00
WerWolv
428fbddbbb impr: Better updater experience on macOS 2025-12-21 11:47:21 +01:00
WerWolv
5774837a6e fix: Updater architecture check on MSVC 2025-12-21 10:54:14 +01:00
iTrooz
17c2dfcbd0 chore: apply more light lints (#2575)
<!--
Please provide as much information as possible about what your PR aims
to do.
PRs with no description will most likely be closed until more
information is provided.
If you're planing on changing fundamental behaviour or add big new
features, please open a GitHub Issue first before starting to work on
it.
If it's not something big and you still want to contact us about it,
feel free to do so !
-->

### Problem description
<!-- Describe the bug that you fixed/feature request that you
implemented, or link to an existing issue describing it -->

### Implementation description
<!-- Explain what you did to correct the problem -->

### Screenshots
<!-- If your change is visual, take a screenshot showing it. Ideally,
make before/after sceenshots -->

### Additional things
<!-- Anything else you would like to say -->
2025-12-20 15:59:48 +01:00
WerWolv
a1711ccfa6 impr: Added icons to all menu items on macOS 2025-12-20 14:07:31 +01:00
iTrooz
261610dcf1 chore: apply light lints (#2570) 2025-12-19 23:49:37 +01:00
WerWolv
92cfdf1145 fix: Fedora 43 not being updatable
(cherry picked from commit 5a853569e6)
2025-12-19 19:17:13 +01:00
WerWolv
155465b8c6 fix: Linux aarch64 detection in the updater 2025-12-19 18:43:02 +01:00
WerWolv
6f49bbdd41 fix: Updater not detecting architecture on Windows correctly 2025-12-19 18:13:39 +01:00
iTrooz
3badaa5cba ci: cancel old workflows when pushing a new commit 2025-12-19 13:38:17 +01:00
iTrooz
a66747a0d0 ci: build dependencies in different step than configuring cmake (#2574)
<!--
Please provide as much information as possible about what your PR aims
to do.
PRs with no description will most likely be closed until more
information is provided.
If you're planing on changing fundamental behaviour or add big new
features, please open a GitHub Issue first before starting to work on
it.
If it's not something big and you still want to contact us about it,
feel free to do so !
-->

### Problem description
<!-- Describe the bug that you fixed/feature request that you
implemented, or link to an existing issue describing it -->

### Implementation description
<!-- Explain what you did to correct the problem -->

### Screenshots
<!-- If your change is visual, take a screenshot showing it. Ideally,
make before/after sceenshots -->

### Additional things
<!-- Anything else you would like to say -->
2025-12-19 12:26:07 +00:00
iTrooz
c376759be0 chore: update invalid comments in arm64 Dockerfile 2025-12-19 12:19:03 +01:00
iTrooz
1c17f3ee43 fix: DiskProvider::open() default result on Windows (#2573)
<!--
Please provide as much information as possible about what your PR aims
to do.
PRs with no description will most likely be closed until more
information is provided.
If you're planing on changing fundamental behaviour or add big new
features, please open a GitHub Issue first before starting to work on
it.
If it's not something big and you still want to contact us about it,
feel free to do so !
-->

### Problem description
<!-- Describe the bug that you fixed/feature request that you
implemented, or link to an existing issue describing it -->

### Implementation description
<!-- Explain what you did to correct the problem -->

### Screenshots
<!-- If your change is visual, take a screenshot showing it. Ideally,
make before/after sceenshots -->

### Additional things
<!-- Anything else you would like to say -->
2025-12-19 11:12:08 +00:00
paxcut
5f549cc8aa patterns: update pattern language (#2572)
for the ghost break point bug
2025-12-19 03:29:52 -07:00
WerWolv
f97be02087 impr: Added proper cancel buttons to tutorial popups and tutorial selector
Closes #2571
2025-12-19 10:13:48 +01:00
WerWolv
2d82776e62 fix: Disk provider still using old open() api on windows 2025-12-17 23:42:13 +01:00
WerWolv
fdee0ac3e3 fix: Test Provider still using old open function signature 2025-12-17 16:56:55 +01:00
WerWolv
d775b80a44 feat: Add initial MCP commands to query, open select and read data 2025-12-17 16:04:07 +01:00
iTrooz
2047a41498 chore: use cleaner yaml format for clang-tidy 2025-12-17 16:02:06 +01:00
iTrooz
f88890a052 chore: disable all clang-tidy checks in third_party libraries 2025-12-17 16:02:06 +01:00
WerWolv
5500faa57e fix: Inverted logic when opening files from the command line 2025-12-17 13:50:57 +01:00
WerWolv
89004574d3 impr: Handle provider opening more centrally, switch to existing provider if same file is being opened again 2025-12-17 12:55:34 +01:00
iTrooz
c11c05a399 build: do not bundle SDK in snap and flatpak packages (#2567) 2025-12-17 11:31:35 +01:00
WerWolv
baa3329e7f fix: Make sure providers returned by createProvider don't get deleted unexpectedly 2025-12-16 23:36:05 +01:00
WerWolv
e696d384c2 feat: Add initial MCP Server support 2025-12-16 20:25:46 +01:00
WerWolv
932c281223 fix: Pattern files not getting truncated correctly when saving
Fixes #2566
2025-12-16 10:02:10 +01:00
WerWolv
858fe0384e impr: Make most windows non-scrolling by default 2025-12-15 21:06:44 +01:00
WerWolv
e904cd749f fix: Inverted sorting of find view table
Fixes #2564
2025-12-15 20:13:19 +01:00
WerWolv
6b16f39be4 impr: Allow tutorials to use markdown formatted text 2025-12-15 20:07:43 +01:00
WerWolv
021c7e5fdb impr: Add localization option to store long, formatted texts in external files 2025-12-15 20:07:18 +01:00
WerWolv
c161a5c71b fix: Typo in crash popup 2025-12-15 11:31:41 +01:00
WerWolv
76ccdbccea patterns: Update pattern language 2025-12-15 10:10:15 +01:00
WerWolv
553ee89787 fix: Only enable widgets in pattern data view when there's actually any patterns available 2025-12-15 10:00:29 +01:00
WerWolv
cb6247b16e fix: Crash when using @ command palette command
Fixes #2563
2025-12-15 09:52:44 +01:00
WerWolv
cfac7ff0ba impr: Unionize exception and assertion handling 2025-12-15 09:52:13 +01:00
iTrooz
49bbe7dc77 build: remove IMHEX_PLUGINS_IN_SHARE option + only allow AppImage to load plugins from inside itself
Rationale: The `IMHEX_PLUGINS_IN_SHARE` is a hack to prevent the appimage from loading plugin from system imhex installation, like /usr/lib/imhex/

In reality, I do not think people compile plugins specifically for the AppImage (plugins must be compiled for the specific imhex & compiler version the imhex binary is used), and this lets us remove the hack
2025-12-14 18:29:00 +01:00
iTrooz
07b6fa0e2e build(web): add BUILD_TYPE arg to Dockerfile 2025-12-14 15:02:46 +01:00
iTrooz
67396f2009 chore: fix web Dockerfile ARG syntax 2025-12-14 15:02:46 +01:00
iTrooz
a20ff87cc9 chore: update comment 2025-12-14 15:02:46 +01:00
iTrooz
e02e57a729 chore: remove version attribute from web compose.yaml 2025-12-14 14:19:15 +01:00
iTrooz
225dc53795 build(appimage): use https when querying repos 2025-12-14 02:02:40 +01:00
iTrooz
e7404376db build: do not bundle plugin SDK in AppImage 2025-12-14 01:55:57 +01:00
iTrooz
d6aec341fe build(web): make a shallow clone of vcpkg 2025-12-14 01:43:40 +01:00
iTrooz
3a3c2fb204 build: add defaults to AppImage build for x86 2025-12-14 00:45:56 +01:00
iTrooz
e9b5cdbccf build: fix IMHEX_INCLUDE_PLUGINS defined check 2025-12-14 00:15:08 +01:00
iTrooz
3f30e63d95 chore(web): allow nginx to access files in development docker image 2025-12-14 00:08:11 +01:00
iTrooz
f9c6866c7b build: require all plugins that builtint depends on to be present 2025-12-13 23:54:58 +01:00
iTrooz
388dccfd9f chore: organise cmake build flags 2025-12-13 23:46:13 +01:00
paxcut
1676342e28 Various fixes for pattern editor (#2561)
- Fix for vertical scroll bar being too far to the left.
- Fix constructor not initializing from const char pointer properly
- maxcolumn not being set for console text lines causing crashes on
empty pattern evaluation
- A replacement using replace all is now undone in one step.
- Find/replace no longer need to have enter or return key to accept
text. You can use arrows or shortcuts.
- More efficient search replace implementation with plans to add even
faster.
- Tooltips added to find/replace window
- Providers now save both horizontal and vertical scroll positions when
switching to another one and restore them when switching back. This is
independent to the cursor position which is also saved.
- Pattern editor no longer takes focus when changing providers via a tab
click. This has the effect that menus won't change by just clicking on a
tab.
- Small fixes and code refactoring.
2025-12-13 05:23:16 -07:00
WerWolv
62732de227 fix: Gaps in-between hex editor highlighting on specific scalings 2025-12-12 22:04:15 +01:00
WerWolv
63e777c84c impr: Intercept glibc++ assertion handler 2025-12-12 22:02:56 +01:00
WerWolv
ab95cdf3e5 fix: Minimap not allowing scrolling as far as the scroll bar 2025-12-12 17:19:57 +01:00
WerWolv
827b5b01dd patterns: Update pattern language 2025-12-12 16:57:20 +01:00
paxcut
bfa9788099 impr: Various fixes and improvements to the pattern editor (#2559)
- fixed crash when utf8 chars were present in text editor
- fixed unable to scroll when cursor at line 1
- removed dependencies on thext editor that were not being used.

I had to go back to the old code (old for me) and fit in the changes
that were applied to the new code.That was only possible by
incorporating some of the new structural differences to the text editor.
This created new bugs and crashes that I ve have fixed but there may be
ones that I couldn't find in the very small amount of time I could spend
testing so that this commit wouldn't be delayed. If more crashes are
found due to the mixing of old and new code they should be resolved when
the new code is brought in.
2025-12-12 16:27:26 +01:00
WerWolv
de25ce7fbb feat: Add support for custom inspector edit widgets 2025-12-12 13:15:16 +01:00
WerWolv
21e61bfce6 fix: Extended ASCII display being enabled by default 2025-12-12 13:14:49 +01:00
WerWolv
82e168c438 build: Update libwolv 2025-12-12 13:14:27 +01:00
WerWolv
48583a2b6e build: Go back to WiX 4 again for ARM64 support 2025-12-11 23:41:37 +01:00
WerWolv
0db0982fa7 build: Update dependencies 2025-12-11 23:15:13 +01:00
WerWolv
6a28ce9e4b fix: Wrong variable access 2025-12-11 21:32:18 +01:00
WerWolv
1db79f6117 feat: Add command line arguments to process tooltips, exclude kthreads on Linux
Fixes #2558
2025-12-11 17:09:26 +01:00
WerWolv
f234103320 fix: Off-by-one of starts of process memory regions 2025-12-11 16:27:36 +01:00
WerWolv
45c382a19a fix: Auto backup localization key names 2025-12-11 16:27:13 +01:00
WerWolv
fb7ef61d06 fix: Crash when canceling creation of SSH provider
Fixes #2557
2025-12-11 16:24:43 +01:00
WerWolv
2586645d02 build: Force-set REINSTALLMODE=amus for WiX installer 2025-12-11 12:34:29 +01:00
WerWolv
e23cb5509d build: Use WiX 3 for packaging 2025-12-11 11:13:26 +01:00
WerWolv
5cbd53ae7a build: Fix version stripping issues 2025-12-09 21:58:29 +01:00
WerWolv
a4ee590875 build: Only build the version stripper on mingw 2025-12-09 21:31:50 +01:00
WerWolv
495608ed7c build: Force-remove all version information from libwinpthread 2025-12-09 21:25:35 +01:00
WerWolv
4d10d9a195 build: Manually set FILEVERSION of libwinpthread to 0.0.0.0
#2550
2025-12-08 23:54:33 +01:00
WerWolv
4914a34dd9 build: Fix WiX patch 2025-12-08 21:26:18 +01:00
WerWolv
ab0fb3131d fix: Reset selected row after checking it 2025-12-08 21:10:49 +01:00
WerWolv
7922d3b3cb build: Try to force-overwrite libwinpthread 2025-12-08 18:17:33 +01:00
WerWolv
6427f53b5a feat: Add endian option to Sum hash 2025-12-07 22:18:34 +01:00
WerWolv
994df0a3a4 feat: Add shortcut to directly search for the selected bytes 2025-12-07 22:17:57 +01:00
WerWolv
e6eee55810 fix: Editing of WString, String16, String32 in data inspector 2025-12-07 21:44:33 +01:00
WerWolv
855e4c4913 feat: Add option to create auto backups of files before they're modified 2025-12-07 21:37:14 +01:00
WerWolv
c2e07bf7b2 feat: Added data inspector shortcut to toggle endianness 2025-12-07 20:58:30 +01:00
WerWolv
77b9e3eac8 impr: Allow Esc to clear editing and selected state in data inspector 2025-12-07 20:58:17 +01:00
WerWolv
790487eea6 impr: If there's multiple foreground highlighting providers, only evaluate until a color is found 2025-12-07 20:48:16 +01:00
WerWolv
eb83354179 feat: Add option to automatically apply found pattern when a provider is opened 2025-12-07 20:47:33 +01:00
WerWolv
9ba8754f97 build: Remove file version from main executable to make msi installer not skip it 2025-12-07 17:57:21 +01:00
WerWolv
84346119b3 fix: Missing includes 2025-12-07 17:22:26 +01:00
WerWolv
2b3abd06db build: Fix deb package referring to incorrect md4c library package
Fixes #2548
2025-12-07 16:25:15 +01:00
WerWolv
8267aad79e feat: Add new Command Line data source 2025-12-07 16:24:36 +01:00
WerWolv
3f9ce561b9 fix: Post-pone file opening till everything has been initialized 2025-12-07 14:00:10 +01:00
WerWolv
347fc3ed9f impr: Show proper error message if nethost header can't be found 2025-12-07 13:35:08 +01:00
WerWolv
0488c996e9 impr: Make icons look slightly nicer at low resolutions 2025-12-07 11:48:14 +01:00
WerWolv
37bfd97d93 git: Make release CI more reliable 2025-12-07 10:36:03 +01:00
WerWolv
c8652b0576 Merge branch 'feature/code-signing' 2025-12-07 00:01:54 +01:00
WerWolv
1208d2eb5e git: Fix issues with the release CI 2025-12-07 00:01:40 +01:00
WerWolv
ab34fed0c5 build: Bump version to 1.39.0.WIP 2025-12-07 00:01:22 +01:00
WerWolv
0906e5f9cf git: Remove test signing, get release signing ready 2025-12-06 23:59:56 +01:00
WerWolv
47b1c603b3 git: Remove signpath parameters 2025-12-06 23:59:56 +01:00
WerWolv
4bda321e7a git: Fix version string 2025-12-06 23:59:56 +01:00
WerWolv
691ff11fbc git: Added Windows code signing 2025-12-06 23:59:56 +01:00
328 changed files with 8290 additions and 4044 deletions

View File

@@ -1,69 +1,97 @@
# Generated from CLion Inspection settings
---
Checks: '-*,
mpi-*,
bugprone-*,
-bugprone-signal-handler,
-bugprone-narrowing-conversions,
-bugprone-redundant-branch-condition,
-bugprone-exception-escape,
-bugprone-shared-ptr-array-mismatch,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-signed-char-misuse,
-bugprone-unhandled-exception-at-new,
-bugprone-infinite-loop,
-bugprone-easily-swappable-parameters,
cert-err52-cpp,
cert-err60-cpp,
cert-err34-c,
cert-str34-c,
cert-dcl21-cpp,
cert-msc50-cpp,
cert-msc51-cpp,
cert-dcl58-cpp,
cert-flp30-c,
cppcoreguidelines-avoid-const-or-ref-data-members,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-slicing,
cppcoreguidelines-interfaces-global-init,
cppcoreguidelines-pro-type-static-cast-downcast,
cppcoreguidelines-narrowing-conversions,
google-default-arguments,
google-runtime-operator,
google-explicit-constructor,
hicpp-multiway-paths-covered,
hicpp-exception-baseclass,
misc-*,
-misc-definitions-in-headers,
-misc-unused-parameters,
-misc-unused-alias-decls,
-misc-use-anonymous-namespace,
-misc-misleading-identifier,
-misc-confusable-identifiers,
-misc-misleading-bidirectional,
-misc-static-assert,
-misc-no-recursion,
-misc-const-correctness,
modernize-*,
-modernize-use-trailing-return-type,
openmp-use-default-none,
performance-*,
-performance-no-int-to-ptr,
portability-*,
-portability-restrict-system-includes,
readability-*,
-readability-redundant-preprocessor,
-readability-named-parameter,
-readability-function-size,
-readability-use-anyofallof,
-readability-identifier-length,
-readability-magic-numbers,
-readability-braces-around-statements,
-readability-suspicious-call-argument,
-readability-isolate-declaration,
-readability-else-after-return,
-readability-redundant-access-specifiers,
-readability-function-cognitive-complexity,
-readability-identifier-naming,
*-include-cleaner,
-readability-qualified-auto'
# All rules should have a comment associated
# Directives that do not have any effect (e.g. disabling a rule that is not enabled) can be done to add an explanation comment.
# Or at least an empty comment # to show they were put here explicitely,
# and not as part of the historical CLion-generated rules
# Note: `- -X` means disable X
# CLI usage: go to the build directory and run: `run-clang-tidy -allow-no-checks -source-filter ".*/lib/.*" -fix -j`
Checks:
- -*
- mpi-*
- bugprone-*
- -bugprone-signal-handler
- -bugprone-narrowing-conversions
- -bugprone-redundant-branch-condition
- -bugprone-exception-escape
- -bugprone-shared-ptr-array-mismatch
- -bugprone-implicit-widening-of-multiplication-result
- -bugprone-signed-char-misuse
- -bugprone-unhandled-exception-at-new
- -bugprone-infinite-loop
- -bugprone-easily-swappable-parameters
- -bugprone-float-loop-counter #
- -bugprone-unchecked-string-to-number-conversion # Unfortunately no alternative
- -bugprone-branch-clone # Mostly warns about one-line duplicates
- cert-err52-cpp
- cert-err60-cpp
- cert-str34-c
- cert-dcl21-cpp
- cert-msc50-cpp
- cert-msc51-cpp
- cert-dcl58-cpp
- cppcoreguidelines-avoid-const-or-ref-data-members
- cppcoreguidelines-pro-type-member-init # We want to use default member initializers
- cppcoreguidelines-slicing
- cppcoreguidelines-interfaces-global-init
- -cppcoreguidelines-pro-type-static-cast-downcast # dynamic_cast has a runtime overhead
- -cppcoreguidelines-narrowing-conversions #
- google-runtime-operator
- google-explicit-constructor
- -google-default-arguments # Provider and ViewProvider read() is a good example of why this is useful
- hicpp-multiway-paths-covered
- hicpp-exception-baseclass
- misc-*
- -misc-definitions-in-headers
- -misc-unused-parameters
- -misc-unused-alias-decls
- -misc-use-anonymous-namespace
- -misc-misleading-identifier
- -misc-confusable-identifiers
- -misc-misleading-bidirectional
- -misc-static-assert
- -misc-no-recursion
- -misc-const-correctness
- -misc-use-internal-linkage # False positives if header where function is defined is not included
- -misc-include-cleaner # Allow indirect includes
- -misc-non-private-member-variables-in-classes #
- modernize-*
- -modernize-use-trailing-return-type
- -modernize-use-std-print # We want to use fmt::print instead
- -modernize-use-integer-sign-comparison # Too much occurrences to change
- openmp-use-default-none
- performance-*
- -performance-no-int-to-ptr
- portability-*
- -portability-restrict-system-includes
- readability-*
- -readability-redundant-preprocessor
- -readability-named-parameter
- -readability-function-size
- -readability-use-anyofallof
- -readability-identifier-length
- -readability-magic-numbers
- -readability-braces-around-statements
- -readability-suspicious-call-argument
- -readability-isolate-declaration
- -readability-else-after-return
- -readability-redundant-access-specifiers
- -readability-function-cognitive-complexity
- -readability-identifier-naming
- -readability-qualified-auto
- -readability-use-std-min-max # Less readable imo
- -readability-math-missing-parentheses # Basic math
- -readability-implicit-bool-conversion # Not much of a problem ?
- -readability-convert-member-functions-to-static #
- -readability-use-concise-preprocessor-directives # We do not use #ifdef
- -readability-uppercase-literal-suffix # Not important enough
- -readability-redundant-string-cstr # Sometimes used to stop at first null byte
- -readability-static-accessed-through-instance #
- -readability-ambiguous-smartptr-reset-call # Fix is hard to read
# Will fix later
- -modernize-avoid-c-arrays
- -readability-make-member-function-const # idk + lots of occurences
- -readability-misleading-indentation # We need to handle cases with #if defined()
- -bugprone-unchecked-optional-access
- -performance-unnecessary-value-param # idk
- -readability-avoid-nested-conditional-operator

4
.github/FUNDING.yml vendored
View File

@@ -1,5 +1,5 @@
# Sponsor links
patreon: werwolv
custom: https://werwolv.net/donate
github: WerWolv
ko_fi: WerWolv
custom: "https://werwolv.net/donate"

7
.github/scripts/delete-artifact.sh vendored Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
set -xe
ARTIFACT_NAME="$1"
ARTIFACT_ID=$(gh api repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/artifacts --jq ".artifacts[] | select(.name==\"$ARTIFACT_NAME\") | .id")
gh api -X DELETE repos/$GITHUB_REPOSITORY/actions/artifacts/$ARTIFACT_ID
echo "Deleted artifact $ARTIFACT_NAME with ID $ARTIFACT_ID"

View File

@@ -1,5 +1,9 @@
name: Build
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
on:
push:
branches:
@@ -49,7 +53,7 @@ jobs:
submodules: recursive
- name: 📜 Setup ccache
uses: hendrikmuhs/ccache-action@main
uses: hendrikmuhs/ccache-action@v1
id: cache-ccache
with:
key: ${{ runner.os }}-mingw-ccache-${{ github.run_id }}
@@ -95,7 +99,6 @@ jobs:
-DIMHEX_GENERATE_PDBS=ON \
-DIMHEX_REPLACE_DWARF_WITH_PDB=ON \
-DDOTNET_EXECUTABLE="C:/Program Files/dotnet/dotnet.exe" \
-DCPACK_WIX_VERSION="4" \
-DCPACK_WIX_ROOT="$(echo "$USERPROFILE" | tr '\\' '/')/.dotnet/tools" \
..
@@ -166,6 +169,7 @@ jobs:
- name: ⬆️ Upload Windows Installer
uses: actions/upload-artifact@v4
id: upload-installer
with:
if-no-files-found: error
name: Windows Installer ${{ matrix.architecture_name }}
@@ -233,7 +237,7 @@ jobs:
arch: ${{ matrix.vs_arch }}
- name: 📜 Setup ccache
uses: hendrikmuhs/ccache-action@main
uses: hendrikmuhs/ccache-action@v1
id: cache-ccache
with:
key: ${{ runner.os }}-msvc-${{ matrix.vs_arch }}-ccache-${{ github.run_id }}
@@ -279,7 +283,6 @@ jobs:
-DIMHEX_COMMIT_HASH_LONG="$env:GITHUB_SHA" `
-DIMHEX_COMMIT_BRANCH="$($env:GITHUB_REF -replace '.*/', '')" `
-DDOTNET_EXECUTABLE="C:/Program Files/dotnet/dotnet.exe" `
-DCPACK_WIX_VERSION="4" `
-DCPACK_WIX_ROOT="$($env:USERPROFILE -replace '\\','/')/.dotnet/tools" `
.
@@ -386,18 +389,7 @@ jobs:
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
include:
- file_suffix: "-NoGPU"
name_suffix: "NoGPU"
custom_glfw: true
- file_suffix: ""
name_suffix: ""
custom_glfw: false
name: 🍎 macOS 15 x86_64 ${{ matrix.name_suffix }}
name: 🍎 macOS 10.15 x86_64
steps:
- name: 🧰 Checkout
@@ -412,61 +404,40 @@ jobs:
- name: 📜 Setup ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ runner.os }}${{ matrix.file_suffix }}-ccache-${{ github.run_id }}
restore-keys: ${{ runner.os }}${{ matrix.file_suffix }}-ccache
key: ${{ runner.os }}-ccache-${{ github.run_id }}
restore-keys: ${{ runner.os }}-ccache
max-size: 1G
- name: Set Xcode version
run: sudo xcode-select -s /Library/Developer/CommandLineTools
run: |
sudo xcode-select --install || true
sudo xcode-select -s /Library/Developer/CommandLineTools
- name: 📦 Install MacPorts
run: |
wget https://github.com/macports/macports-base/releases/download/v2.11.6/MacPorts-2.11.6-15-Sequoia.pkg
sudo installer -pkg MacPorts-2.11.6-15-Sequoia.pkg -target /
export PATH=/opt/local/bin:/opt/local/sbin:$PATH
echo "PATH=/opt/local/bin:/opt/local/sbin:$PATH" >> $GITHUB_ENV
echo "MACOSX_DEPLOYMENT_TARGET=10.15" >> $GITHUB_ENV
echo "universal_target 10.15" | sudo tee -a /opt/local/etc/macports/macports.conf
echo "macos_deployment_target 10.15" | sudo tee -a /opt/local/etc/macports/macports.conf
echo "macosx_sdk_version 10.15" | sudo tee -a /opt/local/etc/macports/macports.conf
sudo port selfupdate
- name: ⬇️ Install dependencies
env:
# Make brew not display useless errors
HOMEBREW_TESTS: 1
run: |
brew reinstall python --quiet || true
brew link --overwrite --quiet python 2>/dev/null || true
brew bundle --quiet --file dist/macOS/Brewfile || true
rm -rf /usr/local/Cellar/capstone
- name: ⬇️ Install classic glfw
if: ${{! matrix.custom_glfw }}
run: |
brew install --quiet glfw || true
brew install llvm automake
sudo -E port install mbedtls3 nlohmann-json ccache freetype libmagic pkgconfig curl glfw ninja zlib xz bzip2 zstd libssh2 md4c
- name: ⬇️ Install .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.100'
- name: 🧰 Checkout glfw
if: ${{ matrix.custom_glfw }}
uses: actions/checkout@v4
with:
repository: glfw/glfw
path: glfw
# GLFW custom build (to allow software rendering)
- name: ⬇️ Patch and install custom glfw
if: ${{ matrix.custom_glfw }}
run: |
set -x
cd glfw
git apply ../dist/macOS/0001-glfw-SW.patch
mkdir build
cd build
cmake -G "Ninja" \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DBUILD_SHARED_LIBS=ON \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_OBJC_COMPILER_LAUNCHER=ccache \
-DCMAKE_OBJCXX_COMPILER_LAUNCHER=ccache \
..
ninja install
# MacOS cmake build
- name: 🛠️ Configure CMake
run: |
@@ -477,8 +448,8 @@ jobs:
CXX=$(brew --prefix llvm)/bin/clang++ \
OBJC=$(brew --prefix llvm)/bin/clang \
OBJCXX=$(brew --prefix llvm)/bin/clang++ \
PKG_CONFIG_PATH="$(brew --prefix openssl)/lib/pkgconfig":"$(brew --prefix)/lib/pkgconfig" \
cmake -G "Ninja" \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DIMHEX_GENERATE_PACKAGE=ON \
-DIMHEX_SYSTEM_LIBRARY_PATH="$(brew --prefix llvm)/lib;$(brew --prefix llvm)/lib/unwind;$(brew --prefix llvm)/lib/c++;$(brew --prefix)/lib" \
@@ -499,7 +470,6 @@ jobs:
run: |
set -x
cd build/install
mv imhex.app ImHex.app
codesign --remove-signature ImHex.app
codesign --force --deep --sign - ImHex.app
@@ -528,7 +498,7 @@ jobs:
break;
fi
done
mv *.dmg ../../imhex-${{ env.IMHEX_VERSION }}-macOS${{ matrix.file_suffix }}-x86_64.dmg
mv *.dmg ../../imhex-${{ env.IMHEX_VERSION }}-macOS-x86_64.dmg
- name: 🗝️ Generate build provenance attestations
uses: actions/attest-build-provenance@v2
@@ -541,12 +511,12 @@ jobs:
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
name: macOS DMG ${{ matrix.name_suffix }} x86_64
name: macOS DMG x86_64
path: ./*.dmg
macos-arm64:
runs-on: ubuntu-24.04
name: 🍎 macOS 15 arm64
name: 🍎 macOS 11 arm64
outputs:
IMHEX_VERSION: ${{ steps.build.outputs.IMHEX_VERSION }}
@@ -594,7 +564,7 @@ jobs:
macos-arm64-package:
runs-on: macos-15-intel
name: 🍎 macOS 15 arm64 Packaging
name: 🍎 macOS 11 arm64 Packaging
needs: macos-arm64
env:
@@ -620,7 +590,6 @@ jobs:
run: |
set -x
cd out
mv imhex.app ImHex.app
codesign --remove-signature ImHex.app
codesign --force --deep --entitlements Entitlements.plist --sign - ImHex.app
@@ -671,14 +640,21 @@ jobs:
fail-fast: false
matrix:
include:
- release_num: "24.04"
- release_num: "25.04"
- name: "Ubuntu"
release_num: "24.04"
image: "ubuntu:24.04"
- name: "Ubuntu"
release_num: "25.10"
image: "ubuntu:25.10"
- name: "Debian"
release_num: "13"
image: "debian:13"
name: 🐧 Ubuntu ${{ matrix.release_num }}
name: 🐧 ${{ matrix.name }} ${{ matrix.release_num }} x86_64
runs-on: ubuntu-24.04
container:
image: "ubuntu:${{ matrix.release_num }}"
image: "${{ matrix.image }}"
options: --privileged
permissions:
@@ -697,8 +673,8 @@ jobs:
- name: 📜 Setup ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: Ubuntu-${{ matrix.release_num }}-ccache-${{ github.run_id }}
restore-keys: Ubuntu-${{ matrix.release_num }}-ccache
key: ${{ matrix.image }}-ccache-${{ github.run_id }}
restore-keys: ${{ matrix.image }}-ccache
max-size: 1G
- name: ⬇️ Install dependencies
@@ -743,7 +719,7 @@ jobs:
run: |
cp -r build/DEBIAN build/DebDir
dpkg-deb -Zzstd --build build/DebDir
mv build/DebDir.deb imhex-${{ env.IMHEX_VERSION }}-Ubuntu-${{ matrix.release_num }}-x86_64.deb
mv build/DebDir.deb imhex-${{ env.IMHEX_VERSION }}-${{ matrix.name }}-${{ matrix.release_num }}-x86_64.deb
- name: 🗝️ Generate build provenance attestations
uses: actions/attest-build-provenance@v2
@@ -756,7 +732,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
if-no-files-found: error
name: Ubuntu ${{ matrix.release_num }} DEB x86_64
name: ${{ matrix.name }} ${{ matrix.release_num }} DEB x86_64
path: '*.deb'
# AppImage build
@@ -831,7 +807,7 @@ jobs:
# ArchLinux build
archlinux-build:
name: 🐧 ArchLinux
name: 🐧 ArchLinux x86_64
runs-on: ubuntu-24.04
container:
@@ -946,23 +922,11 @@ jobs:
fail-fast: false
matrix:
include:
- name: Fedora
release_num: rawhide
mock_config: fedora-rawhide
- name: Fedora
release_num: 43
mock_config: fedora-43
- name: Fedora
release_num: 42
mock_config: fedora-42
- name: Fedora
release_num: 41
mock_config: fedora-41
- name: RHEL-AlmaLinux
release_num: 9
mock_config: "alma+epel-9"
name: 🐧 ${{ matrix.name }} ${{ matrix.release_num }}
name: 🐧 ${{ matrix.name }} ${{ matrix.release_num }} x86_64
runs-on: ubuntu-24.04
container:
@@ -1225,10 +1189,6 @@ jobs:
webassembly-build:
runs-on: ubuntu-24.04
name: 🌍 Web
permissions:
pages: write
id-token: write
actions: write
steps:
- name: 🧰 Checkout
uses: actions/checkout@v4
@@ -1248,17 +1208,20 @@ jobs:
cache-source: cache
cache-target: /cache
- name: 🛠️ Build using docker
- name: 🔨 Copy necessary files
run: |
mkdir -p out/nightly
cp dist/web/serve.py out/nightly/start_imhex_web.py
- name: 🛠️ Build using docker
run: |
docker buildx build . -f dist/web/Dockerfile --progress=plain --build-arg 'JOBS=4' --output out/nightly --target raw
- name: ⬇️ Download Release
- name: ⬇️ Download Release artifact
if: ${{ github.event.repository.fork == false }}
uses: robinraju/release-downloader@v1
with:
latest: true
fileName: 'imhex-*-Web.zip'
env:
GH_TOKEN: ${{ github.token }}
run: gh --repo $GITHUB_REPOSITORY release download --pattern "imhex-*-Web.zip"
- name: 🔨 Fix permissions
if: ${{ github.event.repository.fork == false }}
@@ -1271,10 +1234,6 @@ jobs:
with:
path: out/
- name: 🔨 Copy necessary files
run: |
cp dist/web/serve.py out/nightly/start_imhex_web.py
- name: ⬆️ Upload package
uses: actions/upload-artifact@v4
with:
@@ -1293,7 +1252,7 @@ jobs:
webassembly-deploy:
environment:
name: github-pages
name: ImHex Web
url: ${{ steps.deployment.outputs.page_url }}
permissions:
pages: write
@@ -1306,11 +1265,64 @@ jobs:
needs: webassembly-build
steps:
- name: 🧰 Checkout
uses: actions/checkout@v4
- name: 🌍 Deploy WebAssembly Build to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
- name: 🗑️ Delete artifact
uses: geekyeggo/delete-artifact@v5
env:
GH_TOKEN: ${{ github.token }}
run: |
.github/scripts/delete-artifact.sh "github-pages"
webassembly-docker-image-deploy:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
needs: webassembly-build
name: 🐋 Deploy to ghcr.io
permissions:
contents: read
packages: write
steps:
- name: 🧰 Checkout
uses: actions/checkout@v4
- name: ⬇️ Download artifact
uses: actions/download-artifact@v4
with:
name: github-pages
name: ImHex Web
path: out
- name: 📜 Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: ⛓️ Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}/imhex-web
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: 🔨 Build and push Docker image
uses: docker/build-push-action@v6
env:
DOCKER_BUILD_RECORD_UPLOAD: false
with:
context: .
file: dist/web/Host.Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

47
.github/workflows/dl-cache.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
# https://gist.github.com/iTrooz/d5bacca32c0974edc6c1ac3ad3ee82f3
# See https://github.com/cli/cli/issues/9125
# Extract archive with `tar -xf cache.tzst --transform 's@\.\./@#@g' -P` to avoid ../ errors
name: Download cache key
on:
workflow_dispatch:
inputs:
cache_key:
description: 'Cache key'
required: true
type: string
jobs:
cache-download:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Query cache version
id: version
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION=$(gh api repos/$GITHUB_REPOSITORY/actions/caches \
--jq "
.actions_caches[]
| select(.ref == \"refs/heads/$GITHUB_REF_NAME\")
| select(.key == \"${{ github.event.inputs.cache_key }}\")
| .version
")
echo "version=$VERSION" | tee $GITHUB_OUTPUT
- name: Restore cache
uses: iTrooz/cache/restore@restore_with_version
with:
# Path won't be actually used, we will match by 'version'.
path: .
key: ${{ github.event.inputs.cache_key }}
version: ${{ steps.version.outputs.version }}
- name: Upload cached folder as artifact
uses: actions/upload-artifact@v4
with:
name: cache-artifact
path: |
/home/runner/work/**/*.tzst

View File

@@ -37,13 +37,14 @@ jobs:
run: |
project_version=`cat ImHex/VERSION`
echo "IMHEX_VERSION=$project_version" >> $GITHUB_ENV
# TODO: Replace by Github CLI when github.com/cli/cli/pull/12435 is closed
- name: ⬇️ Download artifacts from latest workflow
uses: dawidd6/action-download-artifact@v6
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
workflow: build.yml
branch: ${{ github.event.release.target_commitish }}
branch: master
workflow_conclusion: success
skip_unpack: true
@@ -125,4 +126,19 @@ jobs:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
with:
snap: imhex-${{ env.IMHEX_VERSION }}-arm64.snap
release: edge
release: edge
website_update:
name: 🌍 Update ImHex Landing Website
needs: nightly-release
runs-on: ubuntu-24.04
env:
WEBSITE_DISPATCH_TOKEN: ${{ secrets.WEBSITE_DISPATCH_TOKEN }}
steps:
- name: ✉️ Dispatch Landing page update
if: ${{ env.WEBSITE_DISPATCH_TOKEN != '' }}
uses: peter-evans/repository-dispatch@v4
with:
token: ${{ secrets.WEBSITE_DISPATCH_TOKEN }}
repository: WerWolv/ImHexWebsite
event-type: update_page

View File

@@ -7,6 +7,12 @@ on:
release:
types: [published]
workflow_dispatch:
inputs:
commit_hash:
type: string
description: 'The commit hash to build (defaults to the latest commit on the default branch)'
required: false
default: ''
jobs:
release-update-repos:
@@ -25,7 +31,7 @@ jobs:
project_version=`cat ImHex/VERSION`
tag_version="${{github.event.release.tag_name}}"
tag_version="${tag_version:1}"
if [ "$project_version" != "$tag_version" ]; then
if [ "$project_version" != "$tag_version" ] && [ ! -z "$tag_version" ]; then
echo "::warning::$project_version and $tag_version are not the same ! Refusing to populate release"
exit 1
fi
@@ -41,6 +47,7 @@ jobs:
tag: ImHex-v${{ env.IMHEX_VERSION }}
repo: PatternLanguage
token: ${{ secrets.RELEASE_TOKEN }}
skipIfReleaseExists: true
- name: 🎫 Create ImHex-Patterns release
uses: ncipollo/release-action@v1
@@ -51,6 +58,7 @@ jobs:
tag: ImHex-v${{ env.IMHEX_VERSION }}
repo: ImHex-Patterns
token: ${{ secrets.RELEASE_TOKEN }}
skipIfReleaseExists: true
- name: 🎫 Create imhex-download-sdk release
uses: ncipollo/release-action@v1
@@ -61,11 +69,13 @@ jobs:
tag: v${{ env.IMHEX_VERSION }}
repo: imhex-download-sdk
token: ${{ secrets.RELEASE_TOKEN }}
skipIfReleaseExists: true
release-upload-artifacts:
runs-on: ubuntu-24.04
name: Release Upload Artifacts
outputs:
IMHEX_VERSION: ${{ steps.verify_version.outputs.IMHEX_VERSION }}
steps:
- name: 🧰 Checkout
uses: actions/checkout@v4
@@ -74,17 +84,19 @@ jobs:
submodules: recursive
- name: 📜 Verify version and set version variable
id: verify_version
run: |
set -x
project_version=`cat ImHex/VERSION`
tag_version="${{github.event.release.tag_name}}"
tag_version="${tag_version:1}"
if [ "$project_version" != "$tag_version" ]; then
if [ "$project_version" != "$tag_version" ] && [ ! -z "$tag_version" ]; then
echo "::warning::$project_version and $tag_version are not the same ! Refusing to populate release"
exit 1
fi
echo "IMHEX_VERSION=$project_version" >> $GITHUB_ENV
echo "IMHEX_VERSION=$project_version" >> $GITHUB_OUTPUT
- name: 🗜️ Create tarball from sources with dependencies
run: tar --exclude-vcs -czvf Full.Sources.tar.gz ImHex
@@ -97,6 +109,7 @@ jobs:
branch: ${{ github.event.release.target_commitish }}
workflow_conclusion: success
skip_unpack: true
commit: ${{ github.event.inputs.commit_hash }}
- name: 🗜️ Unzip files when needed
run: |
@@ -115,25 +128,87 @@ jobs:
- name: 🟩 Rename artifacts when needed
run: |
mv "Windows Portable x86_64.zip" imhex-${{ env.IMHEX_VERSION }}-Windows-Portable-x86_64.zip
mv "Windows Portable arm64.zip" imhex-${{ env.IMHEX_VERSION }}-Windows-Portable-arm64.zip
mv "Windows Portable NoGPU x86_64.zip" imhex-${{ env.IMHEX_VERSION }}-Windows-Portable-NoGPU-x86_64.zip
mv "ImHex Web.zip" imhex-${{ env.IMHEX_VERSION }}-Web.zip
mv "Windows Portable x86_64.zip" imhex-${{ env.IMHEX_VERSION }}-Windows-Portable-x86_64.zip || true
mv "Windows Portable arm64.zip" imhex-${{ env.IMHEX_VERSION }}-Windows-Portable-arm64.zip || true
mv "Windows Portable NoGPU x86_64.zip" imhex-${{ env.IMHEX_VERSION }}-Windows-Portable-NoGPU-x86_64.zip || true
mv "ImHex Web.zip" imhex-${{ env.IMHEX_VERSION }}-Web.zip || true
rm artifact.tar || true
- name: ⬆️ Upload Unsigned x86_64 Windows Installer
uses: actions/upload-artifact@v4
id: upload-installer-x86_64
with:
if-no-files-found: error
name: Windows Installer x86_64
path: |
imhex-*-x86_64.msi
- name: ⬆️ Upload Unsigned ARM64 Windows Installer
if: false
uses: actions/upload-artifact@v4
id: upload-installer-arm64
with:
if-no-files-found: error
name: Windows Installer ARM64
path: |
imhex-*-arm64.msi
- name: 🗑️ Delete unsigned installers
run: |
rm imhex-*-x86_64.msi
- name: 🗝️ Sign x86_64 Installer
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: 'f605a0e8-86cd-411c-bb6f-e05025afcc33'
project-slug: 'ImHex'
signing-policy-slug: 'release-signing'
github-artifact-id: '${{ steps.upload-installer-x86_64.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: '.'
- name: 🗝️ Sign ARM64 Installer
if: false
uses: signpath/github-action-submit-signing-request@v1
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: 'f605a0e8-86cd-411c-bb6f-e05025afcc33'
project-slug: 'ImHex'
signing-policy-slug: 'release-signing'
github-artifact-id: '${{ steps.upload-installer-arm64.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: '.'
- name: ⬆️ Upload everything to release
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981
with:
files: '*'
release-update-aur:
name: Release update AUR package
needs: release-upload-artifacts
runs-on: ubuntu-24.04
steps:
- name: 🧰 Checkout
uses: actions/checkout@v4
with:
path: ImHex
- name: ⬇️ Download artifacts
run: |
tagname=${GITHUB_REF#refs/tags/}
version=${tagname#v}
wget https://github.com/WerWolv/ImHex/releases/download/${tagname}/imhex-${version}-ArchLinux-x86_64.pkg.tar.zst
- name: ✒️ Prepare PKGBUILD
run: |
set -x
cp ImHex/dist/Arch/PKGBUILD .
hash=`md5sum imhex-${{ env.IMHEX_VERSION }}-ArchLinux-x86_64.pkg.tar.zst | cut -d ' ' -f 1`
hash=`md5sum imhex-${{ needs.release-upload-artifacts.outputs.IMHEX_VERSION }}-ArchLinux-x86_64.pkg.tar.zst | cut -d ' ' -f 1`
sed -i 's/%version%/${{ env.IMHEX_VERSION }}/g' PKGBUILD
sed -i 's/%version%/${{ needs.release-upload-artifacts.outputs.IMHEX_VERSION }}/g' PKGBUILD
sed -i "s/(SKIP)/($hash)/g" PKGBUILD
- name: ⬆️ Publish AUR package
@@ -147,9 +222,9 @@ jobs:
pkgname: imhex-bin
pkgbuild: ./PKGBUILD
commit_username: iTrooz
commit_email: itrooz@protonmail.com
commit_email: hey@itrooz.fr
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: Bump to version ${{ env.IMHEX_VERSION }}
commit_message: Bump to version ${{ needs.release-upload-artifacts.outputs.IMHEX_VERSION }}
ssh_keyscan_types: rsa,ecdsa,ed25519
release-update-winget:
@@ -161,6 +236,7 @@ jobs:
shell: pwsh
run: |
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
- name: ⬆️ Update winget manifest
shell: pwsh
env:
@@ -193,7 +269,7 @@ jobs:
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
with:
snap: imhex-${{ env.IMHEX_VERSION }}-x86_64.snap
snap: imhex-${{ needs.release-upload-artifacts.outputs.IMHEX_VERSION }}-x86_64.snap
release: stable
- name: ⬆️ Publish arm64 Snap package
@@ -202,5 +278,5 @@ jobs:
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
with:
snap: imhex-${{ env.IMHEX_VERSION }}-arm64.snap
snap: imhex-${{ needs.release-upload-artifacts.outputs.IMHEX_VERSION }}-arm64.snap
release: stable

View File

@@ -1,33 +1,39 @@
cmake_minimum_required(VERSION 3.25)
# Options
option(IMHEX_PLUGINS_IN_SHARE "Put the plugins in share/imhex/plugins instead of lib[..]/imhex/plugins (Linux only)" OFF)
## General
option(IMHEX_STRIP_RELEASE "Strip the release builds" ON )
option(IMHEX_OFFLINE_BUILD "Enable offline build" OFF)
option(IMHEX_IGNORE_BAD_CLONE "Disable the bad clone prevention checks" OFF)
option(IMHEX_PATTERNS_PULL_MASTER "Download latest files from master branch of the ImHex-Patterns repo" OFF)
option(IMHEX_IGNORE_BAD_COMPILER "Allow compiling with an unsupported compiler" OFF)
option(IMHEX_USE_GTK_FILE_PICKER "Use GTK file picker instead of xdg-desktop-portals (Linux only)" OFF)
option(IMHEX_DISABLE_STACKTRACE "Disables support for printing stack traces" OFF)
option(IMHEX_BUNDLE_DOTNET "Bundle .NET runtime" ON )
option(IMHEX_ENABLE_LTO "Enables Link Time Optimizations if possible" OFF)
option(IMHEX_USE_DEFAULT_BUILD_SETTINGS "Use default build settings" OFF)
option(IMHEX_STRICT_WARNINGS "Enable most available warnings and treat them as errors" ON )
option(IMHEX_BUILD_HARDENING "Enable hardening flags for build" ON )
option(IMHEX_STATIC_LINK_PLUGINS "Statically link all plugins into the main executable" OFF)
option(IMHEX_GENERATE_PACKAGE "Specify if a native package should be built. (Windows and MacOS only)" OFF)
option(IMHEX_GENERATE_PACKAGE "Specify if a cpack package should be built. (Windows only)" OFF)
option(IMHEX_MACOS_CREATE_BUNDLE "Creates a macOS .app bundle when building (macOS only)" ON )
option(IMHEX_ENABLE_UNITY_BUILD "Enables building ImHex as a unity build." OFF)
option(IMHEX_GENERATE_PDBS "Enable generating PDB files in non-debug builds (Windows only)" OFF)
option(IMHEX_REPLACE_DWARF_WITH_PDB "Remove DWARF information from binaries when generating PDBS (Windows only)" OFF)
option(IMHEX_ENABLE_STD_ASSERTS "Enable debug asserts in the C++ std library. (Breaks Plugin ABI!)" OFF)
option(IMHEX_ENABLE_UNIT_TESTS "Enable building unit tests" ON )
option(IMHEX_ENABLE_PLUGIN_TESTS "Enable building plugin tests" ON )
option(IMHEX_ENABLE_IMGUI_TEST_ENGINE "Enable the ImGui Test Engine" OFF)
option(IMHEX_ENABLE_PRECOMPILED_HEADERS "Enable precompiled headers" OFF)
option(IMHEX_COMPRESS_DEBUG_INFO "Compress debug information" ON )
option(IMHEX_ENABLE_CXX_MODULES "Enable C++20 Module compilation. Testing only!" OFF)
option(IMHEX_ENABLE_CPPCHECK "Enable cppcheck static analysis" OFF)
option(IMHEX_BUNDLE_PLUGIN_SDK "Enable bundling of Plugin SDK into install package" ON )
## Testing
option(IMHEX_ENABLE_UNIT_TESTS "Enable building unit tests" ON )
option(IMHEX_ENABLE_IMGUI_TEST_ENGINE "Enable the ImGui Test Engine" OFF)
option(IMHEX_ENABLE_STD_ASSERTS "Enable debug asserts in the C++ std library. (Breaks Plugin ABI!)" OFF)
## Debug info
option(IMHEX_COMPRESS_DEBUG_INFO "Compress debug information" ON )
option(IMHEX_GENERATE_PDBS "Enable generating PDB files in non-debug builds (Windows only)" OFF)
option(IMHEX_REPLACE_DWARF_WITH_PDB "Remove DWARF information from binaries when generating PDBS (Windows only)" OFF)
option(IMHEX_STRICT_WARNINGS "Enable most available warnings and treat them as errors" ON )
option(IMHEX_DISABLE_STACKTRACE "Disables support for printing stack traces" OFF)
## Plugins
option(IMHEX_STATIC_LINK_PLUGINS "Statically link all plugins into the main executable" OFF)
option(IMHEX_ENABLE_PLUGIN_TESTS "Enable building plugin tests" ON )
option(IMHEX_INCLUDE_PLUGINS "Semicolon-separated list of plugins to include in the build (empty = build all)" "" )
option(IMHEX_EXCLUDE_PLUGINS "Semicolon-separated list of plugins to exclude from the build" "" )
set(IMHEX_BASE_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}")
set(CMAKE_MODULE_PATH "${IMHEX_BASE_FOLDER}/cmake/modules")

View File

@@ -29,11 +29,11 @@
## Supporting
If you like my work, please consider supporting me on GitHub Sponsors, Patreon or PayPal. Thanks a lot!
If you like my work, please consider supporting me on GitHub Sponsors, Ko-Fi or PayPal. Thanks a lot!
<p align="center">
<a href="https://github.com/sponsors/WerWolv"><img src="https://werwolv.net/assets/github_banner.png" alt="GitHub donate button" /></a>
<a href="https://www.patreon.com/werwolv"><img src="https://c5.patreon.com/external/logo/become_a_patron_button.png" alt="Patreon donate button" /></a>
<a href="https://ko-fi.com/WerWolv"><img src="https://werwolv.net/assets/kofi_banner.png" alt="Ko-Fi donate button" /></a>
<a href="https://werwolv.net/donate"><img src="https://werwolv.net/assets/paypal_banner.png" alt="PayPal donate button" /></a>
</p>

View File

@@ -1 +1 @@
1.38.0
1.39.0.WIP

View File

@@ -175,15 +175,11 @@ macro(detectOS)
endif()
include(GNUInstallDirs)
if(IMHEX_PLUGINS_IN_SHARE)
set(PLUGINS_INSTALL_LOCATION "share/imhex/plugins")
else()
set(PLUGINS_INSTALL_LOCATION "${CMAKE_INSTALL_LIBDIR}/imhex/plugins")
set(PLUGINS_INSTALL_LOCATION "${CMAKE_INSTALL_LIBDIR}/imhex/plugins")
# Add System plugin location for plugins to be loaded from
# IMPORTANT: This does not work for Sandboxed or portable builds such as the Flatpak or AppImage release
add_compile_definitions(SYSTEM_PLUGINS_LOCATION="${CMAKE_INSTALL_FULL_LIBDIR}/imhex")
endif()
# Add System plugin location for plugins to be loaded from
# IMPORTANT: This does not work for Sandboxed or portable builds such as the Flatpak or AppImage release
add_compile_definitions(SYSTEM_PLUGINS_LOCATION="${CMAKE_INSTALL_FULL_LIBDIR}/imhex")
else ()
message(FATAL_ERROR "Unknown / unsupported system!")
@@ -205,11 +201,18 @@ macro(configurePackingResources)
set(CPACK_GENERATOR "WIX")
set(CPACK_PACKAGE_NAME "ImHex")
set(CPACK_PACKAGE_VENDOR "WerWolv")
set(CPACK_WIX_VERSION 4)
set(CPACK_WIX_PRODUCT_GUID "*")
set(CPACK_WIX_UPGRADE_GUID "05000E99-9659-42FD-A1CF-05C554B39285")
set(CPACK_WIX_PRODUCT_ICON "${PROJECT_SOURCE_DIR}/resources/dist/windows/icon.ico")
set(CPACK_WIX_UI_BANNER "${PROJECT_SOURCE_DIR}/resources/dist/windows/wix_banner.png")
set(CPACK_WIX_UI_DIALOG "${PROJECT_SOURCE_DIR}/resources/dist/windows/wix_dialog.png")
set(CPACK_WIX_CULTURES "en-US;de-DE;ja-JP;it-IT;pt-BR;zh-CN;zh-TW;ru-RU")
set(CPACK_WIX_TEMPLATE "${PROJECT_SOURCE_DIR}/resources/dist/windows/WIX.template.in")
set(CPACK_WIX_EXTENSIONS "WixToolset.UI.wixext")
file(GLOB_RECURSE CPACK_WIX_EXTRA_SOURCES "${PROJECT_SOURCE_DIR}/resources/dist/windows/wix/*.wxs")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "ImHex")
set_property(INSTALL "$<TARGET_FILE_NAME:main>"
PROPERTY CPACK_START_MENU_SHORTCUTS "ImHex"
@@ -218,9 +221,9 @@ macro(configurePackingResources)
endif()
elseif (APPLE OR ${CMAKE_HOST_SYSTEM_NAME} MATCHES "Darwin")
set(IMHEX_ICON "${IMHEX_BASE_FOLDER}/resources/dist/macos/AppIcon.icns")
set(BUNDLE_NAME "imhex.app")
set(BUNDLE_NAME "ImHex.app")
if (IMHEX_GENERATE_PACKAGE)
if (IMHEX_MACOS_CREATE_BUNDLE)
set(APPLICATION_TYPE MACOSX_BUNDLE)
set_source_files_properties(${IMHEX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
set(MACOSX_BUNDLE_ICON_FILE "AppIcon.icns")
@@ -236,9 +239,9 @@ macro(configurePackingResources)
string(TIMESTAMP CURR_YEAR "%Y")
set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2020 - ${CURR_YEAR} WerWolv. All rights reserved." )
if ("${CMAKE_GENERATOR}" STREQUAL "Xcode")
set (IMHEX_BUNDLE_PATH "${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${BUNDLE_NAME}")
set(IMHEX_BUNDLE_PATH "${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}/${BUNDLE_NAME}")
else ()
set (IMHEX_BUNDLE_PATH "${CMAKE_BINARY_DIR}/${BUNDLE_NAME}")
set(IMHEX_BUNDLE_PATH "${CMAKE_BINARY_DIR}/${BUNDLE_NAME}")
endif()
set(PLUGINS_INSTALL_LOCATION "${IMHEX_BUNDLE_PATH}/Contents/MacOS/plugins")
@@ -256,7 +259,7 @@ macro(addPluginDirectories)
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${IMHEX_MAIN_OUTPUT_DIRECTORY}/plugins")
if (APPLE)
if (IMHEX_GENERATE_PACKAGE)
if (IMHEX_MACOS_CREATE_BUNDLE)
set_target_properties(${plugin} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGINS_INSTALL_LOCATION})
endif ()
else ()
@@ -312,7 +315,7 @@ macro(createPackage)
POST_EXCLUDE_REGEXES ".*system32/.*\\.dll"
)
if(_c_deps_FILENAMES AND NOT _c_deps STREQUAL "")
if(_c_deps_FILENAMES AND _c_deps AND NOT (_c_deps STREQUAL ""))
message(WARNING "Conflicting dependencies for library: \"${_c_deps}\"!")
endif()
@@ -348,24 +351,21 @@ macro(createPackage)
endif()
if (APPLE)
if (IMHEX_GENERATE_PACKAGE)
if (IMHEX_MACOS_CREATE_BUNDLE)
set(EXTRA_BUNDLE_LIBRARY_PATHS ${EXTRA_BUNDLE_LIBRARY_PATHS} "${IMHEX_SYSTEM_LIBRARY_PATH}")
include(PostprocessBundle)
set_target_properties(libimhex PROPERTIES SOVERSION ${IMHEX_VERSION})
set_property(TARGET main PROPERTY MACOSX_BUNDLE_INFO_PLIST ${MACOSX_BUNDLE_INFO_PLIST})
set_property(TARGET main PROPERTY MACOSX_BUNDLE_BUNDLE_NAME "${MACOSX_BUNDLE_BUNDLE_NAME}")
# Fix rpath
install(CODE "execute_process(COMMAND ${CMAKE_INSTALL_NAME_TOOL} -add_rpath \"@executable_path/../Frameworks/\" $<TARGET_FILE:main>)")
install(CODE "execute_process(COMMAND ${CMAKE_INSTALL_NAME_TOOL} -add_rpath \"@executable_path/../Frameworks/\" $<TARGET_FILE:updater>)")
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("${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}/Contents/MacOS")
install(FILES ${IMHEX_ICON} DESTINATION "${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}/Contents/Resources")
install(TARGETS main BUNDLE DESTINATION ".")
install(TARGETS updater DESTINATION "${CMAKE_INSTALL_PREFIX}/${BUNDLE_NAME}/Contents/MacOS")
install(
@@ -431,7 +431,7 @@ macro(createPackage)
endif()
endif()
if (IMHEX_GENERATE_PACKAGE)
if (IMHEX_MACOS_CREATE_BUNDLE)
set(CPACK_BUNDLE_NAME "ImHex")
include(CPack)
@@ -560,6 +560,9 @@ function(detectBadClone)
file (GLOB EXTERNAL_DIRS "lib/external/*" "lib/third_party/*")
foreach (EXTERNAL_DIR ${EXTERNAL_DIRS})
if(NOT IS_DIRECTORY "${EXTERNAL_DIR}")
continue()
endif()
file(GLOB_RECURSE RESULT "${EXTERNAL_DIR}/*")
list(LENGTH RESULT ENTRY_COUNT)
if(ENTRY_COUNT LESS_EQUAL 1)
@@ -587,7 +590,9 @@ endfunction()
macro(detectBundledPlugins)
file(GLOB PLUGINS_DIRS "plugins/*")
if (NOT DEFINED IMHEX_INCLUDE_PLUGINS)
if (IMHEX_INCLUDE_PLUGINS)
set(PLUGINS ${IMHEX_INCLUDE_PLUGINS})
else()
foreach(PLUGIN_DIR ${PLUGINS_DIRS})
if (EXISTS "${PLUGIN_DIR}/CMakeLists.txt")
get_filename_component(PLUGIN_NAME ${PLUGIN_DIR} NAME)
@@ -596,8 +601,6 @@ macro(detectBundledPlugins)
endif ()
endif()
endforeach()
else()
set(PLUGINS ${IMHEX_INCLUDE_PLUGINS})
endif()
foreach(PLUGIN_NAME ${PLUGINS})
@@ -608,9 +611,13 @@ macro(detectBundledPlugins)
message(FATAL_ERROR "No bundled plugins enabled")
endif()
if (NOT ("builtin" IN_LIST PLUGINS))
message(FATAL_ERROR "The 'builtin' plugin is required for ImHex to work!")
endif ()
set(REQUIRED_PLUGINS builtin fonts ui)
foreach(PLUGIN ${REQUIRED_PLUGINS})
list(FIND PLUGINS ${PLUGIN} PLUGIN_INDEX)
if (PLUGIN_INDEX EQUAL -1)
message(FATAL_ERROR "Required plugin '${PLUGIN}' is not enabled!")
endif()
endforeach()
endmacro()
macro(setVariableInParent variable value)

View File

@@ -2,11 +2,7 @@ find_path(LIBMAGIC_INCLUDE_DIR magic.h)
find_library(LIBMAGIC_LIBRARY NAMES magic)
if (LIBMAGIC_INCLUDE_DIR AND LIBMAGIC_LIBRARY)
set(LIBMAGIC_FOUND TRUE)
endif (LIBMAGIC_INCLUDE_DIR AND LIBMAGIC_LIBRARY)
find_package_handle_standard_args("libmagic" DEFAULT_MSG
find_package_handle_standard_args(Magic DEFAULT_MSG
LIBMAGIC_LIBRARY
LIBMAGIC_INCLUDE_DIR
)
@@ -14,5 +10,5 @@ find_package_handle_standard_args("libmagic" DEFAULT_MSG
mark_as_advanced(
LIBMAGIC_INCLUDE_DIR
LIBMAGIC_LIBRARY
LIBMAGIC_FOUND
)
Magic_FOUND
)

View File

@@ -15,8 +15,8 @@ AppDir:
- "{{ARCHITECTURE_PACKAGE}}"
allow_unauthenticated: true
sources:
- sourceline: 'deb [arch=amd64] http://us.archive.ubuntu.com/ubuntu/ noble main restricted universe multiverse'
- sourceline: 'deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ noble main restricted universe multiverse'
- sourceline: 'deb [arch=amd64] https://us.archive.ubuntu.com/ubuntu/ noble main restricted universe multiverse'
- sourceline: 'deb [arch=arm64] https://ports.ubuntu.com/ubuntu-ports/ noble main restricted universe multiverse'
include:
- libgdk-pixbuf2.0-0
- libgdk-pixbuf2.0-common

View File

@@ -30,9 +30,9 @@ ARG LTO=ON
ARG BUILD_TYPE=RelWithDebInfo
ARG GIT_COMMIT_HASH
ARG GIT_BRANCH
ARG ARCHITECTURE_PACKAGE
ARG ARCHITECTURE_FILE_NAME
ARG ARCHITECTURE_APPIMAGE_BUILDER
ARG ARCHITECTURE_PACKAGE=x86_64
ARG ARCHITECTURE_FILE_NAME=amd64
ARG ARCHITECTURE_APPIMAGE_BUILDER=x86_64
WORKDIR /build
# Ubuntu sh doesnt support string substitution
@@ -42,16 +42,18 @@ RUN <<EOF
# Prepare ImHex build
set -xe
CC=gcc-14 CXX=g++-14 cmake -G "Ninja" \
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DIMHEX_PATTERNS_PULL_MASTER=ON \
-DIMHEX_COMMIT_HASH_LONG="${GIT_COMMIT_HASH}" \
-DIMHEX_COMMIT_BRANCH="${GIT_BRANCH}" \
-DIMHEX_ENABLE_LTO=${LTO} \
-DIMHEX_PLUGINS_IN_SHARE=ON \
CC=gcc-14 CXX=g++-14 cmake -G "Ninja" \
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DIMHEX_PATTERNS_PULL_MASTER=ON \
-DIMHEX_COMMIT_HASH_LONG="${GIT_COMMIT_HASH}" \
-DIMHEX_COMMIT_BRANCH="${GIT_BRANCH}" \
-DIMHEX_ENABLE_LTO=${LTO} \
-DIMHEX_BUNDLE_PLUGIN_SDK=OFF \
`# To prevent using a libdir with an architecture-specific name` \
-DCMAKE_INSTALL_LIBDIR="lib" \
/imhex
EOF

View File

@@ -4,7 +4,7 @@ Section: editors
Priority: optional
Architecture: amd64
License: GNU GPL-2
Depends: libfontconfig1, libglfw3 | libglfw3-wayland, libmagic1, libmbedtls14, libfreetype6, libopengl0, libdbus-1-3, xdg-desktop-portal, libssh2-1, md4c
Depends: libfontconfig1, libglfw3 | libglfw3-wayland, libmagic1, libmbedtls14, libfreetype6, libopengl0, libdbus-1-3, xdg-desktop-portal, libssh2-1, libmd4c0
Maintainer: WerWolv <hey@werwolv.net>
Description: ImHex Hex Editor
A Hex Editor for Reverse Engineers, Programmers and

2
dist/ImHex.run.xml vendored
View File

@@ -8,6 +8,6 @@
</method>
</configuration>
<configuration default="false" name="CMake Debug" type="CMakeListConfigurationType" factoryName="CMakeListConfigurationFactory">
<method v="2" />
<method v="2" />
</configuration>
</component>

View File

@@ -3,7 +3,7 @@
On macOS, ImHex is built through regular GCC and LLVM clang.
1. Clone the repo using `git clone https://github.com/WerWolv/ImHex --recurse-submodules`
2. Install all the dependencies using `brew bundle --no-lock --file dist/macOS/Brewfile`
2. Install all the dependencies using `brew bundle --file dist/macOS/Brewfile`
3. Build ImHex itself using the following commands:
```sh
cd ImHex
@@ -20,3 +20,5 @@ cmake -G "Ninja" \
..
ninja install
```
If your MacOS installation doesn't have graphic acceleration, you can check the [MacOS NoGPU guide](./macos_nogpu.md)

10
dist/compiling/macos_nogpu.md vendored Normal file
View File

@@ -0,0 +1,10 @@
### Compiling and running ImHex on macOS without a GPU
In order to run ImHex on a macOS installation without a GPU, you need a custom build of GLFW. You can build it this way:
Note: only tested on macOS x86
1. `git clone --depth 1 https://github.com/glfw/glfw`
2. `git apply {IMHEX_DIR}/dist/macOS/0001-glfw-SW.patch` (file is [here](../macOS/0001-glfw-SW.patch) in the ImHex repository. [Source](https://github.com/glfw/glfw/issues/2080).)
3. `cmake -G "Ninja" -DBUILD_SHARED_LIBS=ON ..`
4. `ninja install`, or `ninja` and figure out how to make ImHex detect the shared library

View File

@@ -120,6 +120,7 @@ modules:
- -DUSE_SYSTEM_FMT=ON
- -DUSE_SYSTEM_YARA=ON
- -DIMHEX_OFFLINE_BUILD=ON
- -DIMHEX_BUNDLE_PLUGIN_SDK=OFF
- -DCMAKE_INSTALL_LIBDIR=lib
- -DCMAKE_INSTALL_RPATH='$ORIGIN/../lib:$ORIGIN/../lib64'
sources:

View File

@@ -1,5 +1,5 @@
From 9c8665af4c2e2ce66555c15c05c72027bfdf0cb6 Mon Sep 17 00:00:00 2001
From: iTrooz <itrooz@protonmail.com>
From: iTrooz <hey@itrooz.fr>
Date: Mon, 29 Aug 2022 17:29:38 +0200
Subject: [PATCH] Use software rendering on MacOS

View File

@@ -1,7 +1,7 @@
# This base image is also known as "crosscompile". See arm64.crosscompile.Dockerfile
FROM ghcr.io/werwolv/macos-crosscompile:4c4af2d1a6a102fab93cc9cd660280c2ec9d72af as build
FROM ghcr.io/werwolv/macos-crosscompile:6d89b20ac5ebedb6f680f94637591c94cb36f40b as build
ENV MACOSX_DEPLOYMENT_TARGET 13.0
ENV MACOSX_DEPLOYMENT_TARGET 11.0
# -- DOWNLOADING STUFF
@@ -132,6 +132,7 @@ if [ "$CUSTOM_GLFW" ]; then
mkdir build
cd build
CC=o64-clang CXX=o64-clang++ cmake -G "Ninja" \
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DBUILD_SHARED_LIBS=ON \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
@@ -150,9 +151,7 @@ EOF
# Build ImHex
## Copy ImHex
COPY --from=imhex / /mnt/ImHex
## Patch ImHex with hacks
# COPY toolchain.cmake.2 /osxcross/target/toolchain.cmake
# Configure ImHex build
## Configure ImHex build
RUN --mount=type=cache,target=/cache --mount=type=cache,target=/mnt/ImHex/build/_deps \
cd /mnt/ImHex && \
# compilers
@@ -170,6 +169,7 @@ RUN --mount=type=cache,target=/cache --mount=type=cache,target=/mnt/ImHex/build/
-DIMHEX_STRICT_WARNINGS=OFF \
-DIMHEX_PATTERNS_PULL_MASTER=ON \
-DCMAKE_INSTALL_PREFIX=/mnt/ImHex/build/install \
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
-B build
## Build ImHex
RUN --mount=type=cache,target=/cache --mount=type=cache,target=/mnt/ImHex/build/_deps <<EOF
@@ -184,4 +184,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

11
dist/macOS/osx_10_15/x64-osx.cmake vendored Normal file
View File

@@ -0,0 +1,11 @@
set(VCPKG_TARGET_ARCHITECTURE x64)
set(VCPKG_BUILD_TYPE release)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE static)
set(VCPKG_CMAKE_SYSTEM_NAME Darwin)
set(VCPKG_OSX_ARCHITECTURES x86_64)
set(VCPKG_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "" FORCE)
set(VCPKG_C_FLAGS "-mmacosx-version-min=10.15")
set(VCPKG_CXX_FLAGS "-mmacosx-version-min=10.15")
set(ENV{MACOSX_DEPLOYMENT_TARGET} "10.15")

View File

@@ -41,6 +41,7 @@ parts:
- -DCMAKE_C_COMPILER_LAUNCHER=${CCACHE}
- -DCMAKE_CXX_COMPILER_LAUNCHER=${CCACHE}
- -DIMHEX_PATTERNS_PULL_MASTER=ON
- -DIMHEX_BUNDLE_PLUGIN_SDK=OFF
cmake-generator: Ninja
build-packages:
- cmake

12
dist/web/Dockerfile vendored
View File

@@ -2,7 +2,7 @@ FROM emscripten/emsdk:4.0.21 AS build
# Used to invalidate layer cache but not mount cache
# See https://github.com/moby/moby/issues/41715#issuecomment-733976493
ARG UNIQUEKEY 1
ARG UNIQUEKEY=1
RUN apt update
RUN apt install -y git ccache autoconf automake libtool pkg-config ninja-build
@@ -12,13 +12,13 @@ RUN <<EOF
# Note: we are a patch on the libmagic port
set -xe
git clone https://github.com/microsoft/vcpkg /vcpkg
git -C /vcpkg pull
git clone --depth 1 https://github.com/microsoft/vcpkg /vcpkg
/vcpkg/bootstrap-vcpkg.sh
sed -i 's/vcpkg_install_make(${EXTRA_ARGS})/vcpkg_install_make(${EXTRA_ARGS} SUBPATH src)/g' /vcpkg/ports/libmagic/portfile.cmake
EOF
# Patch vcpkg build instructions to add -pthread
# Patch vcpkg build instructions to add -pthread flag
# Even dependencies must be built with -pthread to be able to use USE_PTHREADS=1
RUN <<EOF
set -xe
@@ -50,6 +50,7 @@ ENV CCACHE_DIR=/cache/ccache
RUN mkdir /build
WORKDIR /build
ARG BUILD_TYPE=Release
RUN --mount=type=cache,target=/cache \
--mount=type=bind,source=.,target=/imhex <<EOF
@@ -70,7 +71,7 @@ ccache -zs
-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \
-DLIBROMFS_COMPRESS_RESOURCES=OFF \
-DIMHEX_ENABLE_PLUGIN_TESTS=OFF \
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_BUILD_TYPE=${BUILD_TYPE}
ninja -j $JOBS
@@ -106,3 +107,4 @@ COPY --from=build [ \
FROM nginx
COPY --from=raw . /usr/share/nginx/html
RUN chmod -R 755 /usr/share/nginx/html

9
dist/web/Host.Dockerfile vendored Normal file
View File

@@ -0,0 +1,9 @@
FROM python:3.12-slim
WORKDIR /imhex
COPY ./out/ .
EXPOSE 9090
CMD [ "python", "/imhex/start_imhex_web.py" ]

View File

@@ -1,5 +1,4 @@
# docker compose -f dist/web/compose.yml up --build
version: '3'
services:
imhex_web:
image: imhex_web:latest

View File

@@ -15,17 +15,17 @@
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://imhex.werwolv.net/">
<meta property="og:url" content="https://web.imhex.werwolv.net/">
<meta property="og:title" content="ImHex Web - Online Hex Editor">
<meta property="og:image" content="https://imhex.werwolv.net/assets/splash_wasm.png">
<meta property="og:image" content="splash_wasm.png">
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="https://imhex.werwolv.net/">
<meta property="twitter:url" content="https://web.imhex.werwolv.net/">
<meta property="twitter:title" content="ImHex Web - Online Hex Editor">
<meta property="twitter:description"
content="A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM.">
<meta property="twitter:image" content="https://imhex.werwolv.net/assets/splash_wasm.png">
<meta property="twitter:image" content="splash_wasm.png">
<link rel="stylesheet" type="text/css" href="style.css">
@@ -37,8 +37,8 @@
"email": "hey@werwolv.net",
"founder": "WerWolv",
"slogan": "A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM.",
"url": "https://imhex.werwolv.net",
"logo": "https://imhex.werwolv.net/assets/logos/logo.svg"
"url": "https://web.imhex.werwolv.net",
"logo": "https://web.imhex.werwolv.net/icon.svg"
}
</script>

BIN
dist/web/source/splash_wasm.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

View File

@@ -59,8 +59,14 @@ monkeyPatch((file, done) => {
const mibTotal = (wasmSize / 1024**2).toFixed(1);
let root = document.querySelector(':root');
root.style.setProperty("--progress", `${percent}%`)
document.getElementById("progress-bar-content").innerHTML = `${percent}% &nbsp;[${mibNow} MiB / ${mibTotal} MiB]`;
if (root != null) {
root.style.setProperty("--progress", `${percent}%`)
let progressBar = document.getElementById("progress-bar-content");
if (progressBar != null) {
progressBar.innerHTML = `${percent}% &nbsp;[${mibNow} MiB / ${mibTotal} MiB]`;
}
}
});
function glfwSetCursorCustom(wnd, shape) {
@@ -100,7 +106,9 @@ var notWorkingTimer = setTimeout(() => {
}, 5000);
var Module = {
preRun: [],
preRun: () => {
ENV.IMHEX_SKIP_SPLASH_SCREEN = "1";
},
postRun: function() {
// Patch the emscripten GLFW module to send mouse and touch events in the right order
// For ImGui interactions to correctly work with touch input, MousePos events need
@@ -158,7 +166,7 @@ var Module = {
if (status == 1) {
GLFW.active.buttons |= (1 << eventButton);
try {
event.target.setCapture();
event.target.setPointerCapture(event.pointerId);
} catch (e) {}
} else {
GLFW.active.buttons &= ~(1 << eventButton);
@@ -174,7 +182,9 @@ var Module = {
},
onRuntimeInitialized: function() {
// Triggered when the wasm module is loaded and ready to use.
document.getElementById("loading").style.display = "none"
let loading = document.getElementById("loading");
if (loading != null)
document.getElementById("loading").style.display = "none"
document.getElementById("canvas").style.display = "initial"
clearTimeout(notWorkingTimer);
@@ -188,6 +198,8 @@ var Module = {
e.preventDefault();
}, false);
js_resizeCanvas()
// Turn long touches into right-clicks
let timer = null;
canvas.addEventListener('touchstart', event => {
@@ -257,15 +269,17 @@ if (urlParams.has("lang")) {
Module["arguments"].push(urlParams.get("save-editor"));
}
window.addEventListener('resize', js_resizeCanvas, false);
function js_resizeCanvas() {
let canvas = document.getElementById('canvas');
canvas.top = document.documentElement.clientTop;
canvas.left = document.documentElement.clientLeft;
canvas.width = Math.min(document.documentElement.clientWidth, window.innerWidth || 0);
canvas.height = Math.min(document.documentElement.clientHeight, window.innerHeight || 0);
canvas.top = canvas.parentElement.clientTop;
canvas.left = canvas.parentElement.clientLeft;
canvas.style.width = "100%";
canvas.style.height = "100%";
}
let resizeObserver = new ResizeObserver(js_resizeCanvas);
resizeObserver.observe(document.getElementById("canvas"))
// Prevent some default browser shortcuts from preventing ImHex ones to work
document.addEventListener('keydown', e => {

View File

@@ -57,6 +57,9 @@ set(LIBIMHEX_SOURCES
source/ui/toast.cpp
source/ui/banner.cpp
source/mcp/client.cpp
source/mcp/server.cpp
source/subcommands/subcommands.cpp
)

View File

@@ -145,62 +145,17 @@ EXPORT_MODULE namespace hex {
* @brief Returns the icon of the achievement
* @return Icon of the achievement
*/
[[nodiscard]] const ImGuiExt::Texture &getIcon() const {
if (m_iconData.empty())
return m_icon;
if (m_icon.isValid())
return m_icon;
m_icon = ImGuiExt::Texture::fromImage(m_iconData.data(), m_iconData.size(), ImGuiExt::Texture::Filter::Linear);
return m_icon;
[[nodiscard]] const char* getIcon() const {
return m_icon.c_str();
}
/**
* @brief Sets the icon of the achievement
* @param data Icon data
* @param icon Icon glyph
* @return Reference to the achievement
*/
Achievement& setIcon(std::span<const std::byte> data) {
m_iconData.reserve(data.size());
for (auto &byte : data)
m_iconData.emplace_back(static_cast<u8>(byte));
return *this;
}
/**
* @brief Sets the icon of the achievement
* @param data Icon data
* @return Reference to the achievement
*/
Achievement& setIcon(std::span<const u8> data) {
m_iconData.assign(data.begin(), data.end());
return *this;
}
/**
* @brief Sets the icon of the achievement
* @param data Icon data
* @return Reference to the achievement
*/
Achievement& setIcon(std::vector<u8> data) {
m_iconData = std::move(data);
return *this;
}
/**
* @brief Sets the icon of the achievement
* @param data Icon data
* @return Reference to the achievement
*/
Achievement& setIcon(const std::vector<std::byte> &data) {
m_iconData.reserve(data.size());
for (auto &byte : data)
m_iconData.emplace_back(static_cast<u8>(byte));
Achievement& setIcon(std::string icon) {
m_icon = std::move(icon);
return *this;
}
@@ -284,8 +239,7 @@ EXPORT_MODULE namespace hex {
std::function<void(Achievement &)> m_clickCallback;
std::vector<u8> m_iconData;
mutable ImGuiExt::Texture m_icon;
std::string m_icon;
u32 m_progress = 0;
u32 m_maxProgress = 1;

View File

@@ -16,7 +16,7 @@ EXPORT_MODULE namespace hex {
void stopServices();
}
void registerService(const UnlocalizedString &unlocalizedString, const impl::Callback &callback);
void registerService(const UnlocalizedString &unlocalizedName, const impl::Callback &callback);
}
}

View File

@@ -7,6 +7,8 @@
#include <map>
#include <string>
#include <hex/mcp/server.hpp>
EXPORT_MODULE namespace hex {
/* Network Communication Interface Registry. Allows adding new communication interface endpoints */
@@ -22,4 +24,19 @@ EXPORT_MODULE namespace hex {
}
namespace ContentRegistry::MCP {
namespace impl {
std::unique_ptr<mcp::Server>& getMcpServerInstance();
void setEnabled(bool enabled);
}
bool isEnabled();
bool isConnected();
void registerTool(std::string_view capabilities, std::function<nlohmann::json(const nlohmann::json &params)> function);
}
}

View File

@@ -8,6 +8,7 @@
#include <optional>
#include <string>
#include <vector>
#include <bit>
EXPORT_MODULE namespace hex {
@@ -22,8 +23,10 @@ EXPORT_MODULE namespace hex {
namespace impl {
struct DoNotUseThisByItselfTag {};
using DisplayFunction = std::function<std::string()>;
using EditingFunction = std::function<std::vector<u8>(std::string, std::endian)>;
using EditingFunction = std::function<std::optional<std::vector<u8>>(std::string&, std::endian, DoNotUseThisByItselfTag)>;
using GeneratorFunction = std::function<DisplayFunction(const std::vector<u8> &, std::endian, NumberDisplayStyle)>;
struct Entry {
@@ -38,6 +41,35 @@ EXPORT_MODULE namespace hex {
}
namespace EditWidget {
class Widget {
public:
using Function = std::function<std::vector<u8>(const std::string&, std::endian)>;
explicit Widget(const Function &function) : m_function(function) {}
virtual ~Widget() = default;
virtual std::optional<std::vector<u8>> draw(std::string &value, std::endian endian) = 0;
std::optional<std::vector<u8>> operator()(std::string &value, std::endian endian, impl::DoNotUseThisByItselfTag) {
return draw(value, endian);
}
std::vector<u8> getBytes(const std::string &value, std::endian endian) const {
return m_function(value, endian);
}
private:
Function m_function;
};
struct TextInput : Widget {
explicit TextInput(const Function &function) : Widget(function) {}
std::optional<std::vector<u8>> draw(std::string &value, std::endian endian) override;
};
}
/**
* @brief Adds a new entry to the data inspector
* @param unlocalizedName The unlocalized name of the entry

View File

@@ -19,7 +19,7 @@ EXPORT_MODULE namespace hex {
void addProviderName(const UnlocalizedString &unlocalizedName, const char *icon);
using ProviderCreationFunction = std::function<std::unique_ptr<prv::Provider>()>;
using ProviderCreationFunction = std::function<std::shared_ptr<prv::Provider>()>;
void add(const std::string &typeName, ProviderCreationFunction creationFunction);
struct Entry {

View File

@@ -52,7 +52,7 @@ EXPORT_MODULE namespace hex {
return *this;
}
Interface& setTooltip(const std::string &tooltip) {
Interface& setTooltip(const UnlocalizedString &tooltip) {
m_tooltip = tooltip;
return *this;
@@ -239,6 +239,14 @@ EXPORT_MODULE namespace hex {
nlohmann::json store() override { return {}; }
};
class Spacer : public Widget {
public:
bool draw(const std::string &name) override;
void load(const nlohmann::json &) override {}
nlohmann::json store() override { return {}; }
};
}
namespace impl {
@@ -290,8 +298,8 @@ EXPORT_MODULE namespace hex {
public:
SettingsValue(nlohmann::json value) : m_value(std::move(value)) {}
template<typename T>
T get(std::common_type_t<T> defaultValue) const {
template<typename T> requires (!(std::is_reference_v<T> || std::is_const_v<T>))
[[nodiscard]] T get(T defaultValue) const {
try {
auto result = m_value;
if (result.is_number() && std::same_as<T, bool>)
@@ -308,8 +316,8 @@ EXPORT_MODULE namespace hex {
nlohmann::json m_value;
};
template<typename T>
[[nodiscard]] T read(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const std::common_type_t<T> &defaultValue) {
template<typename T> requires (!(std::is_reference_v<T> || std::is_const_v<T>))
[[nodiscard]] T read(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, T defaultValue) {
auto setting = impl::getSetting(unlocalizedCategory, unlocalizedName, defaultValue);
try {
@@ -326,8 +334,8 @@ EXPORT_MODULE namespace hex {
}
}
template<typename T>
void write(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const std::common_type_t<T> &value) {
template<typename T> requires (!(std::is_reference_v<T> || std::is_const_v<T>))
void write(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, T value) {
impl::getSetting(unlocalizedCategory, unlocalizedName, value) = value;
impl::runOnChangeHandlers(unlocalizedCategory, unlocalizedName, value);
@@ -336,10 +344,75 @@ EXPORT_MODULE namespace hex {
using OnChangeCallback = std::function<void(const SettingsValue &)>;
u64 onChange(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const OnChangeCallback &callback);
void removeOnChangeHandler(u64 id);
using OnSaveCallback = std::function<void()>;
u64 onSave(const OnSaveCallback &callback);
template<typename T, wolv::type::StaticString UnlocalizedCategory, wolv::type::StaticString UnlocalizedName>
requires (!(std::is_reference_v<T> || std::is_const_v<T>))
class SettingsVariable {
public:
explicit(false) SettingsVariable(T defaultValue) : m_defaultValue(std::move(defaultValue)) { }
SettingsVariable(const SettingsVariable&) = delete;
SettingsVariable& operator=(const SettingsVariable&) = delete;
SettingsVariable(SettingsVariable&&) = delete;
SettingsVariable& operator=(SettingsVariable&&) = delete;
~SettingsVariable() {
if (m_onChangeId > 0)
removeOnChangeHandler(m_onChangeId);
}
[[nodiscard]] T get() const {
registerChangeHandler();
if (!m_value.has_value()) {
m_value = read<T>(
UnlocalizedCategory.value.data(),
UnlocalizedName.value.data(),
m_defaultValue
);
}
return m_value.value_or(m_defaultValue);
}
void set(T value) {
registerChangeHandler();
write<T>(
UnlocalizedCategory.value.data(),
UnlocalizedName.value.data(),
std::move(value)
);
}
explicit(false) operator T() const {
return get();
}
SettingsVariable& operator=(T value) {
set(std::move(value));
return *this;
}
private:
void registerChangeHandler() const {
if (m_onChangeId > 0)
return;
m_onChangeId = onChange(UnlocalizedCategory.value.data(), UnlocalizedName.value.data(), [this](const SettingsValue &value) {
m_value = value.get<T>(m_defaultValue);
});
}
private:
mutable std::optional<T> m_value;
T m_defaultValue;
mutable u64 m_onChangeId = 0;
};
}
}

View File

@@ -68,6 +68,7 @@ EXPORT_MODULE namespace hex {
constexpr static auto SeparatorValue = "$SEPARATOR$";
constexpr static auto SubMenuValue = "$SUBMENU$";
constexpr static auto TaskBarMenuValue = "$TASKBAR$";
const std::multimap<u32, MainMenuItem>& getMainMenuItems();
@@ -199,6 +200,19 @@ EXPORT_MODULE namespace hex {
*/
void addMenuItemSeparator(std::vector<UnlocalizedString> unlocalizedMainMenuNames, u32 priority, View *view = nullptr);
/**
* @brief Adds a new main menu entry
* @param unlocalizedMainMenuNames The unlocalized names of the main menu entries
* @param priority The priority of the entry. Lower values are displayed first
* @param function The function to call when the entry is clicked
* @param enabledCallback The function to call to determine if the entry is enabled
*/
void addTaskBarMenuItem(
std::vector<UnlocalizedString> unlocalizedMainMenuNames,
u32 priority,
const impl::MenuCallback &function,
const impl::EnabledCallback& enabledCallback
);
/**
* @brief Adds a new welcome screen entry
@@ -220,10 +234,10 @@ EXPORT_MODULE namespace hex {
/**
* @brief Adds a menu item to the toolbar
* @param unlocalizedName Unlocalized name of the menu item
* @param unlocalizedNames Unlocalized name of the menu item
* @param color Color of the toolbar icon
*/
void addMenuItemToToolbar(const UnlocalizedString &unlocalizedName, ImGuiCustomCol color);
void addMenuItemToToolbar(const std::vector<UnlocalizedString> &unlocalizedNames, ImGuiCustomCol color);
/**
* @brief Reconstructs the toolbar items list after they have been modified

View File

@@ -15,7 +15,7 @@ namespace hex {
* This event is responsible for (optionally) initializing the provider and calling EventProviderOpened
* (although the event can also be called manually without problem)
*/
EVENT_DEF(EventProviderCreated, prv::Provider *);
EVENT_DEF(EventProviderCreated, std::shared_ptr<prv::Provider>);
/**
* @brief Called as a continuation of EventProviderCreated

View File

@@ -8,7 +8,12 @@ namespace hex {
/**
* @brief Creates a provider from its unlocalized name, and add it to the provider list
*/
EVENT_DEF(RequestCreateProvider, std::string, bool, bool, hex::prv::Provider **);
EVENT_DEF(RequestCreateProvider, std::string, bool, bool, std::shared_ptr<hex::prv::Provider> *);
/**
* @brief Used internally when opening a provider through the API
*/
EVENT_DEF(RequestOpenProvider, std::shared_ptr<prv::Provider>);
/**
* @brief Move the data from all PerProvider instances from one provider to another

View File

@@ -86,7 +86,7 @@ EXPORT_MODULE namespace hex {
* @param skipLoadInterface Whether to skip the provider's loading interface (see property documentation)
* @param select Whether to select the provider after adding it
*/
void add(std::unique_ptr<prv::Provider> &&provider, bool skipLoadInterface = false, bool select = true);
void add(std::shared_ptr<prv::Provider> &&provider, bool skipLoadInterface = false, bool select = true);
/**
* @brief Creates a new provider and adds it to the list of providers
@@ -111,12 +111,18 @@ EXPORT_MODULE namespace hex {
* @param skipLoadInterface Whether to skip the provider's loading interface (see property documentation)
* @param select Whether to select the provider after adding it
*/
prv::Provider* createProvider(
std::shared_ptr<prv::Provider> createProvider(
const UnlocalizedString &unlocalizedName,
bool skipLoadInterface = false,
bool select = true
);
/**
* @brief Opens a provider, making its data available to ImHex and handling any error that may occur
* @param provider The provider to open
*/
void openProvider(std::shared_ptr<prv::Provider> provider);
}
}

View File

@@ -62,6 +62,7 @@ EXPORT_MODULE namespace hex {
void setMainWindowSize(u32 width, u32 height);
void setMainDockSpaceId(ImGuiID id);
void setMainWindowHandle(GLFWwindow *window);
void setMainWindowFocusState(bool focused);
void setGlobalScale(float scale);
void setNativeScale(float scale);
@@ -161,6 +162,12 @@ EXPORT_MODULE namespace hex {
*/
GLFWwindow* getMainWindowHandle();
/**
* @brief Checks if the main window is currently focused
* @return Whether the main window is focused
*/
bool isMainWindowFocused();
/**
* @brief Checks if borderless window mode is enabled currently
* @return Whether borderless window mode is enabled

View File

@@ -10,6 +10,7 @@
#include <condition_variable>
#include <source_location>
#include <thread>
#include <hex/trace/exceptions.hpp>
EXPORT_MODULE namespace hex {
@@ -22,7 +23,7 @@ EXPORT_MODULE namespace hex {
class Task {
public:
Task() = default;
Task(const UnlocalizedString &unlocalizedName, u64 maxValue, bool background, bool blocking, std::function<void(Task &)> function);
Task(UnlocalizedString unlocalizedName, u64 maxValue, bool background, bool blocking, std::function<void(Task &)> function);
Task(const Task&) = delete;
Task(Task &&other) noexcept;
@@ -94,7 +95,16 @@ EXPORT_MODULE namespace hex {
std::atomic_flag m_hadException;
std::string m_exceptionMessage;
struct TaskInterruptor { virtual ~TaskInterruptor() = default; };
struct TaskInterruptor: public std::exception {
TaskInterruptor() {
trace::disableExceptionCaptureForCurrentThread();
}
virtual ~TaskInterruptor() = default;
[[nodiscard]] const char* what() const noexcept override {
return "Task Interrupted";
}
};
friend class TaskHolder;
friend class TaskManager;
@@ -242,6 +252,8 @@ EXPORT_MODULE namespace hex {
static const std::list<std::shared_ptr<Task>>& getRunningTasks();
static void runDeferredCalls();
static void addTaskCompletionCallback(const std::function<void(Task&)>& function);
private:
static TaskHolder createTask(const UnlocalizedString &unlocalizedName, u64 maxValue, bool background, bool blocking, std::function<void(Task &)> function);
};

View File

@@ -10,6 +10,8 @@
#include <hex/ui/imgui_imhex_extensions.h>
struct ImRect;
EXPORT_MODULE namespace hex {
class TutorialManager {
@@ -22,6 +24,8 @@ EXPORT_MODULE namespace hex {
Right = 8
};
using DrawFunction = std::function<void()>;
struct Tutorial {
Tutorial() = delete;
Tutorial(const UnlocalizedString &unlocalizedName, const UnlocalizedString &unlocalizedDescription) :
@@ -101,6 +105,7 @@ EXPORT_MODULE namespace hex {
std::vector<Highlight> m_highlights;
std::optional<Message> m_message;
std::function<void()> m_onAppear, m_onComplete;
DrawFunction m_drawFunction;
};
Step& addStep();
@@ -146,6 +151,7 @@ EXPORT_MODULE namespace hex {
* @param unlocalizedName Name of tutorial to start
*/
static void startTutorial(const UnlocalizedString &unlocalizedName);
static void stopCurrentTutorial();
static void startHelpHover();
static void addInteractiveHelpText(std::initializer_list<std::variant<Lang, std::string, int>> &&ids, UnlocalizedString unlocalizedString);
@@ -166,6 +172,10 @@ EXPORT_MODULE namespace hex {
*/
static void reset();
static void setRenderer(std::function<DrawFunction(const std::string &)> renderer);
static void postElementRendered(ImGuiID id, const ImRect &boundingBox);
private:
TutorialManager() = delete;

View File

@@ -7,6 +7,7 @@
#include <set>
#include <span>
#include <utility>
#include <vector>
#include <nlohmann/json_fwd.hpp>
@@ -49,9 +50,15 @@ namespace hex::dp {
virtual void store(nlohmann::json &j) const { std::ignore = j; }
virtual void load(const nlohmann::json &j) { std::ignore = j; }
struct NodeError {
struct NodeError: public std::exception {
Node *node;
std::string message;
NodeError(Node *node, std::string message) : node(node), message(std::move(message)) {}
[[nodiscard]] const char* what() const noexcept override {
return this->message.c_str();
}
};
void resetOutputData() {
@@ -102,7 +109,7 @@ namespace hex::dp {
void unmarkInputProcessed(u32 index);
protected:
[[noreturn]] void throwNodeError(const std::string &message);
[[noreturn]] void throwNodeError(const std::string &msg);
void setOverlayData(u64 address, const std::vector<u8> &data);
void setAttributes(std::vector<Attribute> attributes);

View File

@@ -1,6 +1,7 @@
#pragma once
#include <hex/api/imhex_api/system.hpp>
#include <hex/helpers/logger.hpp>
namespace hex {
@@ -9,6 +10,9 @@ namespace hex {
class AutoResetBase {
public:
virtual ~AutoResetBase() = default;
private:
friend void ImHexApi::System::impl::cleanup();
virtual void reset() = 0;
};
@@ -19,16 +23,20 @@ namespace hex {
public:
using Type = T;
AutoReset() {
ImHexApi::System::impl::addAutoResetObject(this);
AutoReset() noexcept {
try {
ImHexApi::System::impl::addAutoResetObject(this);
} catch (std::exception &e) {
log::error("Failed to register AutoReset object: {}", e.what());
}
}
AutoReset(const T &value) : AutoReset() {
explicit(false) AutoReset(const T &value) : AutoReset() {
m_value = value;
m_valid = true;
}
AutoReset(T &&value) noexcept : AutoReset() {
explicit(false) AutoReset(T &&value) noexcept : AutoReset() {
m_value = std::move(value);
m_valid = true;
}
@@ -61,29 +69,27 @@ namespace hex {
return m_value;
}
T& operator=(const T &value) {
AutoReset& operator=(const T &value) {
m_value = value;
m_valid = true;
return m_value;
return *this;
}
T& operator=(T &&value) noexcept {
AutoReset& operator=(T &&value) noexcept {
m_value = std::move(value);
m_valid = true;
return m_value;
return *this;
}
bool isValid() const {
[[nodiscard]] bool isValid() const {
return m_valid;
}
private:
friend void ImHexApi::System::impl::cleanup();
void reset() override {
if constexpr (requires { m_value.reset(); }) {
if constexpr (requires(T t) { t.reset(); }) {
m_value.reset();
} else if constexpr (requires { m_value.clear(); }) {
} else if constexpr (requires(T t) { t.clear(); }) {
m_value.clear();
} else if constexpr (std::is_pointer_v<T>) {
m_value = nullptr; // cppcheck-suppress nullPointer

View File

@@ -14,6 +14,10 @@
static_assert(false, "Debug variables are only intended for use during development.");
#endif
namespace hex::trace {
struct StackTraceResult;
}
namespace hex::dbg {
namespace impl {
@@ -47,4 +51,6 @@ namespace hex::dbg {
bool debugModeEnabled();
void setDebugModeEnabled(bool enabled);
void printStackTrace(const trace::StackTraceResult &stackTrace);
}

View File

@@ -12,6 +12,9 @@ namespace hex::menu {
bool beginMenu(const char *label, bool enabled = true);
void endMenu();
bool beginTaskBarMenu();
void endTaskBarMenu();
bool beginMenuEx(const char* label, const char* icon, bool enabled = true);
bool menuItem(const char *label, const Shortcut &shortcut = Shortcut::None, bool selected = false, bool enabled = true);

View File

@@ -10,6 +10,7 @@
#include <span>
#include <string>
#include <numbers>
#include <array>
#include <opengl_support.h>
#include "imgui.h"
@@ -935,7 +936,7 @@ namespace hex::gl {
void attachTexture(const Texture &texture) const;
private:
GLuint m_frameBuffer, m_renderBuffer;
GLuint m_frameBuffer = 0, m_renderBuffer = 0;
};
class AxesVectors {

View File

@@ -98,6 +98,8 @@ namespace hex {
void startProgram(const std::vector<std::string> &command);
int executeCommand(const std::string &command);
std::optional<std::string> executeCommandWithOutput(const std::string &command);
void executeCommandDetach(const std::string &command);
void openWebpage(std::string url);
extern "C" void registerFont(const char *fontName, const char *fontPath);

View File

@@ -32,6 +32,7 @@
void macosInstallEventListener();
void toastMessageMacos(const char *title, const char *message);
void macosSetupDockMenu(void);
}
#endif

View File

@@ -0,0 +1,15 @@
#pragma once
#include <iostream>
namespace hex::mcp {
class Client {
public:
Client() = default;
~Client() = default;
int run(std::istream &input, std::ostream &output);
};
}

View File

@@ -0,0 +1,123 @@
#pragma once
#include <hex.hpp>
#include <functional>
#include <nlohmann/json.hpp>
#include <wolv/net/socket_server.hpp>
namespace hex::mcp {
class JsonRpc {
public:
explicit JsonRpc(std::string request) : m_request(std::move(request)){ }
struct MethodNotFoundException : std::exception {};
struct InvalidParametersException : std::exception {};
enum class ErrorCode: i16 {
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
};
using Callback = std::function<nlohmann::json(const std::string &method, const nlohmann::json &params)>;
std::optional<std::string> execute(const Callback &callback);
void setError(ErrorCode code, std::string message);
private:
std::optional<nlohmann::json> handleMessage(const nlohmann::json &request, const Callback &callback);
std::optional<nlohmann::json> handleBatchedMessages(const nlohmann::json &request, const Callback &callback);
nlohmann::json createDefaultMessage();
nlohmann::json createErrorMessage(ErrorCode code, const std::string &message);
nlohmann::json createResponseMessage(const nlohmann::json &result);
private:
std::string m_request;
std::optional<int> m_id;
struct Error {
ErrorCode code;
std::string message;
};
std::optional<Error> m_error;
};
struct TextContent {
std::string text;
operator nlohmann::json() const {
nlohmann::json result;
result["content"] = nlohmann::json::array({
nlohmann::json::object({
{ "type", "text" },
{ "text", text }
})
});
return result;
}
};
struct StructuredContent {
std::string text;
nlohmann::json data;
operator nlohmann::json() const {
nlohmann::json result;
result["content"] = nlohmann::json::array({
nlohmann::json::object({
{ "type", "text" },
{ "text", text }
})
});
result["structuredContent"] = data;
return result;
}
};
class Server {
public:
constexpr static auto McpInternalPort = 19743;
Server();
~Server();
void listen();
void shutdown();
void disconnect();
bool isConnected();
void addPrimitive(std::string type, std::string_view capabilities, std::function<nlohmann::json(const nlohmann::json &params)> function);
struct ClientInfo {
std::string name;
std::string version;
std::string protocolVersion;
};
const ClientInfo& getClientInfo() const {
return m_clientInfo;
}
private:
nlohmann::json handleInitialize(const nlohmann::json &params);
void handleNotifications(const std::string &method, const nlohmann::json &params);
struct Primitive {
nlohmann::json capabilities;
std::function<nlohmann::json(const nlohmann::json &params)> function;
};
std::map<std::string, std::map<std::string, Primitive>> m_primitives;
wolv::net::SocketServer m_server;
bool m_connected = false;
ClientInfo m_clientInfo;
};
}

View File

@@ -21,7 +21,7 @@ namespace hex::prv {
CachedProvider(size_t cacheBlockSize = 4096, size_t maxBlocks = 1024);
~CachedProvider() override;
bool open() override;
OpenResult open() override;
void close() override;
void readRaw(u64 offset, void *buffer, size_t size) override;

View File

@@ -27,7 +27,7 @@ namespace hex::prv {
[[nodiscard]] bool isSavable() const override { return m_name.empty(); }
[[nodiscard]] bool isSavableAsRecent() const override { return false; }
[[nodiscard]] bool open() override;
[[nodiscard]] OpenResult open() override;
void close() override { }
void readRaw(u64 offset, void *buffer, size_t size) override;

View File

@@ -72,6 +72,21 @@ namespace hex::prv {
[[nodiscard]] virtual std::vector<Description> getDataDescription() const = 0;
};
class IProviderDataBackupable {
public:
explicit IProviderDataBackupable(Provider *provider);
virtual ~IProviderDataBackupable() = default;
void createBackupIfNeeded(const std::fs::path &inputFilePath);
private:
Provider *m_provider = nullptr;
bool m_backupCreated = false;
bool m_shouldCreateBackups = true;
u64 m_maxSize;
std::string m_backupExtension;
};
/**
* @brief Represent the data source for a tab in the UI
*/
@@ -79,6 +94,65 @@ namespace hex::prv {
public:
constexpr static u64 MaxPageSize = 0xFFFF'FFFF'FFFF'FFFF;
class OpenResult {
public:
OpenResult() : m_result(std::monostate{}) {}
[[nodiscard]] static OpenResult failure(std::string errorMessage) {
OpenResult result;
result.m_result = std::move(errorMessage);
return result;
}
[[nodiscard]] static OpenResult warning(std::string warningMessage) {
OpenResult result;
result.m_result = std::move(warningMessage);
result.m_warning = true;
return result;
}
[[nodiscard]] static OpenResult redirect(Provider *provider) {
OpenResult result;
result.m_result = provider;
return result;
}
[[nodiscard]] bool isSuccess() const {
return std::holds_alternative<std::monostate>(m_result);
}
[[nodiscard]] bool isFailure() const {
return std::holds_alternative<std::string>(m_result) && !m_warning;
}
[[nodiscard]] bool isWarning() const {
return std::holds_alternative<std::string>(m_result) && m_warning;
}
[[nodiscard]] bool isRedirecting() const {
return std::holds_alternative<Provider*>(m_result);
}
[[nodiscard]] Provider* getRedirectProvider() const {
if (std::holds_alternative<Provider*>(m_result)) {
return std::get<Provider*>(m_result);
}
return nullptr;
}
[[nodiscard]] std::string_view getErrorMessage() const {
if (std::holds_alternative<std::string>(m_result)) {
return std::get<std::string>(m_result);
}
return "";
}
private:
std::variant<std::monostate, std::string, Provider*> m_result;
bool m_warning = false;
};
Provider();
virtual ~Provider();
Provider(const Provider&) = delete;
@@ -94,7 +168,7 @@ namespace hex::prv {
* @note This is not related to the EventProviderOpened event
* @return true if the provider was opened successfully, else false
*/
[[nodiscard]] virtual bool open() = 0;
[[nodiscard]] virtual OpenResult open() = 0;
/**
* @brief Closes this provider
@@ -262,9 +336,6 @@ namespace hex::prv {
void skipLoadInterface() { m_skipLoadInterface = true; }
[[nodiscard]] bool shouldSkipLoadInterface() const { return m_skipLoadInterface; }
void setErrorMessage(const std::string &errorMessage) { m_errorMessage = errorMessage; }
[[nodiscard]] const std::string& getErrorMessage() const { return m_errorMessage; }
template<std::derived_from<undo::Operation> T>
bool addUndoableOperation(auto && ... args) {
return m_undoRedoStack.add<T>(std::forward<decltype(args)...>(args)...);
@@ -296,8 +367,6 @@ namespace hex::prv {
*/
bool m_skipLoadInterface = false;
std::string m_errorMessage = "Unspecified error";
u64 m_pageSize = MaxPageSize;
};

View File

@@ -7,6 +7,7 @@
#include <map>
#include <memory>
#include <mutex>
#include <vector>
namespace hex::prv {
@@ -40,6 +41,8 @@ namespace hex::prv::undo {
bool add(std::unique_ptr<Operation> &&operation);
static std::recursive_mutex& getMutex();
const std::vector<std::unique_ptr<Operation>> &getAppliedOperations() const {
return m_undoStack;
}

View File

@@ -49,7 +49,7 @@ namespace hex::test {
[[nodiscard]] UnlocalizedString getTypeName() const override { return "hex.test.provider.test"; }
bool open() override { return true; }
OpenResult open() override { return {}; }
void close() override { }
nlohmann::json storeSettings(nlohmann::json) const override { return {}; }

View File

@@ -319,6 +319,7 @@ namespace ImGuiExt {
bool DimmedButtonToggle(const char *icon, bool *v, ImVec2 size = ImVec2(0, 0), ImVec2 iconOffset = ImVec2(0, 0));
bool DimmedIconToggle(const char *icon, bool *v);
bool DimmedIconToggle(const char *iconOn, const char *iconOff, bool *v);
bool DimmedArrowButton(const char *id, ImGuiDir dir, ImVec2 size = ImVec2(ImGui::GetFrameHeight(), ImGui::GetFrameHeight()));
void TextOverlay(const char *text, ImVec2 pos, float maxWidth = -1);

View File

@@ -14,7 +14,6 @@
#include <map>
#include <string>
#include <hex/api/tutorial_manager.hpp>
namespace hex {
@@ -27,7 +26,7 @@ namespace hex {
* @brief Draws the view
* @note Do not override this method. Override drawContent() instead
*/
virtual void draw() = 0;
virtual void draw(ImGuiWindowFlags extraFlags = ImGuiWindowFlags_None) = 0;
/**
* @brief Draws the content of the view
@@ -126,6 +125,7 @@ namespace hex {
class Window;
class Special;
class Floating;
class Scrolling;
class Modal;
class FullScreen;
@@ -153,16 +153,10 @@ namespace hex {
*/
virtual void drawHelpText() = 0;
void draw() final {
if (this->shouldDraw()) {
ImGui::SetNextWindowSizeConstraints(this->getMinSize(), this->getMaxSize());
const auto title = fmt::format("{} {}", this->getIcon(), View::toWindowName(this->getUnlocalizedName()));
if (ImGui::Begin(title.c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse | this->getWindowFlags())) {
TutorialManager::setLastItemInteractiveHelpPopup([this]{ this->drawHelpText(); });
this->drawContent();
}
ImGui::End();
}
void draw(ImGuiWindowFlags extraFlags = ImGuiWindowFlags_None) override;
virtual bool allowScroll() const {
return false;
}
};
@@ -174,12 +168,7 @@ namespace hex {
public:
explicit Special(UnlocalizedString unlocalizedName) : View(std::move(unlocalizedName), "") {}
void draw() final {
if (this->shouldDraw()) {
ImGui::SetNextWindowSizeConstraints(this->getMinSize(), this->getMaxSize());
this->drawContent();
}
}
void draw(ImGuiWindowFlags extraFlags = ImGuiWindowFlags_None) final;
};
/**
@@ -189,7 +178,24 @@ namespace hex {
public:
explicit Floating(UnlocalizedString unlocalizedName, const char *icon) : Window(std::move(unlocalizedName), icon) {}
[[nodiscard]] ImGuiWindowFlags getWindowFlags() const override { return ImGuiWindowFlags_NoDocking; }
void draw(ImGuiWindowFlags extraFlags = ImGuiWindowFlags_None) final;
[[nodiscard]] bool shouldStoreWindowState() const override { return false; }
};
/**
* @brief A view that draws all its content at once without any scrolling being done by the window itself
*/
class View::Scrolling : public View::Window {
public:
explicit Scrolling(UnlocalizedString unlocalizedName, const char *icon) : Window(std::move(unlocalizedName), icon) {}
void draw(ImGuiWindowFlags extraFlags = ImGuiWindowFlags_None) final;
bool allowScroll() const final {
return true;
}
[[nodiscard]] bool shouldStoreWindowState() const override { return false; }
};
@@ -200,24 +206,7 @@ namespace hex {
public:
explicit Modal(UnlocalizedString unlocalizedName, const char *icon) : View(std::move(unlocalizedName), icon) {}
void draw() final {
if (this->shouldDraw()) {
if (this->getWindowOpenState())
ImGui::OpenPopup(View::toWindowName(this->getUnlocalizedName()).c_str());
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F));
ImGui::SetNextWindowSizeConstraints(this->getMinSize(), this->getMaxSize());
const auto title = fmt::format("{} {}", this->getIcon(), View::toWindowName(this->getUnlocalizedName()));
if (ImGui::BeginPopupModal(title.c_str(), this->hasCloseButton() ? &this->getWindowOpenState() : nullptr, ImGuiWindowFlags_NoCollapse | this->getWindowFlags())) {
this->drawContent();
ImGui::EndPopup();
}
if (ImGui::IsKeyPressed(ImGuiKey_Escape))
this->getWindowOpenState() = false;
}
}
void draw(ImGuiWindowFlags extraFlags = ImGuiWindowFlags_None) final;
[[nodiscard]] virtual bool hasCloseButton() const { return true; }
[[nodiscard]] bool shouldStoreWindowState() const override { return false; }
@@ -227,10 +216,7 @@ namespace hex {
public:
explicit FullScreen() : View("FullScreen", "") {}
void draw() final {
this->drawContent();
this->drawAlwaysVisibleContent();
}
void draw(ImGuiWindowFlags extraFlags = ImGuiWindowFlags_None) final;
};
}

View File

@@ -264,8 +264,7 @@ namespace hex {
}
}
if (json.empty())
return;
if (json.empty()) return;
#if defined(OS_WEB)
auto data = json.dump();

View File

@@ -594,6 +594,12 @@ namespace hex {
return false;
}
bool Spacer::draw(const std::string& name) {
std::ignore = name;
ImGui::NewLine();
return false;
}
}
@@ -624,22 +630,22 @@ namespace hex {
void add(Type type, const std::string &command, const UnlocalizedString &unlocalizedDescription, const impl::DisplayCallback &displayCallback, const impl::ExecuteCallback &executeCallback) {
log::debug("Registered new command palette command: {}", command);
impl::s_entries->push_back(impl::Entry { type, command, unlocalizedDescription, displayCallback, executeCallback });
impl::s_entries->push_back(impl::Entry { .type=type, .command=command, .unlocalizedDescription=unlocalizedDescription, .displayCallback=displayCallback, .executeCallback=executeCallback });
}
void addHandler(Type type, const std::string &command, const impl::QueryCallback &queryCallback, const impl::DisplayCallback &displayCallback) {
log::debug("Registered new command palette command handler: {}", command);
impl::s_handlers->push_back(impl::Handler { type, command, queryCallback, displayCallback });
impl::s_handlers->push_back(impl::Handler { .type=type, .command=command, .queryCallback=queryCallback, .displayCallback=displayCallback });
}
void setDisplayedContent(const impl::ContentDisplayCallback &displayCallback) {
impl::s_displayedContent = impl::ContentDisplay { true, displayCallback };
impl::s_displayedContent = impl::ContentDisplay { .showSearchBox=true, .callback=displayCallback };
}
void openWithContent(const impl::ContentDisplayCallback &displayCallback) {
RequestOpenCommandPalette::post();
impl::s_displayedContent = impl::ContentDisplay { false, displayCallback };
impl::s_displayedContent = impl::ContentDisplay { .showSearchBox=false, .callback=displayCallback };
}
}
@@ -777,12 +783,12 @@ namespace hex {
void addVisualizer(const std::string &name, const impl::VisualizerFunctionCallback &function, pl::api::FunctionParameterCount parameterCount) {
log::debug("Registered new pattern visualizer function: {}", name);
(*impl::s_visualizers)[name] = impl::Visualizer { parameterCount, function };
(*impl::s_visualizers)[name] = impl::Visualizer { .parameterCount=parameterCount, .callback=function };
}
void addInlineVisualizer(const std::string &name, const impl::VisualizerFunctionCallback &function, pl::api::FunctionParameterCount parameterCount) {
log::debug("Registered new inline pattern visualizer function: {}", name);
(*impl::s_inlineVisualizers)[name] = impl::Visualizer { parameterCount, function };
(*impl::s_inlineVisualizers)[name] = impl::Visualizer { .parameterCount=parameterCount, .callback=function };
}
}
@@ -848,7 +854,7 @@ namespace hex {
void add(const UnlocalizedString &unlocalizedName, const char *icon, const impl::Callback &function) {
log::debug("Registered new tool: {}", unlocalizedName.get());
impl::s_tools->emplace_back(impl::Entry { unlocalizedName, icon, function });
impl::s_tools->emplace_back(impl::Entry { .unlocalizedName=unlocalizedName, .icon=icon, .function=function });
}
}
@@ -864,6 +870,18 @@ namespace hex {
}
namespace EditWidget {
std::optional<std::vector<u8>> TextInput::draw(std::string &value, std::endian endian) {
if (ImGui::InputText("##InspectorLineEditing", value,
ImGuiInputTextFlags_EnterReturnsTrue |
ImGuiInputTextFlags_AutoSelectAll)) {
return getBytes(value, endian);
}
return std::nullopt;
}
}
void add(const UnlocalizedString &unlocalizedName, size_t requiredSize, impl::GeneratorFunction displayGeneratorFunction, std::optional<impl::EditingFunction> editingFunction) {
log::debug("Registered new data inspector format: {}", unlocalizedName.get());
@@ -991,7 +1009,7 @@ namespace hex {
coloredIcon.color = ImGuiCustomCol_ToolbarGray;
impl::s_menuItems->insert({
priority, impl::MenuItem { unlocalizedMainMenuNames, coloredIcon, shortcut, view, function, enabledCallback, selectedCallback, -1 }
priority, impl::MenuItem { .unlocalizedNames=unlocalizedMainMenuNames, .icon=coloredIcon, .shortcut=shortcut, .view=view, .callback=function, .enabledCallback=enabledCallback, .selectedCallback=selectedCallback, .toolbarIndex=-1 }
});
if (shortcut != Shortcut::None) {
@@ -1015,14 +1033,23 @@ namespace hex {
unlocalizedMainMenuNames.emplace_back(impl::SubMenuValue);
impl::s_menuItems->insert({
priority, impl::MenuItem { unlocalizedMainMenuNames, icon, showOnWelcomeScreen ? Shortcut({ ShowOnWelcomeScreen }) : Shortcut::None, view, function, enabledCallback, []{ return false; }, -1 }
priority, impl::MenuItem { .unlocalizedNames=unlocalizedMainMenuNames, .icon=icon, .shortcut=showOnWelcomeScreen ? Shortcut({ ShowOnWelcomeScreen }) : Shortcut::None, .view=view, .callback=function, .enabledCallback=enabledCallback, .selectedCallback=[]{ return false; }, .toolbarIndex=-1 }
});
}
void addMenuItemSeparator(std::vector<UnlocalizedString> unlocalizedMainMenuNames, u32 priority, View *view) {
unlocalizedMainMenuNames.emplace_back(impl::SeparatorValue);
impl::s_menuItems->insert({
priority, impl::MenuItem { unlocalizedMainMenuNames, "", Shortcut::None, view, []{}, []{ return true; }, []{ return false; }, -1 }
priority, impl::MenuItem { .unlocalizedNames=unlocalizedMainMenuNames, .icon="", .shortcut=Shortcut::None, .view=view, .callback=[]{}, .enabledCallback=[]{ return true; }, .selectedCallback=[]{ return false; }, .toolbarIndex=-1 }
});
}
void addTaskBarMenuItem(std::vector<UnlocalizedString> unlocalizedMainMenuNames, u32 priority, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback) {
log::debug("Added new taskbar menu item to menu {} ", unlocalizedMainMenuNames[0].get());
unlocalizedMainMenuNames.insert(unlocalizedMainMenuNames.begin(), impl::TaskBarMenuValue);
impl::s_menuItems->insert({
priority, impl::MenuItem { .unlocalizedNames=unlocalizedMainMenuNames, .icon="", .shortcut=Shortcut::None, .view=nullptr, .callback=function, .enabledCallback=enabledCallback, .selectedCallback=[]{ return false; }, .toolbarIndex=-1 }
});
}
@@ -1038,13 +1065,13 @@ namespace hex {
impl::s_toolbarItems->push_back(function);
}
void addMenuItemToToolbar(const UnlocalizedString& unlocalizedName, ImGuiCustomCol color) {
void addMenuItemToToolbar(const std::vector<UnlocalizedString>& unlocalizedNames, ImGuiCustomCol color) {
const auto maxIndex = std::ranges::max_element(impl::getMenuItems(), [](const auto &a, const auto &b) {
return a.second.toolbarIndex < b.second.toolbarIndex;
})->second.toolbarIndex;
for (auto &[priority, menuItem] : *impl::s_menuItems) {
if (menuItem.unlocalizedNames.back() == unlocalizedName) {
if (menuItem.unlocalizedNames == unlocalizedNames) {
menuItem.toolbarIndex = maxIndex + 1;
menuItem.icon.color = color;
updateToolbarItems();
@@ -1096,18 +1123,18 @@ namespace hex {
}
namespace ContentRegistry::Provider {
namespace impl {
namespace ContentRegistry::Provider::impl {
void add(const std::string &typeName, ProviderCreationFunction creationFunction) {
(void)RequestCreateProvider::subscribe([expectedName = typeName, creationFunction](const std::string &name, bool skipLoadInterface, bool selectProvider, prv::Provider **provider) {
(void)RequestCreateProvider::subscribe([expectedName = typeName, creationFunction](const std::string &name, bool skipLoadInterface, bool selectProvider, std::shared_ptr<prv::Provider> *provider) {
if (name != expectedName) return;
auto newProvider = creationFunction();
if (provider != nullptr) {
*provider = newProvider.get();
*provider = newProvider;
ImHexApi::Provider::add(std::move(newProvider), skipLoadInterface, selectProvider);
}
});
@@ -1127,7 +1154,7 @@ namespace hex {
}
}
namespace ContentRegistry::DataFormatter {
@@ -1283,47 +1310,42 @@ namespace hex {
}
namespace ContentRegistry::Diffing {
namespace impl {
namespace ContentRegistry::Diffing::impl {
static AutoReset<std::vector<std::unique_ptr<Algorithm>>> s_algorithms;
const std::vector<std::unique_ptr<Algorithm>>& getAlgorithms() {
return *s_algorithms;
}
void addAlgorithm(std::unique_ptr<Algorithm> &&hash) {
s_algorithms->emplace_back(std::move(hash));
}
static AutoReset<std::vector<std::unique_ptr<Algorithm>>> s_algorithms;
const std::vector<std::unique_ptr<Algorithm>>& getAlgorithms() {
return *s_algorithms;
}
void addAlgorithm(std::unique_ptr<Algorithm> &&hash) {
s_algorithms->emplace_back(std::move(hash));
}
}
namespace ContentRegistry::Hashes {
namespace impl {
namespace ContentRegistry::Hashes::impl {
static AutoReset<std::vector<std::unique_ptr<Hash>>> s_hashes;
const std::vector<std::unique_ptr<Hash>>& getHashes() {
return *s_hashes;
}
void add(std::unique_ptr<Hash> &&hash) {
s_hashes->emplace_back(std::move(hash));
}
static AutoReset<std::vector<std::unique_ptr<Hash>>> s_hashes;
const std::vector<std::unique_ptr<Hash>>& getHashes() {
return *s_hashes;
}
void add(std::unique_ptr<Hash> &&hash) {
s_hashes->emplace_back(std::move(hash));
}
}
namespace ContentRegistry::BackgroundServices {
namespace impl {
class Service {
public:
Service(const UnlocalizedString &unlocalizedName, std::jthread thread) : m_unlocalizedName(std::move(unlocalizedName)), m_thread(std::move(thread)) { }
Service(UnlocalizedString unlocalizedName, std::jthread thread) : m_unlocalizedName(std::move(unlocalizedName)), m_thread(std::move(thread)) { }
Service(const Service&) = delete;
Service(Service &&) = default;
~Service() {
@@ -1395,6 +1417,40 @@ namespace hex {
}
namespace ContentRegistry::MCP {
namespace impl {
std::unique_ptr<mcp::Server>& getMcpServerInstance() {
static std::unique_ptr<mcp::Server> server;
if (server == nullptr)
server = std::make_unique<mcp::Server>();
return server;
}
static bool s_mcpEnabled = false;
void setEnabled(bool enabled) {
s_mcpEnabled = enabled;
}
}
bool isEnabled() {
return impl::s_mcpEnabled;
}
bool isConnected() {
return impl::getMcpServerInstance()->isConnected();
}
void registerTool(std::string_view capabilities, std::function<nlohmann::json(const nlohmann::json &params)> function) {
impl::getMcpServerInstance()->addPrimitive("tools", capabilities, function);
}
}
namespace ContentRegistry::Experiments {
namespace impl {
@@ -1488,22 +1544,19 @@ namespace hex {
}
}
namespace ContentRegistry::Disassemblers {
namespace ContentRegistry::Disassemblers::impl {
namespace impl {
static AutoReset<std::map<std::string, impl::CreatorFunction>> s_architectures;
static AutoReset<std::map<std::string, impl::CreatorFunction>> s_architectures;
void addArchitectureCreator(impl::CreatorFunction function) {
const auto arch = function();
(*s_architectures)[arch->getName()] = std::move(function);
}
const std::map<std::string, impl::CreatorFunction>& getArchitectures() {
return *s_architectures;
}
void addArchitectureCreator(impl::CreatorFunction function) {
const auto arch = function();
(*s_architectures)[arch->getName()] = std::move(function);
}
const std::map<std::string, impl::CreatorFunction>& getArchitectures() {
return *s_architectures;
}
}

View File

@@ -1,3 +1,4 @@
#include <algorithm>
#include <hex/api/event_manager.hpp>
namespace hex {
@@ -35,7 +36,7 @@ namespace hex {
void EventManager::unsubscribe(void *token, impl::EventId id) {
auto &tokenStore = getTokenStore();
auto iter = std::find_if(tokenStore.begin(), tokenStore.end(), [&](auto &item) {
auto iter = std::ranges::find_if(tokenStore, [&](auto &item) {
return item.first == token && item.second->first == id;
});

View File

@@ -47,6 +47,10 @@
#if defined(OS_WEB)
#include <emscripten.h>
#elif defined(OS_MACOS)
extern "C" {
void macosRegisterFont(const unsigned char *data, size_t size);
}
#endif
namespace hex {
@@ -257,7 +261,7 @@ namespace hex {
}
void setSelection(u64 address, size_t size, prv::Provider *provider) {
setSelection({ { address, size }, provider == nullptr ? Provider::get() : provider });
setSelection({ { .address=address, .size=size }, provider == nullptr ? Provider::get() : provider });
}
void addVirtualFile(const std::string &path, std::vector<u8> data, Region region) {
@@ -281,7 +285,7 @@ namespace hex {
}
u64 add(u64 address, size_t size, const std::string &name, const std::string &comment, u32 color) {
return add(Region { address, size }, name, comment, color);
return add(Region { .address=address, .size=size }, name, comment, color);
}
void remove(u64 id) {
@@ -294,8 +298,8 @@ namespace hex {
namespace ImHexApi::Provider {
static i64 s_currentProvider = -1;
static AutoReset<std::vector<std::unique_ptr<prv::Provider>>> s_providers;
static AutoReset<std::map<prv::Provider*, std::unique_ptr<prv::Provider>>> s_providersToRemove;
static AutoReset<std::vector<std::shared_ptr<prv::Provider>>> s_providers;
static AutoReset<std::map<prv::Provider*, std::shared_ptr<prv::Provider>>> s_providersToRemove;
namespace impl {
@@ -382,7 +386,7 @@ namespace hex {
});
}
void add(std::unique_ptr<prv::Provider> &&provider, bool skipLoadInterface, bool select) {
void add(std::shared_ptr<prv::Provider> &&provider, bool skipLoadInterface, bool select) {
std::scoped_lock lock(impl::s_providerMutex);
if (TaskManager::getRunningTaskCount() > 0)
@@ -391,7 +395,7 @@ namespace hex {
if (skipLoadInterface)
provider->skipLoadInterface();
EventProviderCreated::post(provider.get());
EventProviderCreated::post(provider);
s_providers->emplace_back(std::move(provider));
if (select || s_providers->size() == 1)
@@ -491,13 +495,17 @@ namespace hex {
});
}
prv::Provider* createProvider(const UnlocalizedString &unlocalizedName, bool skipLoadInterface, bool select) {
prv::Provider* result = nullptr;
std::shared_ptr<prv::Provider> createProvider(const UnlocalizedString &unlocalizedName, bool skipLoadInterface, bool select) {
std::shared_ptr<prv::Provider> result = nullptr;
RequestCreateProvider::post(unlocalizedName, skipLoadInterface, select, &result);
return result;
}
void openProvider(std::shared_ptr<prv::Provider> provider) {
RequestOpenProvider::post(provider);
}
}
namespace ImHexApi::System {
@@ -530,6 +538,11 @@ namespace hex {
s_mainWindowHandle = window;
}
static bool s_mainWindowFocused = false;
void setMainWindowFocusState(bool focused) {
s_mainWindowFocused = focused;
}
static float s_globalScale = 1.0;
void setGlobalScale(float scale) {
@@ -673,7 +686,7 @@ namespace hex {
return std::midpoint(xScale, yScale);
}
#elif defined(OS_WEB)
return 1.0F;
return MAIN_THREAD_EM_ASM_INT({ return window.devicePixelRatio; });
#else
return 1.0F;
#endif
@@ -700,6 +713,10 @@ namespace hex {
return impl::s_mainWindowHandle;
}
bool isMainWindowFocused() {
return impl::s_mainWindowFocused;
}
bool isBorderlessWindowModeEnabled() {
return impl::s_borderlessWindowMode;
}
@@ -896,7 +913,7 @@ namespace hex {
}
}
return { { name, version } };
return { { .name=name, .version=version } };
}
const SemanticVersion& getImHexVersion() {
@@ -1201,6 +1218,10 @@ namespace hex {
offset,
fontSizeMultiplier
);
#if defined(OS_MACOS)
macosRegisterFont(data.data(), data.size_bytes());
#endif
}
void registerFont(const Font& font) {
@@ -1212,7 +1233,7 @@ namespace hex {
if (it == impl::s_fontDefinitions->end()) {
const auto defaultFont = ImGui::GetDefaultFont();
return { defaultFont, defaultFont, defaultFont };
return { .regular=defaultFont, .bold=defaultFont, .italic=defaultFont };
} else
return it->second;
}

View File

@@ -146,7 +146,7 @@ namespace hex {
}
void LayoutManager::lockLayout(bool locked) {
log::info("Layout {}", locked ? "locked" : "unlocked");
log::debug("Layout {}", locked ? "locked" : "unlocked");
s_layoutLocked = locked;
}

View File

@@ -50,13 +50,13 @@ namespace hex {
definition.fallbackLanguageId = item["fallback"].get<std::string>();
}
if (item.contains("hidden") && item["hidden"].get<bool>() == true) {
if (item.contains("hidden") && item["hidden"].get<bool>()) {
definition.hidden = true;
}
const auto path = item["path"].get<std::string>();
definition.languageFilePaths.emplace_back(PathEntry{ path, callback });
definition.languageFilePaths.emplace_back(PathEntry{ .path=path, .callback=callback });
}
}
@@ -104,9 +104,21 @@ namespace hex {
for (const auto &entry : json.items()) {
auto value = entry.value().get<std::string>();
// Skip empty values
if (value.empty())
continue;
// Handle references to files
if (value.starts_with("#@")) {
try {
value = path.callback(value.substr(2));
} catch (std::exception &e) {
log::error("Failed to load localization file reference '{}': {}", entry.key(), e.what());
continue;
}
}
localizations.try_emplace(LangConst::hash(entry.key()), std::move(value));
}
} catch (std::exception &e) {
@@ -143,7 +155,7 @@ namespace hex {
static AutoReset<std::unordered_map<std::size_t, std::string>> loadedLocalization;
static std::mutex mutex;
std::lock_guard lock(mutex);
std::scoped_lock lock(mutex);
if (*currentLanguageId != languageId) {
currentLanguageId = languageId;
loadedLocalization->clear();

View File

@@ -85,7 +85,7 @@ namespace hex {
}
Plugin::Plugin(const std::string &name, const hex::PluginFunctions &functions) :
m_handle(0), m_path(name), m_addedManually(true), m_functions(functions) { }
m_path(name), m_addedManually(true), m_functions(functions) { }
Plugin::Plugin(Plugin &&other) noexcept {

View File

@@ -44,7 +44,7 @@ namespace hex {
}
std::fs::path ProjectFile::getPath() {
return s_currProjectPath;
return *s_currProjectPath;
}
void ProjectFile::setPath(const std::fs::path &path) {

View File

@@ -281,25 +281,25 @@ namespace hex {
void ShortcutManager::addGlobalShortcut(const Shortcut &shortcut, const std::vector<UnlocalizedString> &unlocalizedName, const std::function<void()> &callback, const EnabledCallback &enabledCallback) {
log::debug("Adding global shortcut {} for {}", shortcut.toString(), unlocalizedName.back().get());
auto [it, inserted] = s_globalShortcuts->insert({ shortcut, { shortcut, unlocalizedName, callback, enabledCallback } });
auto [it, inserted] = s_globalShortcuts->insert({ shortcut, { .shortcut=shortcut, .unlocalizedName=unlocalizedName, .callback=callback, .enabledCallback=enabledCallback } });
if (!inserted) log::error("Failed to add shortcut!");
}
void ShortcutManager::addGlobalShortcut(const Shortcut &shortcut, const UnlocalizedString &unlocalizedName, const std::function<void()> &callback, const EnabledCallback &enabledCallback) {
log::debug("Adding global shortcut {} for {}", shortcut.toString(), unlocalizedName.get());
auto [it, inserted] = s_globalShortcuts->insert({ shortcut, { shortcut, { unlocalizedName }, callback, enabledCallback } });
auto [it, inserted] = s_globalShortcuts->insert({ shortcut, { .shortcut=shortcut, .unlocalizedName={ unlocalizedName }, .callback=callback, .enabledCallback=enabledCallback } });
if (!inserted) log::error("Failed to add shortcut!");
}
void ShortcutManager::addShortcut(View *view, const Shortcut &shortcut, const std::vector<UnlocalizedString> &unlocalizedName, const std::function<void()> &callback, const EnabledCallback &enabledCallback) {
log::debug("Adding shortcut {} for {}", shortcut.toString(), unlocalizedName.back().get());
auto [it, inserted] = view->m_shortcuts.insert({ shortcut + CurrentView, { shortcut, unlocalizedName, callback, enabledCallback } });
auto [it, inserted] = view->m_shortcuts.insert({ shortcut + CurrentView, { .shortcut=shortcut, .unlocalizedName=unlocalizedName, .callback=callback, .enabledCallback=enabledCallback } });
if (!inserted) log::error("Failed to add shortcut!");
}
void ShortcutManager::addShortcut(View *view, const Shortcut &shortcut, const UnlocalizedString &unlocalizedName, const std::function<void()> &callback, const EnabledCallback &enabledCallback) {
log::debug("Adding shortcut {} for {}", shortcut.toString(), unlocalizedName.get());
auto [it, inserted] = view->m_shortcuts.insert({ shortcut + CurrentView, { shortcut, { unlocalizedName }, callback, enabledCallback } });
auto [it, inserted] = view->m_shortcuts.insert({ shortcut + CurrentView, { .shortcut=shortcut, .unlocalizedName={ unlocalizedName }, .callback=callback, .enabledCallback=enabledCallback } });
if (!inserted) log::error("Failed to add shortcut!");
}
@@ -316,27 +316,40 @@ namespace hex {
pressedShortcut += s_macOSMode ? CTRLCMD : SUPER;
if (focused)
pressedShortcut += CurrentView;
if (!ImHexApi::Provider::isValid()) {
pressedShortcut += CurrentView;
pressedShortcut += ShowOnWelcomeScreen;
}
pressedShortcut += scanCodeToKey(keyCode);
return pressedShortcut;
}
static bool processShortcut(Shortcut shortcut, const std::map<Shortcut, ShortcutManager::ShortcutEntry> &shortcuts) {
static auto findShortcut(const Shortcut &shortcut, const std::map<Shortcut, ShortcutManager::ShortcutEntry> &shortcuts, bool condition, auto ... extraOption) {
auto modifiedShortcut = shortcut;
((modifiedShortcut += extraOption), ...);
if (condition) {
return shortcuts.find(modifiedShortcut);
} else {
auto it = shortcuts.find(shortcut);
if (it == shortcuts.end())
return shortcuts.find(modifiedShortcut);
}
return shortcuts.end();
}
static bool processShortcut(const Shortcut &shortcut, const std::map<Shortcut, ShortcutManager::ShortcutEntry> &shortcuts) {
if (s_paused)
return true;
if (ImGui::IsPopupOpen(ImGuiID(0), ImGuiPopupFlags_AnyPopupId))
return true;
const bool currentlyTyping = ImGui::GetIO().WantTextInput;
auto it = shortcuts.find(shortcut + AllowWhileTyping);
if (!currentlyTyping && it == shortcuts.end()) {
if (it == shortcuts.end())
it = shortcuts.find(shortcut);
}
auto it = findShortcut(shortcut, shortcuts, ImGui::GetIO().WantTextInput, AllowWhileTyping);
if (it == shortcuts.end())
it = findShortcut(shortcut, shortcuts, ImGui::GetIO().WantTextInput, AllowWhileTyping, ShowOnWelcomeScreen);
if (it != shortcuts.end()) {
const auto &[foundShortcut, entry] = *it;

View File

@@ -7,6 +7,9 @@
#include <ranges>
#include <jthread.hpp>
#include <hex/helpers/debugging.hpp>
#include <hex/trace/exceptions.hpp>
#include <utility>
#if defined(OS_WINDOWS)
#include <windows.h>
@@ -52,6 +55,7 @@ namespace hex {
std::list<std::function<void()>> s_deferredCalls;
std::unordered_map<SourceLocationWrapper, std::function<void()>> s_onceDeferredCalls;
std::list<std::function<void()>> s_tasksFinishedCallbacks;
std::list<std::function<void(Task&)>> s_taskCompletionCallbacks;
std::mutex s_queueMutex;
std::condition_variable s_jobCondVar;
@@ -64,8 +68,8 @@ namespace hex {
}
Task::Task(const UnlocalizedString &unlocalizedName, u64 maxValue, bool background, bool blocking, std::function<void(Task &)> function)
: m_unlocalizedName(unlocalizedName),
Task::Task(UnlocalizedString unlocalizedName, u64 maxValue, bool background, bool blocking, std::function<void(Task &)> function)
: m_unlocalizedName(std::move(unlocalizedName)),
m_maxValue(maxValue),
m_function(std::move(function)),
m_background(background), m_blocking(blocking) { }
@@ -310,6 +314,8 @@ namespace hex {
}
try {
trace::enableExceptionCaptureForCurrentThread();
// Set the thread name to the name of the task
TaskManager::setCurrentThreadName(Lang(task->m_unlocalizedName));
@@ -317,21 +323,34 @@ namespace hex {
task->m_function(*task);
log::debug("Task '{}' finished", task->m_unlocalizedName.get());
{
std::scoped_lock lock(s_tasksFinishedMutex);
for (const auto &callback : s_taskCompletionCallbacks)
callback(*task);
}
} catch (const Task::TaskInterruptor &) {
// Handle the task being interrupted by user request
task->interruption();
} catch (const std::exception &e) {
log::error("Exception in task '{}': {}", task->m_unlocalizedName.get(), e.what());
dbg::printStackTrace(trace::getStackTrace());
// Handle the task throwing an uncaught exception
task->exception(e.what());
} catch (...) {
log::error("Exception in task '{}'", task->m_unlocalizedName.get());
dbg::printStackTrace(trace::getStackTrace());
// Handle the task throwing an uncaught exception of unknown type
task->exception("Unknown Exception");
}
trace::disableExceptionCaptureForCurrentThread();
s_currentTask = nullptr;
task->finish();
}
@@ -350,7 +369,10 @@ namespace hex {
thread.request_stop();
// Wake up all the idle worker threads so they can exit
s_jobCondVar.notify_all();
{
std::unique_lock lock(s_queueMutex);
s_jobCondVar.notify_all();
}
// Wait for all worker threads to exit
s_workers.clear();
@@ -361,6 +383,7 @@ namespace hex {
s_deferredCalls.clear();
s_onceDeferredCalls.clear();
s_tasksFinishedCallbacks.clear();
s_taskCompletionCallbacks.clear();
}
TaskHolder TaskManager::createTask(const UnlocalizedString &unlocalizedName, u64 maxValue, bool background, bool blocking, std::function<void(Task&)> function) {
@@ -569,5 +592,13 @@ namespace hex {
return s_mainThreadId == std::this_thread::get_id();
}
void TaskManager::addTaskCompletionCallback(const std::function<void(Task &)> &function) {
std::scoped_lock lock(s_tasksFinishedMutex);
for (const auto &task : s_tasks) {
task->interrupt();
}
s_taskCompletionCallbacks.push_back(function);
}
}

View File

@@ -31,7 +31,7 @@ namespace hex {
void ThemeManager::addThemeHandler(const std::string &name, const ColorMap &colorMap, const std::function<ImColor(u32)> &getFunction, const std::function<void(u32, ImColor)> &setFunction) {
std::unique_lock lock(s_themeMutex);
(*s_themeHandlers)[name] = { colorMap, getFunction, setFunction };
(*s_themeHandlers)[name] = { .colorMap=colorMap, .getFunction=getFunction, .setFunction=setFunction };
}
void ThemeManager::addStyleHandler(const std::string &name, const StyleMap &styleMap) {

View File

@@ -32,6 +32,8 @@ namespace hex {
ImGuiID s_activeHelpId;
bool s_helpHoverActive = false;
AutoReset<std::function<std::function<void()>(const std::string &)>> s_renderer;
class IDStack {
public:
@@ -55,7 +57,7 @@ namespace hex {
void add(const void *pointer) {
const ImGuiID seed = idStack.back();
const ImGuiID id = ImHashData(&pointer, sizeof(pointer), seed);
const ImGuiID id = ImHashData((const void*) &pointer, sizeof(pointer), seed);
idStack.push_back(id);
}
@@ -94,38 +96,47 @@ namespace hex {
}
void TutorialManager::init() {
EventImGuiElementRendered::subscribe([](ImGuiID id, const std::array<float, 4> bb){
const auto boundingBox = ImRect(bb[0], bb[1], bb[2], bb[3]);
if (*s_renderer == nullptr) {
*s_renderer = [](const std::string &message) {
return [message] {
ImGui::PushTextWrapPos(300_scaled);
ImGui::TextUnformatted(message.c_str());
ImGui::PopTextWrapPos();
ImGui::NewLine();
};
};
}
}
if (!ImGui::IsRectVisible(boundingBox.Min, boundingBox.Max))
return;
void TutorialManager::postElementRendered(ImGuiID id, const ImRect &boundingBox) {
if (!ImGui::IsRectVisible(boundingBox.Min, boundingBox.Max))
return;
{
const auto element = hex::s_highlights->find(id);
if (element != hex::s_highlights->end()) {
hex::s_highlightDisplays->emplace_back(boundingBox, element->second);
{
const auto element = hex::s_highlights->find(id);
if (element != hex::s_highlights->end()) {
hex::s_highlightDisplays->emplace_back(boundingBox, element->second);
const auto window = ImGui::GetCurrentWindow();
if (window != nullptr && window->DockNode != nullptr && window->DockNode->TabBar != nullptr)
window->DockNode->TabBar->NextSelectedTabId = window->TabId;
}
const auto window = ImGui::GetCurrentWindow();
if (window != nullptr && window->DockNode != nullptr && window->DockNode->TabBar != nullptr)
window->DockNode->TabBar->NextSelectedTabId = window->TabId;
}
}
{
const auto element = s_interactiveHelpItems->find(id);
if (element != s_interactiveHelpItems->end()) {
(*s_interactiveHelpDisplays)[id] = boundingBox;
}
{
const auto element = s_interactiveHelpItems->find(id);
if (element != s_interactiveHelpItems->end()) {
(*s_interactiveHelpDisplays)[id] = boundingBox;
}
}
if (id != 0 && boundingBox.Contains(ImGui::GetMousePos())) {
if ((s_hoveredRect.GetArea() == 0 || boundingBox.GetArea() < s_hoveredRect.GetArea()) && s_interactiveHelpItems->contains(id)) {
s_hoveredRect = boundingBox;
s_hoveredId = id;
}
if (id != 0 && boundingBox.Contains(ImGui::GetMousePos())) {
if ((s_hoveredRect.GetArea() == 0 || boundingBox.GetArea() < s_hoveredRect.GetArea()) && s_interactiveHelpItems->contains(id)) {
s_hoveredRect = boundingBox;
s_hoveredId = id;
}
}
});
}
}
const std::map<std::string, TutorialManager::Tutorial>& TutorialManager::getTutorials() {
@@ -204,6 +215,10 @@ namespace hex {
s_currentTutorial->second.start();
}
void TutorialManager::stopCurrentTutorial() {
s_currentTutorial = s_tutorials->end();
}
void TutorialManager::drawHighlights() {
if (s_helpHoverActive) {
const auto &drawList = ImGui::GetForegroundDrawList(ImGui::GetMainViewport());
@@ -303,10 +318,10 @@ namespace hex {
if (!message.has_value()) {
message = Tutorial::Step::Message {
Position::None,
"",
"",
false
.position=Position::None,
.unlocalizedTitle="",
.unlocalizedMessage="",
.allowSkip=false
};
}
@@ -333,34 +348,39 @@ namespace hex {
ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot);
ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID);
if (ImGui::Begin("##TutorialMessage", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing)) {
ImGui::SetNextWindowSize(ImVec2(300_scaled, 0));
bool open = true;
if (ImGui::Begin(message->unlocalizedTitle.empty() ? "##TutorialMessage" : Lang(message->unlocalizedTitle), &open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing)) {
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindowRead());
if (!message->unlocalizedTitle.empty())
ImGuiExt::Header(Lang(message->unlocalizedTitle), true);
auto &step = s_currentTutorial->second.m_currentStep;
if (!message->unlocalizedMessage.empty()) {
ImGui::PushTextWrapPos(300_scaled);
ImGui::TextUnformatted(Lang(message->unlocalizedMessage));
ImGui::PopTextWrapPos();
step->m_drawFunction();
ImGui::NewLine();
ImGui::NewLine();
}
ImGui::BeginDisabled(s_currentTutorial->second.m_currentStep == s_currentTutorial->second.m_steps.begin());
if (ImGui::ArrowButton("Backwards", ImGuiDir_Left)) {
ImGui::BeginDisabled(step == s_currentTutorial->second.m_steps.begin());
if (ImGuiExt::DimmedArrowButton("Backwards", ImGuiDir_Left)) {
s_currentTutorial->second.m_currentStep->advance(-1);
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(!message->allowSkip && s_currentTutorial->second.m_currentStep == s_currentTutorial->second.m_latestStep);
if (ImGui::ArrowButton("Forwards", ImGuiDir_Right)) {
s_currentTutorial->second.m_currentStep->advance(1);
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - ImGui::GetFrameHeight() - ImGui::GetStyle().WindowPadding.x);
ImGui::BeginDisabled(!message->allowSkip && step == s_currentTutorial->second.m_latestStep);
if (ImGuiExt::DimmedArrowButton("Forwards", ImGuiDir_Right)) {
step->advance(1);
}
ImGui::EndDisabled();
}
ImGui::End();
if (!open) {
stopCurrentTutorial();
}
}
void TutorialManager::drawTutorial() {
@@ -387,6 +407,10 @@ namespace hex {
s_highlightDisplays->clear();
}
void TutorialManager::setRenderer(std::function<DrawFunction(const std::string &)> renderer) {
s_renderer = std::move(renderer);
}
TutorialManager::Tutorial::Step& TutorialManager::Tutorial::addStep() {
auto &newStep = m_steps.emplace_back(this);
m_currentStep = m_steps.end();
@@ -402,6 +426,9 @@ namespace hex {
return;
m_currentStep->addHighlights();
if (m_currentStep->m_message.has_value())
m_currentStep->m_drawFunction = (*s_renderer)(Lang(m_currentStep->m_message->unlocalizedMessage));
}
void TutorialManager::Tutorial::Step::addHighlights() const {
@@ -426,8 +453,12 @@ namespace hex {
std::advance(m_parent->m_latestStep, steps);
std::advance(m_parent->m_currentStep, steps);
if (m_parent->m_currentStep != m_parent->m_steps.end())
if (m_parent->m_currentStep != m_parent->m_steps.end()) {
m_parent->m_currentStep->addHighlights();
if (m_message.has_value())
m_parent->m_currentStep->m_drawFunction = (*s_renderer)(Lang(m_parent->m_currentStep->m_message->unlocalizedMessage));
}
else
s_currentTutorial = s_tutorials->end();
}
@@ -450,10 +481,10 @@ namespace hex {
TutorialManager::Tutorial::Step& TutorialManager::Tutorial::Step::setMessage(const UnlocalizedString &unlocalizedTitle, const UnlocalizedString &unlocalizedMessage, Position position) {
m_message = Message {
position,
unlocalizedTitle,
unlocalizedMessage,
false
.position=position,
.unlocalizedTitle=unlocalizedTitle,
.unlocalizedMessage=unlocalizedMessage,
.allowSkip=false
};
return *this;
@@ -464,10 +495,10 @@ namespace hex {
m_message->allowSkip = true;
} else {
m_message = Message {
Position::Bottom | Position::Right,
"",
"",
true
.position=Position::Bottom | Position::Right,
.unlocalizedTitle="",
.unlocalizedMessage="",
.allowSkip=true
};
}

View File

@@ -135,7 +135,7 @@ namespace hex {
void WorkspaceManager::process() {
if (s_previousWorkspace != s_currentWorkspace) {
log::info("Updating workspace");
log::debug("Updating workspace");
if (s_previousWorkspace != s_workspaces->end()) {
auto newWorkspace = s_currentWorkspace;
s_currentWorkspace = s_previousWorkspace;

View File

@@ -151,8 +151,8 @@ namespace hex::dp {
m_overlay->getData() = data;
}
[[noreturn]] void Node::throwNodeError(const std::string &message) {
throw NodeError { this, message };
[[noreturn]] void Node::throwNodeError(const std::string &msg) {
throw NodeError(this, msg);
}
void Node::setAttributes(std::vector<Attribute> attributes) {

View File

@@ -7,7 +7,7 @@ namespace hex {
namespace {
void skipWhitespace(std::string_view &string) {
while (string.length() > 0) {
while (!string.empty()) {
if (!std::isspace(string.front()))
break;
string = string.substr(1);
@@ -89,15 +89,15 @@ namespace hex {
return { };
bool inString = false;
while (string.length() > 0) {
BinaryPattern::Pattern pattern = { 0, 0 };
while (!string.empty()) {
BinaryPattern::Pattern pattern = { .mask=0, .value=0 };
if (string.starts_with("\"")) {
inString = !inString;
string = string.substr(1);
continue;
} else if (inString) {
pattern = { 0xFF, u8(string.front()) };
pattern = { .mask=0xFF, .value=u8(string.front()) };
string = string.substr(1);
} else if (string.starts_with("u") || string.starts_with("s")) {
auto newPatterns = parseValueExpression(string);
@@ -106,7 +106,7 @@ namespace hex {
std::ranges::move(newPatterns, std::back_inserter(result));
continue;
} else if (string.starts_with("??")) {
pattern = { 0x00, 0x00 };
pattern = { .mask=0x00, .value=0x00 };
string = string.substr(2);
} else if ((std::isxdigit(string.front()) || string.front() == '?') && string.length() >= 2) {
const auto hex = string.substr(0, 2);

View File

@@ -28,7 +28,6 @@
#endif
#include <array>
#include <functional>
#include <cstddef>
#include <cstdint>
#include <bit>
@@ -82,7 +81,7 @@ namespace hex::crypt {
public:
constexpr Crc(u64 polynomial, u64 init, u64 xorOut, bool reflectInput, bool reflectOutput)
: m_value(0x00), m_init(init & ((0b10ull << (NumBits - 1)) - 1)), m_xorOut(xorOut & ((0b10ull << (NumBits - 1)) - 1)),
: m_init(init & ((0b10ull << (NumBits - 1)) - 1)), m_xorOut(xorOut & ((0b10ull << (NumBits - 1)) - 1)),
m_reflectInput(reflectInput), m_reflectOutput(reflectOutput),
m_table([polynomial] {
auto reflectedPoly = reflect(polynomial & ((0b10ull << (NumBits - 1)) - 1), NumBits);
@@ -129,7 +128,7 @@ namespace hex::crypt {
}
private:
u64 m_value;
u64 m_value = 0x00;
u64 m_init;
u64 m_xorOut;
@@ -144,7 +143,7 @@ namespace hex::crypt {
using Crc = Crc<NumBits>;
Crc crc(polynomial, init, xorout, reflectIn, reflectOut);
processDataByChunks(data, offset, size, std::bind(&Crc::processBytes, &crc, _1, _2));
processDataByChunks(data, offset, size, [&crc](auto && data, auto && size) { crc.processBytes(data, size); });
return crc.checksum();
}
@@ -170,7 +169,7 @@ namespace hex::crypt {
mbedtls_md5_starts(&ctx);
processDataByChunks(data, offset, size, std::bind(mbedtls_md5_update, &ctx, _1, _2));
processDataByChunks(data, offset, size, [&ctx](auto && data, auto && size) { return mbedtls_md5_update(&ctx, data, size); });
mbedtls_md5_finish(&ctx, result.data());
@@ -202,7 +201,7 @@ namespace hex::crypt {
mbedtls_sha1_starts(&ctx);
processDataByChunks(data, offset, size, std::bind(mbedtls_sha1_update, &ctx, _1, _2));
processDataByChunks(data, offset, size, [&ctx](auto && data, auto && size) { return mbedtls_sha1_update(&ctx, data, size); });
mbedtls_sha1_finish(&ctx, result.data());
@@ -234,7 +233,7 @@ namespace hex::crypt {
mbedtls_sha256_starts(&ctx, true);
processDataByChunks(data, offset, size, std::bind(mbedtls_sha256_update, &ctx, _1, _2));
processDataByChunks(data, offset, size, [&ctx](auto && data, auto && size) { return mbedtls_sha256_update(&ctx, data, size); });
mbedtls_sha256_finish(&ctx, result.data());
@@ -266,7 +265,7 @@ namespace hex::crypt {
mbedtls_sha256_starts(&ctx, false);
processDataByChunks(data, offset, size, std::bind(mbedtls_sha256_update, &ctx, _1, _2));
processDataByChunks(data, offset, size, [&ctx](auto && data, auto && size) { return mbedtls_sha256_update(&ctx, data, size); });
mbedtls_sha256_finish(&ctx, result.data());
@@ -298,7 +297,7 @@ namespace hex::crypt {
mbedtls_sha512_starts(&ctx, true);
processDataByChunks(data, offset, size, std::bind(mbedtls_sha512_update, &ctx, _1, _2));
processDataByChunks(data, offset, size, [&ctx](auto && data, auto && size) { return mbedtls_sha512_update(&ctx, data, size); });
mbedtls_sha512_finish(&ctx, result.data());
@@ -330,7 +329,7 @@ namespace hex::crypt {
mbedtls_sha512_starts(&ctx, false);
processDataByChunks(data, offset, size, std::bind(mbedtls_sha512_update, &ctx, _1, _2));
processDataByChunks(data, offset, size, [&ctx](auto && data, auto && size) { return mbedtls_sha512_update(&ctx, data, size); });
mbedtls_sha512_finish(&ctx, result.data());

View File

@@ -1,4 +1,6 @@
#include <hex/helpers/debugging.hpp>
#include <hex/helpers/logger.hpp>
#include <hex/trace/stacktrace.hpp>
namespace hex::dbg {
@@ -21,4 +23,23 @@ namespace hex::dbg {
s_debugMode = enabled;
}
[[noreturn]] void assertionHandler(const char* file, int line, const char *function, const char* exprString) {
log::error("Assertion failed: {} at {}:{} => {}", exprString, file, line, function);
const auto stackTrace = trace::getStackTrace();
dbg::printStackTrace(stackTrace);
std::abort();
}
void printStackTrace(const trace::StackTraceResult &stackTrace) {
log::fatal("Printing stacktrace using implementation '{}'", stackTrace.implementationName);
for (const auto &stackFrame : stackTrace.stackFrames) {
if (stackFrame.line == 0)
log::fatal(" ({}) | {}", stackFrame.file, stackFrame.function);
else
log::fatal(" ({}:{}) | {}", stackFrame.file, stackFrame.line, stackFrame.function);
}
}
}

View File

@@ -1,10 +1,10 @@
#include <algorithm>
#include <hex/helpers/default_paths.hpp>
#include <hex/api/imhex_api/system.hpp>
#include <hex/api/project_file_manager.hpp>
#include <ranges>
#include <algorithm>
#if defined(OS_WINDOWS)
#include <windows.h>
@@ -39,7 +39,7 @@ namespace hex::paths {
paths.push_back(xdg::DataHomeDir());
auto dataDirs = xdg::DataDirs();
std::copy(dataDirs.begin(), dataDirs.end(), std::back_inserter(paths));
std::ranges::copy(dataDirs, std::back_inserter(paths));
#endif
@@ -97,11 +97,18 @@ namespace hex::paths {
}
static std::vector<std::fs::path> getPluginPaths() {
// If running from an AppImage, only allow loaded plugins from inside it
#if defined(OS_LINUX)
if(const char* appdir = std::getenv("APPDIR")) { // check for AppImage environment
return {std::string(appdir) + "/usr/lib/imhex"};
}
#endif
std::vector<std::fs::path> paths = getDataPaths(true);
// Add the system plugin directory to the path if one was provided at compile time
#if defined(OS_LINUX) && defined(SYSTEM_PLUGINS_LOCATION)
paths.push_back(SYSTEM_PLUGINS_LOCATION);
paths.emplace_back(SYSTEM_PLUGINS_LOCATION);
#endif
return paths;

View File

@@ -2,6 +2,7 @@
#include <hex/helpers/utils.hpp>
#include <ranges>
#include <wolv/io/file.hpp>
#include <wolv/utils/string.hpp>
@@ -65,6 +66,9 @@ namespace hex {
EncodingFile &EncodingFile::operator=(const hex::EncodingFile &other) {
if(this == &other) {
return *this;
}
m_mapping = std::make_unique<std::map<size_t, std::map<std::vector<u8>, std::string>>>(*other.m_mapping);
m_tableContent = other.m_tableContent;
m_longestSequence = other.m_longestSequence;
@@ -89,9 +93,7 @@ namespace hex {
std::pair<std::string_view, size_t> EncodingFile::getEncodingFor(std::span<const u8> buffer) const {
for (auto riter = m_mapping->crbegin(); riter != m_mapping->crend(); ++riter) {
const auto &[size, mapping] = *riter;
for (const auto &[size, mapping] : std::ranges::reverse_view(*m_mapping)) {
if (size > buffer.size()) continue;
std::vector key(buffer.begin(), buffer.begin() + size);
@@ -103,9 +105,7 @@ namespace hex {
}
u64 EncodingFile::getEncodingLengthFor(std::span<u8> buffer) const {
for (auto riter = m_mapping->crbegin(); riter != m_mapping->crend(); ++riter) {
const auto &[size, mapping] = *riter;
for (const auto& [size, mapping] : std::ranges::reverse_view(*m_mapping)) {
if (size > buffer.size()) continue;
std::vector key(buffer.begin(), buffer.begin() + size);

View File

@@ -13,8 +13,6 @@
#include <xdg.hpp>
# if defined(OS_FREEBSD)
#include <sys/syslimits.h>
# else
#include <limits.h>
# endif
#endif

View File

@@ -3,13 +3,12 @@
#include <imgui.h>
#include <imgui_internal.h>
#include <array>
#include <hex/api/tutorial_manager.hpp>
#if !defined(IMGUI_TEST_ENGINE)
void ImGuiTestEngineHook_ItemAdd(ImGuiContext*, ImGuiID id, const ImRect& bb, const ImGuiLastItemData*) {
std::array<float, 4> boundingBox = { bb.Min.x, bb.Min.y, bb.Max.x, bb.Max.y };
hex::EventImGuiElementRendered::post(id, boundingBox);
hex::TutorialManager::postElementRendered(id, bb);
}
void ImGuiTestEngineHook_ItemInfo(ImGuiContext*, ImGuiID, const char*, ImGuiItemStatusFlags) {}

View File

@@ -12,6 +12,7 @@
#include <mutex>
#include <chrono>
#include <fmt/chrono.h>
#include <hex/helpers/debugging.hpp>
#if defined(OS_WINDOWS)
#include <Windows.h>
@@ -117,8 +118,8 @@ namespace hex::log {
void addLogEntry(std::string_view project, std::string_view level, std::string message) {
s_logEntries->emplace_back(
std::move(project),
std::move(level),
project,
level,
std::move(message)
);
}
@@ -149,14 +150,6 @@ namespace hex::log {
);
}
void assertionHandler(const char* exprString, const char* file, int line) {
log::error("Assertion failed: {} at {}:{}", exprString, file, line);
#if defined (DEBUG)
std::abort();
#endif
}
namespace color {
fmt::color debug() { return fmt::color::medium_sea_green; }

View File

@@ -1,5 +1,6 @@
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import <objc/runtime.h>
struct KeyEquivalent {
bool valid;
@@ -61,7 +62,120 @@ void macosEndMainMenuBar(void) {
s_constructingMenu = false;
}
bool macosBeginMenu(const char* label, bool enabled) {
static NSMutableArray* g_RegisteredIconFontDescriptors = nil;
void macosRegisterFont(const unsigned char* fontBytes, size_t fontLength) {
if (!fontBytes || fontLength == 0 || fontLength > 100 * 1024 * 1024) { // Max 100MB sanity check
NSLog(@"Invalid font data: bytes=%p, length=%zu", fontBytes, fontLength);
return;
}
// Initialize array on first use
if (!g_RegisteredIconFontDescriptors) {
g_RegisteredIconFontDescriptors = [[NSMutableArray alloc] init];
}
// Create NSData - this will copy the bytes
NSData *fontData = [NSData dataWithBytes:fontBytes length:fontLength];
if (!fontData) {
NSLog(@"Failed to create NSData from font bytes");
return;
}
CFErrorRef error = NULL;
CFArrayRef descriptors = CTFontManagerCreateFontDescriptorsFromData((__bridge CFDataRef)fontData);
if (descriptors && CFArrayGetCount(descriptors) > 0) {
// Register all descriptors from this font file
CTFontManagerRegisterFontDescriptors(descriptors, kCTFontManagerScopeProcess, true, NULL);
if (true) {
// Store each descriptor for later use
for (CFIndex i = 0; i < CFArrayGetCount(descriptors); i++) {
CTFontDescriptorRef descriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descriptors, i);
[g_RegisteredIconFontDescriptors addObject:(__bridge id)descriptor];
// Log the font name for debugging
CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 16.0, NULL);
if (font) {
CFRelease(font);
}
}
} else {
NSLog(@"Failed to register font descriptors");
}
CFRelease(descriptors);
} else {
NSLog(@"Failed to create font descriptors from data (length: %zu)", fontLength);
if (error) {
NSLog(@"Error: %@", (__bridge NSError *)error);
CFRelease(error);
}
}
}
static NSImage* imageFromIconFont(NSString* character,
CGFloat size,
NSColor* color) {
if (!character || [character length] == 0) {
return nil;
}
if (!g_RegisteredIconFontDescriptors || [g_RegisteredIconFontDescriptors count] == 0) {
NSLog(@"No icon fonts registered");
return nil;
}
NSFont *font = nil;
unichar unicode = [character characterAtIndex:0];
for (id descriptorObj in g_RegisteredIconFontDescriptors) {
CTFontDescriptorRef descriptor = (__bridge CTFontDescriptorRef)descriptorObj;
CTFontRef ctFont = CTFontCreateWithFontDescriptor(descriptor, size, NULL);
if (ctFont) {
CGGlyph glyph;
UniChar unichar = unicode;
if (CTFontGetGlyphsForCharacters(ctFont, &unichar, &glyph, 1)) {
font = (__bridge NSFont *)ctFont;
break;
}
CFRelease(ctFont);
}
}
if (!font) {
NSLog(@"No font found with glyph for character U+%04X (string: '%@', length: %lu)",
unicode, character, (unsigned long)[character length]);
return nil;
}
NSDictionary *attributes = @{
NSFontAttributeName: font,
NSForegroundColorAttributeName: color
};
NSAttributedString *attrString = [[NSAttributedString alloc]
initWithString:character
attributes:attributes];
NSSize stringSize = [attrString size];
NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(size, size)];
[image lockFocus];
NSPoint drawPoint = NSMakePoint((size - stringSize.width) / 2.0,
(size - stringSize.height) / 2.0);
[attrString drawAtPoint:drawPoint];
[image unlockFocus];
[image setTemplate:YES];
return image;
}
bool macosBeginMenu(const char* label, const char *icon, bool enabled) {
NSString* title = [NSString stringWithUTF8String:label];
// Search for menu item with the given name
@@ -79,6 +193,17 @@ bool macosBeginMenu(const char* label, bool enabled) {
menuItem.title = title;
[menuItem setSubmenu:newMenu];
// Hide menus starting with '$' (used for special menus)
if (label[0] == '$') {
[menuItem setHidden:YES];
}
if (icon != NULL) {
NSString *iconString = [NSString stringWithUTF8String:icon];
NSImage* iconImage = imageFromIconFont(iconString, 16.0, [NSColor blackColor]);
[menuItem setImage:iconImage];
}
// Add the new menu to the end of the list
menuIndex = [s_menuStack[s_menuStackSize - 1] numberOfItems];
@@ -104,7 +229,7 @@ void macosEndMenu(void) {
s_menuStackSize -= 1;
}
bool macosMenuItem(const char* label, struct KeyEquivalent keyEquivalent, bool selected, bool enabled) {
bool macosMenuItem(const char* label, const char *icon, struct KeyEquivalent keyEquivalent, bool selected, bool enabled) {
NSString* title = [NSString stringWithUTF8String:label];
if (s_constructingMenu) {
@@ -113,6 +238,12 @@ bool macosMenuItem(const char* label, struct KeyEquivalent keyEquivalent, bool s
menuItem.action = @selector(OnClick:);
menuItem.target = s_menuItemHandler;
if (icon != NULL) {
NSString *iconString = [NSString stringWithUTF8String:icon];
NSImage* iconImage = imageFromIconFont(iconString, 16.0, [NSColor blackColor]);
[menuItem setImage:iconImage];
}
[menuItem setTag:s_currTag];
s_currTag += 1;
@@ -164,8 +295,8 @@ bool macosMenuItem(const char* label, struct KeyEquivalent keyEquivalent, bool s
return false;
}
bool macosMenuItemSelect(const char* label, struct KeyEquivalent keyEquivalent, bool* selected, bool enabled) {
if (macosMenuItem(label, keyEquivalent, selected != NULL ? *selected : false, enabled)) {
bool macosMenuItemSelect(const char* label, const char *icon, struct KeyEquivalent keyEquivalent, bool* selected, bool enabled) {
if (macosMenuItem(label, icon, keyEquivalent, selected != NULL ? *selected : false, enabled)) {
if (selected != NULL)
*selected = !(*selected);
@@ -180,3 +311,94 @@ void macosSeparator(void) {
[s_menuStack[s_menuStackSize - 1] addItem:separator];
}
}
@interface NSObject (DockMenuAddition)
- (NSMenu *)imhexApplicationDockMenu:(NSApplication *)sender;
@end
@implementation NSObject (DockMenuAddition)
- (NSMenu *)cloneMenu:(NSMenu *)originalMenu {
NSMenu *clonedMenu = [[NSMenu alloc] initWithTitle:[originalMenu title]];
for (NSMenuItem *item in [originalMenu itemArray]) {
NSMenuItem *clonedItem = [self cloneMenuItem:item];
[clonedMenu addItem:clonedItem];
}
return clonedMenu;
}
- (NSMenuItem *)cloneMenuItem:(NSMenuItem *)original {
if ([original isSeparatorItem]) {
return [NSMenuItem separatorItem];
}
// Create new item with same properties
NSMenuItem *clone = [[NSMenuItem alloc] initWithTitle:[original title]
action:[original action]
keyEquivalent:[original keyEquivalent]];
// Copy other properties
[clone setTarget:[original target]];
[clone setEnabled:[original isEnabled]];
[clone setImage:[original image]];
[clone setTag:[original tag]];
[clone setRepresentedObject:[original representedObject]];
[clone setToolTip:[original toolTip]];
[clone setState:[original state]];
// Handle submenus recursively
if ([original hasSubmenu]) {
NSMenu *clonedSubmenu = [self cloneMenu:[original submenu]];
[clone setSubmenu:clonedSubmenu];
}
return clone;
}
- (NSMenu *)imhexApplicationDockMenu:(NSApplication *)sender {
NSMenu *dockMenu = [[NSMenu alloc] init];
NSInteger menuIndex = [s_menuStack[s_menuStackSize - 1] indexOfItemWithTitle:@"$TASKBAR$"];
if (menuIndex == -1) {
return dockMenu;
}
NSMenuItem *fileMenuItem = [s_menuStack[s_menuStackSize - 1] itemAtIndex:menuIndex];
// Get the File submenu
NSMenu *fileSubmenu = [fileMenuItem submenu];
if (fileSubmenu) {
// Clone each item from the File submenu directly into the dock menu
for (NSMenuItem *item in [fileSubmenu itemArray]) {
NSMenuItem *clonedItem = [self cloneMenuItem:item];
[dockMenu addItem:clonedItem];
}
}
return dockMenu;
}
@end
void macosSetupDockMenu(void) {
@autoreleasepool {
// Get GLFW's delegate class
Class delegateClass = objc_getClass("GLFWApplicationDelegate");
if (delegateClass != nil) {
// Get our custom implementation
Method customMethod = class_getInstanceMethod([NSObject class],
@selector(imhexApplicationDockMenu:));
// Add the method to GLFW's delegate class
class_addMethod(delegateClass,
@selector(applicationDockMenu:),
method_getImplementation(customMethod),
method_getTypeEncoding(customMethod));
} else {
NSLog(@"ERROR: Could not find GLFWApplicationDelegate class");
}
}
}

View File

@@ -1,9 +1,14 @@
#include <cstddef>
#include "wolv/types.hpp"
#include <cmath>
#include <hex/helpers/opengl.hpp>
#include <opengl_support.h>
#include <hex/helpers/utils.hpp>
#include <hex/helpers/logger.hpp>
#include <string_view>
#include <vector>
#include <span>
#include <wolv/utils/guards.hpp>
#include <numbers>
@@ -133,17 +138,18 @@ namespace hex::gl {
GLint Shader::getUniformLocation(std::string_view name) {
auto uniform = m_uniforms.find(name.data());
auto nameStr = std::string(name);
auto uniform = m_uniforms.find(nameStr);
if (uniform == m_uniforms.end()) {
auto location = glGetUniformLocation(m_program, name.data());
auto location = glGetUniformLocation(m_program, nameStr.data());
if (location == -1) {
log::warn("Uniform '{}' not found in shader", name);
m_uniforms[name.data()] = -1;
m_uniforms[nameStr] = -1;
return -1;
}
m_uniforms[name.data()] = location;
uniform = m_uniforms.find(name.data());
m_uniforms[nameStr] = location;
uniform = m_uniforms.find(nameStr);
}
return uniform->second;

View File

@@ -1,3 +1,4 @@
#include <algorithm>
#include <hex/helpers/patches.hpp>
#include <hex/helpers/utils.hpp>
@@ -5,7 +6,6 @@
#include <hex/providers/provider.hpp>
#include <cstring>
#include <string_view>
namespace hex {
@@ -24,7 +24,7 @@ namespace hex {
[[nodiscard]] bool isSavable() const override { return false; }
[[nodiscard]] bool isSavableAsRecent() const override { return false; }
[[nodiscard]] bool open() override { return true; }
[[nodiscard]] OpenResult open() override { return {}; }
void close() override { }
void readRaw(u64 offset, void *buffer, size_t size) override {
@@ -83,7 +83,7 @@ namespace hex {
[[nodiscard]] UnlocalizedString getTypeName() const override { return ""; }
const std::map<u64, u8>& getPatches() const {
[[nodiscard]] const std::map<u64, u8>& getPatches() const {
return m_patches;
}
private:
@@ -92,7 +92,7 @@ namespace hex {
void pushStringBack(std::vector<u8> &buffer, const std::string &string) {
std::copy(string.begin(), string.end(), std::back_inserter(buffer));
std::ranges::copy(string, std::back_inserter(buffer));
}
template<typename T>

View File

@@ -14,7 +14,7 @@
namespace hex {
UDPServer::UDPServer(u16 port, Callback callback)
: m_port(port), m_callback(std::move(callback)), m_running(false), m_socketFd(-1) {
: m_port(port), m_callback(std::move(callback)), m_running(false) {
}
UDPServer::~UDPServer() {

View File

@@ -1,3 +1,4 @@
#include <algorithm>
#include <cwchar>
#include <hex/helpers/utils.hpp>
@@ -29,10 +30,12 @@
#elif defined(OS_LINUX)
#include <unistd.h>
#include <dlfcn.h>
#include <spawn.h>
#include <hex/helpers/utils_linux.hpp>
#elif defined(OS_MACOS)
#include <unistd.h>
#include <dlfcn.h>
#include <spawn.h>
#include <hex/helpers/utils_macos.hpp>
#include <CoreFoundation/CoreFoundation.h>
#elif defined(OS_WEB)
@@ -98,7 +101,7 @@ namespace hex {
string = wolv::util::replaceStrings(string, ",", "");
// Check for non-hex characters
bool isValidHexString = std::find_if(string.begin(), string.end(), [](char c) {
bool isValidHexString = std::ranges::find_if(string, [](char c) {
return !std::isxdigit(c) && !std::isspace(c);
}) == string.end();
@@ -309,6 +312,86 @@ namespace hex {
return ::system(command.c_str());
}
std::optional<std::string> executeCommandWithOutput(const std::string &command) {
std::array<char, 256> buffer = {};
std::string result;
#if defined(OS_WINDOWS)
FILE* pipe = _popen(command.c_str(), "r");
#else
FILE* pipe = popen(command.c_str(), "r");
#endif
if (!pipe) {
hex::log::error("Failed to open pipe for command: {}", command);
return std::nullopt;
}
try {
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
result += buffer.data();
}
} catch (const std::exception &e) {
hex::log::error("Exception while reading command output: {}", e.what());
#if defined(OS_WINDOWS)
_pclose(pipe);
#else
pclose(pipe);
#endif
return std::nullopt;
}
#if defined(OS_WINDOWS)
int exitCode = _pclose(pipe);
#else
int exitCode = pclose(pipe);
#endif
if (exitCode != 0) {
hex::log::debug("Command exited with code {}: {}", exitCode, command);
}
return result;
}
void executeCommandDetach(const std::string &command) {
#if defined(OS_WINDOWS)
STARTUPINFOA si = { };
PROCESS_INFORMATION pi = { };
si.cb = sizeof(si);
DWORD flags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW;
std::string cmdCopy = command;
BOOL result = ::CreateProcessA(
nullptr,
cmdCopy.data(),
nullptr,
nullptr,
false,
flags,
nullptr,
nullptr,
&si,
&pi
);
if (result) {
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
}
#elif defined(OS_MACOS) || defined(OS_LINUX)
pid_t pid;
const char* argv[] = { "sh", "-c", command.c_str(), nullptr };
::posix_spawnp(&pid, "sh", nullptr, nullptr, const_cast<char* const*>(argv), nullptr);
#elif defined(OS_WEB)
std::ignore = command;
#endif
}
void openWebpage(std::string url) {
if (!url.contains("://"))
url = "https://" + url;
@@ -463,7 +546,7 @@ namespace hex {
if (ch <= 0x7F) {
unicode = ch;
unicodeSize = 0;
} else if (ch <= 0xBF) {
} else if (ch <= 0xBF) { //NOLINT(bugprone-branch-clone)
return { };
} else if (ch <= 0xDF) {
unicode = ch&0x1F;
@@ -524,7 +607,7 @@ namespace hex {
index += 1;
if (wch < 0xD800 || wch > 0xDFFF) {
unicode = static_cast<u32>(wch);
unicode = static_cast<u32>(wch); // NOLINT(cert-str34-c)
} else if (wch >= 0xD800 && wch <= 0xDBFF) {
if (index == utf16.size())
return "";
@@ -785,7 +868,7 @@ namespace hex {
input.imbue(std::locale(std::setlocale(LC_ALL, nullptr)));
tm time = {};
input >> std::get_time(&time, format.data());
input >> std::get_time(&time, std::string(format).data());
if (input.fail()) {
return std::nullopt;
}
@@ -824,10 +907,10 @@ namespace hex {
if (lang.has_value() && !lang->empty() && *lang != "C" && *lang != "C.UTF-8") {
auto parts = wolv::util::splitString(*lang, ".");
if (parts.size() > 0)
if (!parts.empty())
return parts[0];
else
return *lang;
return lang;
}
return std::nullopt;
@@ -905,6 +988,12 @@ namespace hex {
nid.dwInfoFlags = NIIF_INFO;
Shell_NotifyIcon(NIM_ADD, &nid);
Sleep(100);
Shell_NotifyIcon(NIM_DELETE, &nid);
CloseWindow(hwnd);
DestroyWindow(hwnd);
#elif defined(OS_MACOS)
toastMessageMacos(title.c_str(), message.c_str());
#elif defined(OS_LINUX)
@@ -913,17 +1002,21 @@ namespace hex {
}
#elif defined(OS_WEB)
EM_ASM({
const t = UTF8ToString($0);
const m = UTF8ToString($1);
try {
const t = UTF8ToString($0);
const m = UTF8ToString($1);
if (Notification.permission === "granted") {
new Notification(t, { body: m });
} else if (Notification.permission !== "denied") {
Notification.requestPermission().then(function(p) {
if (p === "granted") {
new Notification(t, { body: m });
}
});
if (Notification.permission === "granted") {
new Notification(t, { body: m });
} else if (Notification.permission !== "denied") {
Notification.requestPermission().then(function(p) {
if (p === "granted") {
new Notification(t, { body: m });
}
});
}
} catch (e) {
console.error(e);
}
}, title.c_str(), message.c_str());
#endif

View File

@@ -10,13 +10,14 @@ namespace hex {
void executeCmd(const std::vector<std::string> &argsVector) {
std::vector<char*> cArgsVector;
for (const auto &str : argsVector) {
cArgsVector.reserve(argsVector.size());
for (const auto &str : argsVector) {
cArgsVector.push_back(const_cast<char*>(str.c_str()));
}
cArgsVector.push_back(nullptr);
if (fork() == 0) {
execvp(cArgsVector[0], &cArgsVector[0]);
execvp(cArgsVector[0], cArgsVector.data());
log::error("execvp() failed: {}", strerror(errno));
exit(EXIT_FAILURE);
}

View File

@@ -1,5 +1,8 @@
#if defined(OS_MACOS)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include <CoreFoundation/CFBundle.h>
#include <ApplicationServices/ApplicationServices.h>
#include <Foundation/NSUserDefaults.h>
@@ -70,6 +73,49 @@
cocoaWindow.titlebarAppearsTransparent = YES;
cocoaWindow.styleMask |= NSWindowStyleMaskFullSizeContentView;
// Setup liquid glass background effect
{
NSView* glfwContentView = [cocoaWindow contentView];
NSOpenGLContext* context = [NSOpenGLContext currentContext];
if (!context) {
glfwMakeContextCurrent(window);
context = [NSOpenGLContext currentContext];
}
GLint opaque = 0;
[context setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
[context update];
NSView* containerView = [[NSView alloc] initWithFrame:[glfwContentView frame]];
containerView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[containerView setWantsLayer:YES];
Class glassEffectClass = NSClassFromString(@"NSGlassEffectView");
NSView* effectView = nil;
if (glassEffectClass) {
// Use the new liquid glass effect
effectView = [[glassEffectClass alloc] initWithFrame:[containerView bounds]];
} else {
// Fall back to NSVisualEffectView for older systems
NSVisualEffectView* visualEffectView = [[NSVisualEffectView alloc] initWithFrame:[containerView bounds]];
visualEffectView.material = NSVisualEffectMaterialHUDWindow;
visualEffectView.blendingMode = NSVisualEffectBlendingModeBehindWindow;
visualEffectView.state = NSVisualEffectStateActive;
effectView = visualEffectView;
}
effectView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[containerView addSubview:effectView];
[glfwContentView removeFromSuperview];
glfwContentView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[containerView addSubview:glfwContentView];
[cocoaWindow setContentView:containerView];
}
[cocoaWindow setOpaque:NO];
[cocoaWindow setHasShadow:YES];
[cocoaWindow setBackgroundColor:[NSColor colorWithWhite: 0 alpha: 0.001f]];
@@ -370,6 +416,27 @@
return [bundlePath.pathExtension.lowercaseString isEqualToString:@"app"];
}
@interface NotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
@end
@implementation NotificationDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
if (@available(macOS 11.0, *)) {
completionHandler(UNNotificationPresentationOptionBanner |
UNNotificationPresentationOptionSound |
UNNotificationPresentationOptionList);
} else {
// For macOS 10.15 and earlier
completionHandler(UNNotificationPresentationOptionAlert |
UNNotificationPresentationOptionSound);
}
}
@end
void toastMessageMacos(const char *title, const char *message) {
@autoreleasepool {
// Only show notification if we're inside a bundle
@@ -407,5 +474,6 @@
}
}
#pragma clang diagnostic pop
#endif

View File

@@ -0,0 +1,48 @@
#include <hex/mcp/client.hpp>
#include <hex/mcp/server.hpp>
#include <hex.hpp>
#include <string>
#include <cstdlib>
#include <fmt/format.h>
#include <hex/api/imhex_api/system.hpp>
#include <hex/helpers/logger.hpp>
#include <nlohmann/json.hpp>
#include <wolv/literals.hpp>
#include <wolv/net/socket_client.hpp>
namespace hex::mcp {
using namespace wolv::literals;
int Client::run(std::istream &input, std::ostream &output) {
wolv::net::SocketClient client(wolv::net::SocketClient::Type::TCP, true);
client.connect("127.0.0.1", Server::McpInternalPort);
while (true) {
std::string request;
std::getline(input, request);
if (ImHexApi::System::isMainInstance()) {
JsonRpc response(request);
response.setError(JsonRpc::ErrorCode::InternalError, "No other instance of ImHex is running. Make sure that you have ImHex open already.");
output << response.execute([](auto, auto){ return nlohmann::json::object(); }).value_or("") << '\n';
continue;
}
client.writeString(request);
auto response = client.readBytesUntil(0x00);
if (!response.empty() && response.front() != 0x00)
output << std::string(response.begin(), response.end()) << std::endl;
if (!client.isConnected())
break;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return EXIT_SUCCESS;
}
}

View File

@@ -0,0 +1,243 @@
#include <hex/mcp/server.hpp>
#include <string>
#include <fmt/format.h>
#include <hex/helpers/logger.hpp>
#include <nlohmann/json.hpp>
#include <utility>
#include <hex/api/task_manager.hpp>
#include <wolv/net/socket_client.hpp>
#include <hex/api/imhex_api/system.hpp>
namespace hex::mcp {
std::optional<std::string> JsonRpc::execute(const Callback &callback) {
try {
auto requestJson = nlohmann::json::parse(m_request);
if (requestJson.is_array()) {
return handleBatchedMessages(requestJson, callback).transform([](const auto &response) { return response.dump(); });
} else {
return handleMessage(requestJson, callback).transform([](const auto &response) { return response.dump(); });
}
} catch (const nlohmann::json::exception &) {
return createErrorMessage(ErrorCode::ParseError, "Parse error").dump();
}
}
void JsonRpc::setError(ErrorCode code, std::string message) {
m_error = Error{ code, std::move(message) };
}
std::optional<nlohmann::json> JsonRpc::handleMessage(const nlohmann::json &request, const Callback &callback) {
try {
// Validate JSON-RPC request
if (!request.contains("jsonrpc") || request["jsonrpc"] != "2.0" ||
!request.contains("method") || !request["method"].is_string()) {
m_id = request.contains("id") ? std::optional(request["id"].get<int>()) : std::nullopt;
return createErrorMessage(ErrorCode::InvalidRequest, "Invalid Request");
}
m_id = request.contains("id") ? std::optional(request["id"].get<int>()) : std::nullopt;
// Return a user-specified error if set
if (m_error.has_value()) {
return createErrorMessage(m_error->code, m_error->message);
}
// Execute the method
auto result = callback(request["method"].get<std::string>(), request.value("params", nlohmann::json::object()));
if (!m_id.has_value())
return std::nullopt;
return createResponseMessage(result.is_null() ? nlohmann::json::object() : result);
} catch (const MethodNotFoundException &) {
return createErrorMessage(ErrorCode::MethodNotFound, "Method not found");
} catch (const InvalidParametersException &) {
return createErrorMessage(ErrorCode::InvalidParams, "Invalid params");
} catch (const std::exception &e) {
return createErrorMessage(ErrorCode::InternalError, e.what());
}
}
std::optional<nlohmann::json> JsonRpc::handleBatchedMessages(const nlohmann::json &request, const Callback &callback) {
if (!request.is_array()) {
return createErrorMessage(ErrorCode::InvalidRequest, "Invalid Request");
}
nlohmann::json responses = nlohmann::json::array();
for (const auto &message : request) {
auto response = handleMessage(message, callback);
if (response.has_value())
responses.push_back(*response);
}
if (responses.empty())
return std::nullopt;
return responses;
}
nlohmann::json JsonRpc::createDefaultMessage() {
nlohmann::json message;
message["jsonrpc"] = "2.0";
if (m_id.has_value())
message["id"] = m_id.value();
else
message["id"] = nullptr;
return message;
}
nlohmann::json JsonRpc::createErrorMessage(ErrorCode code, const std::string &message) {
auto json = createDefaultMessage();
json["error"] = {
{ "code", int(code) },
{ "message", message }
};
return json;
}
nlohmann::json JsonRpc::createResponseMessage(const nlohmann::json &result) {
auto json = createDefaultMessage();
json["result"] = result;
return json;
}
Server::Server() : m_server(McpInternalPort, 1024, 1, true) {
}
Server::~Server() {
this->shutdown();
}
void Server::listen() {
m_server.accept([this](auto, const std::vector<u8> &data) -> std::vector<u8> {
std::string request(data.begin(), data.end());
TaskManager::setCurrentThreadName("MCP Server");
log::debug("MCP ----> {}", request);
JsonRpc rpc(request);
auto response = rpc.execute([this](const std::string &method, const nlohmann::json &params) -> nlohmann::json {
if (method == "initialize") {
return handleInitialize(params);
} else if (method.starts_with("notifications/")) {
handleNotifications(method.substr(14), params);
return {};
} else if (method.ends_with("/list")) {
auto primitiveName = method.substr(0, method.size() - 5);
if (m_primitives.contains(primitiveName)) {
nlohmann::json capabilitiesList = nlohmann::json::array();
for (const auto &[name, primitive] : m_primitives[primitiveName]) {
capabilitiesList.push_back(primitive.capabilities);
}
nlohmann::json result;
result[primitiveName] = capabilitiesList;
return result;
}
} else if (method.ends_with("/call")) {
auto primitive = method.substr(0, method.size() - 5);
if (auto primitiveIt = m_primitives.find(primitive); primitiveIt != m_primitives.end()) {
auto name = params.value("name", "");
if (auto functionIt = primitiveIt->second.find(name); functionIt != primitiveIt->second.end()) {
auto result = functionIt->second.function(params.value("arguments", nlohmann::json::object()));
return result.is_null() ? nlohmann::json::object() : result;
}
}
}
throw JsonRpc::MethodNotFoundException();
});
log::debug("MCP <---- {}", response.value_or("<Nothing>"));
if (response.has_value()) {
response->push_back(0x00);
return { response->begin(), response->end() };
}
else
return std::vector<u8>{ 0x00 };
}, [this](auto) {
log::info("MCP client disconnected");
m_connected = false;
m_clientInfo = {};
}, true);
}
void Server::shutdown() {
m_server.shutdown();
}
void Server::disconnect() {
m_server.disconnectClients();
}
void Server::addPrimitive(std::string type, std::string_view capabilities, std::function<nlohmann::json(const nlohmann::json &params)> function) {
auto json = nlohmann::json::parse(capabilities);
auto name = json["name"].get<std::string>();
m_primitives[type][name] = {
.capabilities=json,
.function=function
};
}
nlohmann::json Server::handleInitialize(const nlohmann::json &params) {
constexpr static auto ServerName = "ImHex";
constexpr static auto ProtocolVersion = "2025-06-18";
m_clientInfo = {};
if (params.contains("protocolVersion")) {
auto clientProtocolVersion = params["protocolVersion"].get<std::string>();
m_clientInfo.protocolVersion = clientProtocolVersion;
} else {
throw JsonRpc::InvalidParametersException();
}
if (params.contains("clientInfo")) {
const auto &clientInfo = params["clientInfo"];
m_clientInfo.name = clientInfo.value("name", "???");
m_clientInfo.version = clientInfo.value("version", "???");
log::info("MCP client connected: {} v{} (protocol {})", m_clientInfo.name, m_clientInfo.version, m_clientInfo.protocolVersion);
} else {
log::info("MCP client connected: Unknown client info");
}
return {
{ "protocolVersion", ProtocolVersion },
{
"capabilities",
{
{ "tools", nlohmann::json::object() },
},
},
{
"serverInfo", {
{ "name", ServerName },
{ "version", ImHexApi::System::getImHexVersion().get() }
}
}
};
}
void Server::handleNotifications(const std::string &method, [[maybe_unused]] const nlohmann::json &params) {
if (method == "initialized") {
m_connected = true;
}
}
bool Server::isConnected() {
return m_connected;
}
}

View File

@@ -11,9 +11,9 @@ namespace hex::prv {
clearCache();
}
bool CachedProvider::open() {
Provider::OpenResult CachedProvider::open() {
clearCache();
return true;
return {};
}
void CachedProvider::close() {
@@ -49,7 +49,7 @@ namespace hex::prv {
{
std::unique_lock lock(m_cacheMutex);
m_cache[cacheSlot] = Block{blockIndex, std::move(blockData), false};
m_cache[cacheSlot] = Block{.index=blockIndex, .data=std::move(blockData), .dirty=false};
std::copy_n(m_cache[cacheSlot]->data.begin() + blockOffset, toRead, out);
}
@@ -76,7 +76,7 @@ namespace hex::prv {
if (!slot || slot->index != blockIndex) {
std::vector<uint8_t> blockData(m_cacheBlockSize);
readFromSource(blockIndex * m_cacheBlockSize, blockData.data(), m_cacheBlockSize);
slot = Block { blockIndex, std::move(blockData), false };
slot = Block { .index=blockIndex, .data=std::move(blockData), .dirty=false };
}
std::copy_n(in, toWrite, slot->data.begin() + blockOffset);

View File

@@ -4,12 +4,12 @@
namespace hex::prv {
bool MemoryProvider::open() {
Provider::OpenResult MemoryProvider::open() {
if (m_data.empty()) {
m_data.resize(1);
}
return true;
return {};
}
void MemoryProvider::readRaw(u64 offset, void *buffer, size_t size) {

View File

@@ -7,12 +7,14 @@
#include <cmath>
#include <cstring>
#include <optional>
#include <hex/api/content_registry/settings.hpp>
#include <hex/helpers/magic.hpp>
#include <wolv/io/file.hpp>
#include <wolv/literals.hpp>
#include <nlohmann/json.hpp>
#include <wolv/utils/string.hpp>
namespace hex::prv {
@@ -24,6 +26,28 @@ namespace hex::prv {
}
IProviderDataBackupable::IProviderDataBackupable(Provider* provider) : m_provider(provider) {
m_shouldCreateBackups = ContentRegistry::Settings::read<bool>("hex.builtin.setting.general", "hex.builtin.setting.general.backups.file_backup.enable", true);
m_maxSize = ContentRegistry::Settings::read<u32>("hex.builtin.setting.general", "hex.builtin.setting.general.backups.file_backup.max_size", 1_MiB);
m_backupExtension = ContentRegistry::Settings::read<std::string>("hex.builtin.setting.general", "hex.builtin.setting.general.backups.file_backup.extension", ".bak");
}
void IProviderDataBackupable::createBackupIfNeeded(const std::fs::path &inputFilePath) {
if (!m_shouldCreateBackups || m_backupCreated)
return;
if (m_provider->getActualSize() > m_maxSize)
return;
const std::fs::path backupFilePath = wolv::util::toUTF8String(inputFilePath) + m_backupExtension;
if (wolv::io::fs::copyFile(inputFilePath, backupFilePath, std::fs::copy_options::overwrite_existing)) {
if (wolv::io::fs::exists(backupFilePath)) {
m_backupCreated = true;
log::info("Created backup of provider data at '{}'", backupFilePath.string());
}
}
}
Provider::Provider() : m_undoRedoStack(this), m_id(s_idCounter++) {
@@ -33,7 +57,7 @@ namespace hex::prv {
m_overlays.clear();
if (auto selection = ImHexApi::HexEditor::getSelection(); selection.has_value() && selection->provider == this)
EventRegionSelected::post(ImHexApi::HexEditor::ProviderRegion { { 0x00, 0x00 }, nullptr });
EventRegionSelected::post(ImHexApi::HexEditor::ProviderRegion { { .address=0x00, .size=0x00 }, nullptr });
}
void Provider::read(u64 offset, void *buffer, size_t size, bool overlays) {
@@ -265,19 +289,19 @@ namespace hex::prv {
u64 absoluteAddress = address - this->getBaseAddress();
if (absoluteAddress < this->getActualSize())
return { Region { this->getBaseAddress() + absoluteAddress, this->getActualSize() - absoluteAddress }, true };
return { Region { .address=this->getBaseAddress() + absoluteAddress, .size=this->getActualSize() - absoluteAddress }, true };
bool insideValidRegion = false;
std::optional<u64> nextRegionAddress;
for (const auto &overlay : m_overlays) {
Region overlayRegion = { overlay->getAddress(), overlay->getSize() };
Region overlayRegion = { .address=overlay->getAddress(), .size=overlay->getSize() };
if (!nextRegionAddress.has_value() || overlay->getAddress() < nextRegionAddress) {
nextRegionAddress = overlayRegion.getStartAddress();
}
if (Region { address, 1 }.overlaps(overlayRegion)) {
if (Region { .address=address, .size=1 }.overlaps(overlayRegion)) {
insideValidRegion = true;
}
}
@@ -285,7 +309,7 @@ namespace hex::prv {
if (!nextRegionAddress.has_value())
return { Region::Invalid(), false };
else
return { Region { address, *nextRegionAddress - address }, insideValidRegion };
return { Region { .address=address, .size=*nextRegionAddress - address }, insideValidRegion };
}

View File

@@ -12,8 +12,7 @@ namespace hex::prv::undo {
namespace {
std::atomic<bool> s_locked;
std::mutex s_mutex;
std::recursive_mutex s_mutex;
}
@@ -21,12 +20,13 @@ namespace hex::prv::undo {
}
std::recursive_mutex& Stack::getMutex() {
return s_mutex;
}
void Stack::undo(u32 count) {
std::scoped_lock lock(s_mutex);
s_locked = true;
ON_SCOPE_EXIT { s_locked = false; };
std::lock_guard lock(s_mutex);
// If there are no operations, we can't undo anything.
if (m_undoStack.empty())
@@ -42,14 +42,12 @@ namespace hex::prv::undo {
m_redoStack.emplace_back(std::move(m_undoStack.back()));
m_redoStack.back()->undo(m_provider);
m_undoStack.pop_back();
EventDataChanged::post(m_provider);
}
}
void Stack::redo(u32 count) {
std::scoped_lock lock(s_mutex);
s_locked = true;
ON_SCOPE_EXIT { s_locked = false; };
std::lock_guard lock(s_mutex);
// If there are no operations, we can't redo anything.
if (m_redoStack.empty())
@@ -65,10 +63,13 @@ namespace hex::prv::undo {
m_undoStack.emplace_back(std::move(m_redoStack.back()));
m_undoStack.back()->redo(m_provider);
m_redoStack.pop_back();
EventDataChanged::post(m_provider);
}
}
void Stack::groupOperations(u32 count, const UnlocalizedString &unlocalizedName) {
std::lock_guard lock(s_mutex);
if (count <= 1)
return;
@@ -94,14 +95,19 @@ namespace hex::prv::undo {
}
void Stack::apply(const Stack &otherStack) {
std::lock_guard lock(s_mutex);
for (const auto &operation : otherStack.m_undoStack) {
this->add(operation->clone());
}
}
void Stack::reapply() {
std::lock_guard lock(s_mutex);
for (const auto &operation : m_undoStack) {
operation->redo(m_provider);
EventDataChanged::post(m_provider);
}
}
@@ -109,14 +115,7 @@ namespace hex::prv::undo {
bool Stack::add(std::unique_ptr<Operation> &&operation) {
// If we're already inside of an undo/redo operation, ignore new operations being added
if (s_locked)
return false;
s_locked = true;
ON_SCOPE_EXIT { s_locked = false; };
std::scoped_lock lock(s_mutex);
std::lock_guard lock(s_mutex);
// Clear the redo stack
m_redoStack.clear();
@@ -133,10 +132,14 @@ namespace hex::prv::undo {
}
bool Stack::canUndo() const {
std::lock_guard lock(s_mutex);
return !m_undoStack.empty();
}
bool Stack::canRedo() const {
std::lock_guard lock(s_mutex);
return !m_redoStack.empty();
}

View File

@@ -5,7 +5,7 @@ namespace hex::test {
static std::map<std::string, Test> s_tests;
int Tests::addTest(const std::string &name, Function func, bool shouldFail) noexcept {
s_tests.insert({
name, { func, shouldFail }
name, { .function=func, .shouldFail=shouldFail }
});
return 0;

View File

@@ -17,6 +17,7 @@
#include <set>
#include <string>
#include <algorithm>
#include <numbers>
#include <hex/api/imhex_api/system.hpp>
@@ -285,13 +286,11 @@ namespace ImGuiExt {
}
void Texture::reset() {
#if !defined(OS_WEB)
if (glDeleteTextures == nullptr)
return;
#endif
if (m_textureId != 0) {
glDeleteTextures(1, reinterpret_cast<GLuint*>(&m_textureId));
#if !defined(OS_WEB)
if (glDeleteTextures != nullptr)
glDeleteTextures(1, reinterpret_cast<GLuint*>(&m_textureId));
#endif
m_textureId = 0;
}
}
@@ -1218,6 +1217,21 @@ namespace ImGuiExt {
return res;
}
bool DimmedArrowButton(const char *id, ImGuiDir dir, ImVec2 size) {
PushStyleColor(ImGuiCol_ButtonHovered, GetCustomColorU32(ImGuiCustomCol_DescButtonHovered));
PushStyleColor(ImGuiCol_Button, GetCustomColorU32(ImGuiCustomCol_DescButton));
PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_ButtonActive));
PushStyleColor(ImGuiCol_ButtonActive, GetCustomColorU32(ImGuiCustomCol_DescButtonActive));
PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5 * hex::ImHexApi::System::getGlobalScale());
bool res = ArrowButtonEx(id, dir, size);
PopStyleColor(4);
PopStyleVar(1);
return res;
}
bool DimmedButtonToggle(const char *icon, bool *v, ImVec2 size, ImVec2 iconOffset) {
bool pushed = false;
bool toggled = false;
@@ -1355,9 +1369,9 @@ namespace ImGuiExt {
bool VSliderAngle(const char* label, const ImVec2& size, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags) {
if (format == nullptr)
format = "%.0f deg";
float v_deg = (*v_rad) * 360.0F / (2 * IM_PI);
float v_deg = (*v_rad) * 360.0F / (2 * std::numbers::pi_v<float>);
bool value_changed = ImGui::VSliderFloat(label, size, &v_deg, v_degrees_min, v_degrees_max, format, flags);
*v_rad = v_deg * (2 * IM_PI) / 360.0F;
*v_rad = v_deg * (2 * std::numbers::pi_v<float>) / 360.0F;
return value_changed;
}
@@ -1501,9 +1515,9 @@ namespace ImGuiExt {
bool IsDarkBackground(const ImColor& bgColor) {
// Extract RGB components in 0255 range
int r = static_cast<int>(bgColor.Value.x * 255.0f);
int g = static_cast<int>(bgColor.Value.y * 255.0f);
int b = static_cast<int>(bgColor.Value.z * 255.0f);
int r = static_cast<int>(bgColor.Value.x * 255.0F);
int g = static_cast<int>(bgColor.Value.y * 255.0F);
int b = static_cast<int>(bgColor.Value.z * 255.0F);
// Compute brightness using perceived luminance
int brightness = (r * 299 + g * 587 + b * 114) / 1000;

View File

@@ -1,8 +1,10 @@
#include <hex/ui/view.hpp>
#include <hex/api/task_manager.hpp>
#include <hex/helpers/auto_reset.hpp>
#include <hex/api/imhex_api/provider.hpp>
#include <hex/api/task_manager.hpp>
#include <hex/api/tutorial_manager.hpp>
#include <hex/providers/provider.hpp>
#include <imgui.h>
@@ -20,7 +22,7 @@ namespace hex {
}
bool View::shouldProcess() const {
return this->shouldDraw() && this->getWindowOpenState();
return true;
}
bool View::hasViewMenuItemEntry() const {
@@ -116,4 +118,63 @@ namespace hex {
}
void View::Window::draw(ImGuiWindowFlags extraFlags) {
if (this->shouldDraw()) {
if (!allowScroll())
extraFlags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
ImGui::SetNextWindowSizeConstraints(this->getMinSize(), this->getMaxSize());
const auto title = fmt::format("{} {}", this->getIcon(), View::toWindowName(this->getUnlocalizedName()));
if (ImGui::Begin(title.c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse | extraFlags | this->getWindowFlags())) {
TutorialManager::setLastItemInteractiveHelpPopup([this]{ this->drawHelpText(); });
this->drawContent();
}
ImGui::End();
}
}
void View::Special::draw(ImGuiWindowFlags extraFlags) {
std::ignore = extraFlags;
if (this->shouldDraw()) {
ImGui::SetNextWindowSizeConstraints(this->getMinSize(), this->getMaxSize());
this->drawContent();
}
}
void View::Floating::draw(ImGuiWindowFlags extraFlags) {
Window::draw(extraFlags | ImGuiWindowFlags_NoDocking);
}
void View::Scrolling::draw(ImGuiWindowFlags extraFlags) {
Window::draw(extraFlags);
}
void View::Modal::draw(ImGuiWindowFlags extraFlags) {
if (this->shouldDraw()) {
if (this->getWindowOpenState())
ImGui::OpenPopup(View::toWindowName(this->getUnlocalizedName()).c_str());
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F));
ImGui::SetNextWindowSizeConstraints(this->getMinSize(), this->getMaxSize());
const auto title = fmt::format("{} {}", this->getIcon(), View::toWindowName(this->getUnlocalizedName()));
if (ImGui::BeginPopupModal(title.c_str(), this->hasCloseButton() ? &this->getWindowOpenState() : nullptr, ImGuiWindowFlags_NoCollapse | extraFlags | this->getWindowFlags())) {
this->drawContent();
ImGui::EndPopup();
}
if (ImGui::IsKeyPressed(ImGuiKey_Escape))
this->getWindowOpenState() = false;
}
}
void View::FullScreen::draw(ImGuiWindowFlags extraFlags) {
std::ignore = extraFlags;
this->drawContent();
this->drawAlwaysVisibleContent();
}
}

Some files were not shown because too many files have changed in this diff Show More