1
0
mirror of synced 2025-01-29 19:17:28 +01:00

impr: Refactored forwarder executable and add lots more information to it

This commit is contained in:
WerWolv 2023-09-27 14:14:27 +02:00
parent b3ef615158
commit e80c7bff1c
29 changed files with 208 additions and 134 deletions

View File

@ -54,7 +54,7 @@ addBundledLibraries()
# Add ImHex sources
add_subdirectory(lib/libimhex)
add_subdirectory(main)
add_custom_target(imhex_all ALL DEPENDS main libimhex)
add_custom_target(imhex_all ALL DEPENDS main main-forwarder libimhex)
# Add unit tests
enable_testing()

View File

@ -254,7 +254,7 @@ macro(createPackage)
else()
install(TARGETS main RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
if(WIN32) # Forwarder is only needed on Windows
install(TARGETS imhex-forwarder BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR})
install(TARGETS main-forwarder BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
endif()
@ -611,12 +611,12 @@ function(generatePDBs)
)
FetchContent_Populate(cv2pdb)
set(PDBS_TO_GENERATE main imhex-forwarder libimhex ${PLUGINS})
set(PDBS_TO_GENERATE main main-forwarder libimhex ${PLUGINS})
add_custom_target(pdbs)
foreach (PDB ${PDBS_TO_GENERATE})
if (PDB STREQUAL "main")
set(GENERATED_PDB imhex)
elseif (PDB STREQUAL "imhex-forwarder")
elseif (PDB STREQUAL "main-forwarder")
set(GENERATED_PDB imhex-gui)
elseif (PDB STREQUAL "libimhex")
set(GENERATED_PDB libimhex)

View File

@ -1,65 +1,10 @@
project(main)
add_executable(main ${APPLICATION_TYPE}
source/main.cpp
source/crash_handlers.cpp
source/window/window.cpp
source/window/win_window.cpp
source/window/macos_window.cpp
source/window/linux_window.cpp
source/messaging/common.cpp
source/messaging/linux.cpp
source/messaging/macos.cpp
source/messaging/win.cpp
source/init/splash_window.cpp
source/init/tasks.cpp
${IMHEX_ICON}
)
target_include_directories(main PUBLIC include)
setupCompilerFlags(main)
set(LIBROMFS_RESOURCE_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/romfs)
set(LIBROMFS_PROJECT_NAME imhex)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../lib/external/libromfs ${CMAKE_CURRENT_BINARY_DIR}/main/libromfs EXCLUDE_FROM_ALL)
set_target_properties(${LIBROMFS_LIBRARY} PROPERTIES POSITION_INDEPENDENT_CODE ON)
if (WIN32)
# HACK: `imhex` -> `imhex-gui` and we add a forwarder as `imhex`, so that the user can just run `imhex` and it will start the GUI
# Workaround for .NET plugin crashing caused by the console window being freed.
set(IMHEX_APPLICATION_NAME "imhex-gui")
add_executable(imhex-forwarder
source/forwarder/main.cpp
${IMHEX_ICON})
target_link_libraries(imhex-forwarder PRIVATE libwolv-io ${FMT_LIBRARIES})
set_target_properties(imhex-forwarder PROPERTIES
OUTPUT_NAME "imhex"
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/..
CXX_VISIBILITY_PRESET hidden
POSITION_INDEPENDENT_CODE ON)
set(IMHEX_APPLICATION_NAME "imhex-gui")
else ()
set(IMHEX_APPLICATION_NAME "imhex")
endif ()
set_target_properties(main PROPERTIES
OUTPUT_NAME ${IMHEX_APPLICATION_NAME}
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/..
CXX_VISIBILITY_PRESET hidden
POSITION_INDEPENDENT_CODE ON)
add_compile_definitions(IMHEX_PROJECT_NAME="${PROJECT_NAME}")
target_link_libraries(main PRIVATE libromfs-imhex libimhex ${FMT_LIBRARIES})
add_subdirectory(gui)
if (WIN32)
target_link_libraries(main PRIVATE usp10 wsock32 ws2_32 Dwmapi.lib)
else ()
target_link_libraries(main PRIVATE pthread)
endif ()
if (APPLE)
add_compile_definitions(GL_SILENCE_DEPRECATION)
endif ()
add_subdirectory(forwarder)
endif ()

