Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/libimhex/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ set(LIBIMHEX_SOURCES
source/ui/banner.cpp

source/subcommands/subcommands.cpp
source/init/cli.cpp
)

if (APPLE)
Expand Down
7 changes: 7 additions & 0 deletions lib/libimhex/include/hex/api/imhex_api/system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ EXPORT_MODULE namespace hex {
bool frameRateUnlockRequested();
void resetFrameRateUnlockRequested();

void setReadOnlyMode(bool enabled);

}

/**
Expand Down Expand Up @@ -186,6 +188,11 @@ EXPORT_MODULE namespace hex {
*/
std::string getInitArgument(const std::string &key);

/**
* @brief Returns whether ImHex is running in read-only mode (no edits/saves)
*/
bool isReadOnlyMode();

/**
* @brief Sets if ImHex should follow the system theme
* @param enabled Whether to follow the system theme
Expand Down
9 changes: 9 additions & 0 deletions lib/libimhex/source/api/imhex_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,11 @@ namespace hex {
s_frameRateUnlockRequested = false;
}

static bool s_readOnlyMode = false;
void setReadOnlyMode(bool enabled) {
s_readOnlyMode = enabled;
}

}

bool isMainInstance() {
Expand Down Expand Up @@ -1092,6 +1097,10 @@ namespace hex {
RequestSetPostProcessingShader::post(vertexShader, fragmentShader);
}

bool isReadOnlyMode() {
return impl::s_readOnlyMode;
}


}

Expand Down
15 changes: 15 additions & 0 deletions lib/libimhex/source/init/cli.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <hex.hpp>

#include <hex/api/imhex_api/system.hpp>

namespace hex::init::cli_support {

void applyGlobalFlag(const std::string &arg) {
if (arg == "--readonly" || arg == "-r") {
ImHexApi::System::impl::setReadOnlyMode(true);
}
}

}


9 changes: 7 additions & 2 deletions lib/libimhex/source/providers/provider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ namespace hex::prv {
}

void Provider::write(u64 offset, const void *buffer, size_t size) {
if (!this->isWritable())
if (!this->isWritable() || ImHexApi::System::isReadOnlyMode())
return;

EventProviderDataModified::post(this, offset, size, static_cast<const u8*>(buffer));
this->markDirty();
}

void Provider::save() {
if (!this->isWritable())
if (!this->isWritable() || ImHexApi::System::isReadOnlyMode())
return;

EventProviderSaved::post(this);
Expand All @@ -76,6 +76,9 @@ namespace hex::prv {
}

bool Provider::resize(u64 newSize) {
if (ImHexApi::System::isReadOnlyMode()) {
return false;
}
if (newSize >> 63) {
log::error("new provider size is very large ({}). Is it a negative number ?", newSize);
return false;
Expand All @@ -92,12 +95,14 @@ namespace hex::prv {
}

void Provider::insert(u64 offset, u64 size) {
if (ImHexApi::System::isReadOnlyMode()) return;
EventProviderDataInserted::post(this, offset, size);

this->markDirty();
}

void Provider::remove(u64 offset, u64 size) {
if (ImHexApi::System::isReadOnlyMode()) return;
EventProviderDataRemoved::post(this, offset, size);

this->markDirty();
Expand Down
19 changes: 18 additions & 1 deletion main/gui/source/init/run/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,24 @@ namespace hex::init {
PluginManager::load(dir);
}

// Process the arguments
// Process our own global flags first
{
std::vector<std::string> remaining;
remaining.reserve(args.size());

for (size_t i = 0; i < args.size(); i += 1) {
const auto &arg = args[i];
if (arg == "--readonly" || arg == "-r") {
ImHexApi::System::impl::setReadOnlyMode(true);
continue;
}
remaining.push_back(arg);
}

args.swap(remaining);
}

// Process the arguments (subcommands) after handling globals
hex::subcommands::processArguments(args);

// Explicitly don't unload plugins again here.
Expand Down
6 changes: 3 additions & 3 deletions plugins/builtin/source/content/events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ namespace hex::plugin::builtin {

EventWindowClosing::subscribe([](GLFWwindow *window) {
imhexClosing = false;
if (ImHexApi::Provider::isDirty() && !imhexClosing) {
if (!ImHexApi::System::isReadOnlyMode() && ImHexApi::Provider::isDirty() && !imhexClosing) {
glfwSetWindowShouldClose(window, GLFW_FALSE);
ui::PopupQuestion::open("hex.builtin.popup.exit_application.desc"_lang,
[] {
Expand All @@ -106,7 +106,7 @@ namespace hex::plugin::builtin {

EventCloseButtonPressed::subscribe([]() {
if (ImHexApi::Provider::isValid()) {
if (ImHexApi::Provider::isDirty()) {
if (!ImHexApi::System::isReadOnlyMode() && ImHexApi::Provider::isDirty()) {
ui::PopupQuestion::open("hex.builtin.popup.exit_application.desc"_lang,
[] {
for (const auto &provider : ImHexApi::Provider::getProviders())
Expand All @@ -132,7 +132,7 @@ namespace hex::plugin::builtin {
});

EventProviderClosing::subscribe([](const prv::Provider *provider, bool *shouldClose) {
if (provider->isDirty()) {
if (!ImHexApi::System::isReadOnlyMode() && provider->isDirty()) {
*shouldClose = false;
PopupUnsavedChanges::open("hex.builtin.popup.close_provider.desc"_lang,
[]{
Expand Down
2 changes: 1 addition & 1 deletion plugins/builtin/source/content/main_menu_items.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ namespace hex::plugin::builtin {
}

bool noRunningTaskAndWritableProvider() {
return noRunningTasks() && ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isWritable();
return noRunningTasks() && !ImHexApi::System::isReadOnlyMode() && ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isWritable();
}

}
Expand Down
3 changes: 2 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ target_compile_definitions(tests_common PUBLIC IMHEX_PROJECT_NAME="${PROJECT_NAM

add_subdirectory(helpers)
add_subdirectory(algorithms)
add_subdirectory(plugins)
add_subdirectory(plugins)
add_subdirectory(cli)
25 changes: 25 additions & 0 deletions tests/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.16)

project(cli_test)
set(TEST_CATEGORY CLI)

set(AVAILABLE_TESTS
ReadOnlyFlagSetsMode
)

add_executable(${PROJECT_NAME}
source/test_cli_readonly.cpp
)

target_include_directories(${PROJECT_NAME} PRIVATE include)
target_link_libraries(${PROJECT_NAME} PRIVATE libimhex tests_common ${FMT_LIBRARIES})

set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

foreach (test IN LISTS AVAILABLE_TESTS)
add_test(NAME "${TEST_CATEGORY}/${test}" COMMAND ${PROJECT_NAME} "${test}" WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
endforeach ()

add_dependencies(unit_tests ${PROJECT_NAME})


33 changes: 33 additions & 0 deletions tests/cli/source/test_cli_readonly.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <hex/test/tests.hpp>

#include <hex/api/imhex_api/system.hpp>

// We only declare the CLI runner here; it's defined in the GUI main module
namespace hex::init {
void runCommandLine(int argc, char **argv);
}

static int runReadOnlyCLI(const std::vector<const char*> &args) {
int argc = static_cast<int>(args.size());
// const_cast is safe here because CLI layer doesn't mutate program name/args strings
auto argv = const_cast<char**>(reinterpret_cast<char* const*>(args.data()));
hex::init::runCommandLine(argc, argv);
return EXIT_SUCCESS;
}

TEST_SEQUENCE("ReadOnlyFlagSetsMode") {
// Simulate: imhex --readonly somefile
const char *prog = "imhex";
const char *flag = "--readonly";
const char *file = "dummy.bin";
const std::vector<const char*> argv = { prog, flag, file };

(void) runReadOnlyCLI(argv);

// Expect the global read-only mode to be enabled
TEST_ASSERT(hex::ImHexApi::System::isReadOnlyMode());

TEST_SUCCESS();
};