View File

@ -0,0 +1,12 @@
project(main-forwarder)
add_executable(main-forwarder
source/main.cpp
${IMHEX_ICON}
)
target_link_libraries(main-forwarder PRIVATE libwolv-io ${FMT_LIBRARIES})
set_target_properties(main-forwarder PROPERTIES
OUTPUT_NAME "imhex"
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/..
CXX_VISIBILITY_PRESET hidden
POSITION_INDEPENDENT_CODE ON)

View File

@ -0,0 +1,88 @@
/**
* This is a simple forwarder that launches the main ImHex executable with the
* same command line as the current process. The reason for this is that even
* though ImHex is a GUI application in general, it also has a command line
* interface that can be used to perform various tasks. The issue with this is
* that kernel32 will automatically allocate a console for us which will float
* around in the background if we launch ImHex from the explorer. This forwarder
* will get rid of the console window if ImHex was launched from the explorer and
* enables ANSI escape sequences if ImHex was launched from the command line.
*
* The main reason this is done in a separate executable is because we use FreeConsole()
* to get rid of the console window. Due to bugs in older versions of Windows (Windows 10 and older)
* this will also close the process's standard handles (stdin, stdout, stderr) in
* a way that cannot be recovered from. This means that if we were to do this in the
* main application, if any code would try to interact with these handles (duplicate them
* or modify them in any way), the application would crash.
*
* None of this would be necessary if Windows had a third type of application (besides
* console and GUI) that would act like a console application but would not allocate
* a console window. This would allow us to have a single executable that would work
* the same as on all other platforms. There are plans to add this to Windows in the
* future, but it is not yet available. Also even if it was available, it would not
* be available on older versions of Windows, so we would still need this forwarder
*/
#include <windows.h>
#include <wolv/io/fs.hpp>
#include <fmt/format.h>
void handleConsoleWindow() {
HWND consoleWindow = ::GetConsoleWindow();
DWORD processId = 0;
::GetWindowThreadProcessId(consoleWindow, &processId);
// Check if ImHex was launched from the explorer or from the command line
if (::GetCurrentProcessId() == processId) {
// If it was launched from the explorer, kernel32 has allocated a console for us
// Get rid of it to avoid having a useless console window floating around
::FreeConsole();
} else {
// If it was launched from the command line, enable ANSI escape sequences to have colored output
auto hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE);
if (hConsole != INVALID_HANDLE_VALUE) {
DWORD mode = 0;
if (::GetConsoleMode(hConsole, &mode) == TRUE) {
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT;
::SetConsoleMode(hConsole, mode);
}
}
}
}
int launchExecutable() {
// Get the path of the main ImHex executable
auto executablePath = wolv::io::fs::getExecutablePath();
auto executableFullPath = executablePath->parent_path() / "imhex-gui.exe";
::PROCESS_INFORMATION process = { };
::STARTUPINFOW startupInfo = { };
startupInfo.cb = sizeof(STARTUPINFOW);
// Create a new process for imhex-gui.exe with the same command line as the current process
if (::CreateProcessW(executableFullPath.wstring().c_str(), ::GetCommandLineW(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &process) == FALSE) {
// Handle error if the process could not be created
auto errorCode = ::GetLastError();
auto errorMessageString = std::system_category().message(errorCode);
auto errorMessage = fmt::format("Failed to start ImHex:\n\nError code: 0x{:08X}\n\n{}", errorCode, errorMessageString);
::MessageBoxA(nullptr, errorMessage.c_str(), "ImHex Forwarder", MB_OK | MB_ICONERROR);
return EXIT_FAILURE;
}
::WaitForSingleObject(process.hProcess, INFINITE);
::CloseHandle(process.hProcess);
return EXIT_SUCCESS;
}
int main() {
handleConsoleWindow();
auto result = launchExecutable();
return result;
}

48
main/gui/CMakeLists.txt Normal file
View File

@ -0,0 +1,48 @@
project(main)
add_executable(main ${APPLICATION_TYPE}
source/main.cpp
source/crash_handlers.cpp
source/window/window.cpp
source/window/win_window.cpp
source/window/macos_window.cpp
source/window/linux_window.cpp
source/messaging/common.cpp
source/messaging/linux.cpp
source/messaging/macos.cpp
source/messaging/win.cpp
source/init/splash_window.cpp
source/init/tasks.cpp
${IMHEX_ICON}
)
target_include_directories(main PUBLIC include)
setupCompilerFlags(main)
set(LIBROMFS_RESOURCE_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/romfs)
set(LIBROMFS_PROJECT_NAME imhex)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../lib/external/libromfs ${CMAKE_CURRENT_BINARY_DIR}/main/gui/libromfs EXCLUDE_FROM_ALL)
set_target_properties(${LIBROMFS_LIBRARY} PROPERTIES POSITION_INDEPENDENT_CODE ON)
set_target_properties(main PROPERTIES
OUTPUT_NAME ${IMHEX_APPLICATION_NAME}
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/..
CXX_VISIBILITY_PRESET hidden
POSITION_INDEPENDENT_CODE ON)
add_compile_definitions(IMHEX_PROJECT_NAME="${PROJECT_NAME}")
target_link_libraries(main PRIVATE libromfs-imhex libimhex ${FMT_LIBRARIES})
if (WIN32)
target_link_libraries(main PRIVATE usp10 wsock32 ws2_32 Dwmapi.lib)
else ()
target_link_libraries(main PRIVATE pthread)
endif ()
if (APPLE)
add_compile_definitions(GL_SILENCE_DEPRECATION)
endif ()

View File

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 157 KiB

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

View File

@ -57,11 +57,15 @@ namespace hex::init {
std::future<bool> WindowSplash::processTasksAsync() {
return std::async(std::launch::async, [this] {
bool status = true;
std::atomic<u32> tasksCompleted = 0;
// Loop over all registered init tasks
for (const auto &[name, task, async] : this->m_tasks) {
// Construct a new task callback
auto runTask = [&, task = task, name = name] {
try {
// Save an iterator to the current task name
decltype(this->m_currTaskNames)::iterator taskNameIter;
{
std::lock_guard guard(this->m_progressMutex);
@ -69,32 +73,43 @@ namespace hex::init {
taskNameIter = std::prev(this->m_currTaskNames.end());
}
// When the task finished, increment the progress bar
ON_SCOPE_EXIT {
tasksCompleted++;
this->m_progress = float(tasksCompleted) / this->m_tasks.size();
};
// Execute the actual task and track the amount of time it took to run
auto startTime = std::chrono::high_resolution_clock::now();
bool taskStatus = task();
auto endTime = std::chrono::high_resolution_clock::now();
log::info("Task '{}' finished in {} ms (success={})", name, std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count(), taskStatus);
log::info("Task '{}' finished {} in {} ms",
name,
taskStatus ? "successfully" : "unsuccessfully",
std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count()
);
// Track the overall status of the tasks
status = status && taskStatus;
// Erase the task name from the list of running tasks
{
std::lock_guard guard(this->m_progressMutex);
this->m_currTaskNames.erase(taskNameIter);
}
} catch (std::exception &e) {
} catch (const std::exception &e) {
log::error("Init task '{}' threw an exception: {}", name, e.what());
status = false;
} catch (...) {
log::error("Init task '{}' threw an unidentifiable exception", name);
status = false;
}
};
// If the task can be run asynchronously, run it in a separate thread
// otherwise run it in this thread and wait for it to finish
if (async) {
std::thread([runTask]{ runTask(); }).detach();
} else {
@ -102,6 +117,7 @@ namespace hex::init {
}
}
// Check every 100ms if all tasks have run
while (tasksCompleted < this->m_tasks.size()) {
std::this_thread::sleep_for(100ms);
}
@ -127,10 +143,14 @@ namespace hex::init {
auto drawList = ImGui::GetBackgroundDrawList();
{
// Draw the splash screen background
drawList->AddImage(this->splashBackgroundTexture, ImVec2(0, 0), this->splashBackgroundTexture.getSize() * scale);
{
// Function to highlight a given number of bytes at a position in the splash screen
const auto highlightBytes = [&](ImVec2 start, size_t count, ImColor color, float opacity) {
// Dimensions and number of bytes that are drawn. Taken from the splash screen image
const auto hexSize = ImVec2(29, 18) * scale;
const auto hexSpacing = ImVec2(17.4, 15) * scale;
const auto hexStart = ImVec2(27, 127) * scale;
@ -141,22 +161,29 @@ namespace hex::init {
color.Value.w *= opacity;
// Loop over all the bytes on the splash screen
for (u32 y = u32(start.y); y < u32(hexCount.y); y += 1) {
for (u32 x = u32(start.x); x < u32(hexCount.x); x += 1) {
if (count-- == 0)
return;
// Find the start position of the byte to draw
auto pos = hexStart + ImVec2(float(x), float(y)) * (hexSize + hexSpacing);
// Fill the rectangle in the byte with the given color
drawList->AddRectFilled(pos + ImVec2(0, -hexSpacing.y / 2), pos + hexSize + ImVec2(0, hexSpacing.y / 2), color);
// Add some extra color on the right if the current byte isn't the last byte, and we didn't reach the right side of the image
if (count > 0 && x != u32(hexCount.x) - 1)
drawList->AddRectFilled(pos + ImVec2(hexSize.x, -hexSpacing.y / 2), pos + hexSize + ImVec2(hexSpacing.x, hexSpacing.y / 2), color);
// Add some extra color on the left if this is the first byte we're highlighting
if (isStart) {
isStart = false;
drawList->AddRectFilled(pos - hexSpacing / 2, pos + ImVec2(0, hexSize.y + hexSpacing.y / 2), color);
}
// Add some extra color on the right if this is the last byte
if (count == 0) {
drawList->AddRectFilled(pos + ImVec2(hexSize.x, -hexSpacing.y / 2), pos + hexSize + hexSpacing / 2, color);
}
@ -166,16 +193,21 @@ namespace hex::init {
}
};
// Draw all highlights, slowly fading them in as the init tasks progress
for (const auto &highlight : this->highlights)
highlightBytes(highlight.start, highlight.count, highlight.color, this->progressLerp);
}
this->progressLerp += (this->m_progress - this->progressLerp) * 0.1F;
// Draw the splash screen foreground
drawList->AddImage(this->splashTextTexture, ImVec2(0, 0), this->splashTextTexture.getSize() * scale);
// Draw the "copyright" notice
drawList->AddText(ImVec2(35, 85) * scale, ImColor(0xFF, 0xFF, 0xFF, 0xFF), hex::format("WerWolv\n2020 - {0}", &__DATE__[7]).c_str());
// Draw version information
// In debug builds, also display the current commit hash and branch
#if defined(DEBUG)
const static auto VersionInfo = hex::format("{0} : {1} {2}@{3}", ImHexApi::System::getImHexVersion(), ICON_FA_CODE_BRANCH, ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash());
#else
@ -194,8 +226,11 @@ namespace hex::init {
const auto progressStart = progressBackgroundStart + ImVec2(0, 20) * scale;
const auto progressSize = ImVec2(progressBackgroundSize.x * this->m_progress, 10 * scale);
// Draw progress bar
drawList->AddRectFilled(progressStart, progressStart + progressSize, 0xD0FFFFFF);
// Draw task names separated by | characters
if (!this->m_currTaskNames.empty()) {
drawList->PushClipRect(progressBackgroundStart, progressBackgroundStart + progressBackgroundSize, true);
drawList->AddText(progressStart + ImVec2(5, -20) * scale, ImColor(0xFF, 0xFF, 0xFF, 0xFF), hex::format("{}", fmt::join(this->m_currTaskNames, " | ")).c_str());
@ -424,7 +459,7 @@ namespace hex::init {
}
void WindowSplash::startStartupTasks() {
// Launch init tasks in background
// Launch init tasks in the background
this->tasksSucceeded = processTasksAsync();
}

View File

@ -12,7 +12,7 @@ namespace hex::messaging {
void sendToOtherInstance(const std::string &eventName, const std::vector<u8> &args) {
hex::unused(eventName);
hex::unused(args);
log::error("Not implemented function sendToOtherInstance() called");
log::error("Unimplemented function 'sendToOtherInstance()' called");
}
// Not implemented, so lets say we are the main instance every time so events are forwarded to ourselves

View File

@ -12,7 +12,7 @@ namespace hex::messaging {
void sendToOtherInstance(const std::string &eventName, const std::vector<u8> &args) {
hex::unused(eventName);
hex::unused(args);
log::error("Not implemented function sendToOtherInstance() called");
log::error("Unimplemented function 'sendToOtherInstance()' called");
}
// Not implemented, so lets say we are the main instance every time so events are forwarded to ourselves

View File

@ -1,54 +0,0 @@
#include <windows.h>
#include <wolv/io/fs.hpp>
int main() {
HWND consoleWindow = ::GetConsoleWindow();
DWORD processId = 0;
::GetWindowThreadProcessId(consoleWindow, &processId);
if (GetCurrentProcessId() == processId) {
FreeConsole();
} else {
auto hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE);
if (hConsole != INVALID_HANDLE_VALUE) {
DWORD mode = 0;
if (::GetConsoleMode(hConsole, &mode) == TRUE) {
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT;
::SetConsoleMode(hConsole, mode);
}
}
}
auto executablePath = wolv::io::fs::getExecutablePath();
auto executableFullPath = executablePath->parent_path() / "imhex-gui.exe";
PROCESS_INFORMATION process;
STARTUPINFOW startupInfo;
ZeroMemory(&process, sizeof(PROCESS_INFORMATION));
ZeroMemory(&startupInfo, sizeof(STARTUPINFOW));
startupInfo.cb = sizeof(STARTUPINFOW);
if (CreateProcessW(executableFullPath.wstring().c_str(), GetCommandLineW(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startupInfo, &process) == FALSE) {
auto error = GetLastError();
wchar_t errorMessageString[1024];
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, error, 0, errorMessageString, 1024, nullptr);
std::wstring errorMessage = L"Failed to start ImHex:\n\nError code: ";
// Format error code to have 8 digits hex
wchar_t errorCodeString[11];
swprintf_s(errorCodeString, 11, L"0x%08X", error);
errorMessage += errorCodeString;
errorMessage += L"\n\n";
errorMessage += errorMessageString;
MessageBoxW(nullptr, errorMessage.c_str(), L"ImHex Forwarder", MB_OK | MB_ICONERROR);
return 1;
}
WaitForSingleObject(process.hProcess, INFINITE);
CloseHandle(process.hProcess);
return 0;
}

View File

@ -50,18 +50,18 @@ namespace hex::plugin::builtin {
}
IMHEX_PLUGIN_SUBCOMMANDS() {
{ "help", "Print help about this command", hex::plugin::builtin::handleHelpCommand },
{ "version", "Print ImHex version", hex::plugin::builtin::handleVersionCommand },
{ "plugins", "Lists all plugins that have been installed", hex::plugin::builtin::handlePluginsCommand },
{ "help", "Print help about this command", hex::plugin::builtin::handleHelpCommand },
{ "version", "Print ImHex version", hex::plugin::builtin::handleVersionCommand },
{ "plugins", "Lists all plugins that have been installed", hex::plugin::builtin::handlePluginsCommand },
{ "open", "Open files passed as argument. [default]", hex::plugin::builtin::handleOpenCommand },
{ "open", "Open files passed as argument. [default]", hex::plugin::builtin::handleOpenCommand },
{ "calc", "Evaluate a mathematical expression", hex::plugin::builtin::handleCalcCommand },
{ "hash", "Calculate the hash of a file", hex::plugin::builtin::handleHashCommand },
{ "encode", "Encode a string", hex::plugin::builtin::handleEncodeCommand },
{ "decode", "Decode a string", hex::plugin::builtin::handleDecodeCommand },
{ "magic", "Identify file types", hex::plugin::builtin::handleMagicCommand },
{ "pl", "Interact with the pattern language", hex::plugin::builtin::handlePatternLanguageCommand },
{ "calc", "Evaluate a mathematical expression", hex::plugin::builtin::handleCalcCommand },
{ "hash", "Calculate the hash of a file", hex::plugin::builtin::handleHashCommand },
{ "encode", "Encode a string", hex::plugin::builtin::handleEncodeCommand },
{ "decode", "Decode a string", hex::plugin::builtin::handleDecodeCommand },
{ "magic", "Identify file types", hex::plugin::builtin::handleMagicCommand },
{ "pl", "Interact with the pattern language", hex::plugin::builtin::handlePatternLanguageCommand },
};
IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") {