feat: Added Auto Updater for Windows, macOS and Ubuntu (#1377)
This commit is contained in:
parent
c46e445a04
commit
f114239f51
@ -98,7 +98,7 @@ macro(configurePackingResources)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(APPLICATION_TYPE)
|
set(APPLICATION_TYPE WIN32)
|
||||||
set(IMHEX_ICON "${IMHEX_BASE_FOLDER}/resources/resource.rc")
|
set(IMHEX_ICON "${IMHEX_BASE_FOLDER}/resources/resource.rc")
|
||||||
|
|
||||||
if (CREATE_PACKAGE)
|
if (CREATE_PACKAGE)
|
||||||
|
@ -536,6 +536,13 @@ namespace hex {
|
|||||||
* @return Git commit branch
|
* @return Git commit branch
|
||||||
*/
|
*/
|
||||||
std::string getCommitBranch();
|
std::string getCommitBranch();
|
||||||
|
|
||||||
|
enum class UpdateType {
|
||||||
|
Stable,
|
||||||
|
Nightly
|
||||||
|
};
|
||||||
|
|
||||||
|
bool updateImHex(UpdateType updateType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,15 +11,4 @@ namespace hex {
|
|||||||
return fmt::format(fmt::runtime(format), args...);
|
return fmt::format(fmt::runtime(format), args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
inline void print(std::string_view format, Args... args) {
|
|
||||||
fmt::print(fmt::runtime(format), args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
inline void println(std::string_view format, Args... args) {
|
|
||||||
fmt::print(fmt::runtime(format), args...);
|
|
||||||
fmt::print("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -19,6 +19,7 @@ namespace hex::log {
|
|||||||
wolv::io::File& getFile();
|
wolv::io::File& getFile();
|
||||||
bool isRedirected();
|
bool isRedirected();
|
||||||
[[maybe_unused]] void redirectToFile();
|
[[maybe_unused]] void redirectToFile();
|
||||||
|
[[maybe_unused]] void enableColorPrinting();
|
||||||
|
|
||||||
extern std::mutex g_loggerMutex;
|
extern std::mutex g_loggerMutex;
|
||||||
|
|
||||||
@ -50,8 +51,7 @@ namespace hex::log {
|
|||||||
fmt::print(dest, "{}", std::string(ProjectNameLength > 10 ? 0 : 10 - ProjectNameLength, ' '));
|
fmt::print(dest, "{}", std::string(ProjectNameLength > 10 ? 0 : 10 - ProjectNameLength, ' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename... T>
|
[[maybe_unused]] void print(const fmt::text_style &ts, const std::string &level, const std::string &fmt, auto && ... args) {
|
||||||
[[maybe_unused]] void print(const fmt::text_style &ts, const std::string &level, const std::string &fmt, auto... args) {
|
|
||||||
std::scoped_lock lock(impl::g_loggerMutex);
|
std::scoped_lock lock(impl::g_loggerMutex);
|
||||||
|
|
||||||
auto dest = impl::getDestination();
|
auto dest = impl::getDestination();
|
||||||
@ -90,4 +90,23 @@ namespace hex::log {
|
|||||||
hex::log::print(fg(fmt::color::purple) | fmt::emphasis::bold, "[FATAL]", fmt, args...);
|
hex::log::print(fg(fmt::color::purple) | fmt::emphasis::bold, "[FATAL]", fmt, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[maybe_unused]] void print(const std::string &fmt, auto && ... args) {
|
||||||
|
std::scoped_lock lock(impl::g_loggerMutex);
|
||||||
|
|
||||||
|
auto dest = impl::getDestination();
|
||||||
|
auto message = fmt::format(fmt::runtime(fmt), args...);
|
||||||
|
fmt::print(dest, "{}", message);
|
||||||
|
fflush(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]] void println(const std::string &fmt, auto && ... args) {
|
||||||
|
std::scoped_lock lock(impl::g_loggerMutex);
|
||||||
|
|
||||||
|
auto dest = impl::getDestination();
|
||||||
|
auto message = fmt::format(fmt::runtime(fmt), args...);
|
||||||
|
fmt::print(dest, "{}\n", message);
|
||||||
|
fflush(dest);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -59,7 +59,8 @@ namespace hex {
|
|||||||
[[nodiscard]] std::string toByteString(u64 bytes);
|
[[nodiscard]] std::string toByteString(u64 bytes);
|
||||||
[[nodiscard]] std::string makePrintable(u8 c);
|
[[nodiscard]] std::string makePrintable(u8 c);
|
||||||
|
|
||||||
void runCommand(const std::string &command);
|
void startProgram(const std::string &command);
|
||||||
|
int executeCommand(const std::string &command);
|
||||||
void openWebpage(std::string url);
|
void openWebpage(std::string url);
|
||||||
|
|
||||||
[[nodiscard]] std::string encodeByteString(const std::vector<u8> &bytes);
|
[[nodiscard]] std::string encodeByteString(const std::vector<u8> &bytes);
|
||||||
|
@ -5,8 +5,10 @@
|
|||||||
#include <hex/api/task.hpp>
|
#include <hex/api/task.hpp>
|
||||||
#include <hex/providers/provider.hpp>
|
#include <hex/providers/provider.hpp>
|
||||||
#include <hex/helpers/fmt.hpp>
|
#include <hex/helpers/fmt.hpp>
|
||||||
|
#include <hex/helpers/utils.hpp>
|
||||||
|
|
||||||
#include <wolv/io/file.hpp>
|
#include <wolv/io/file.hpp>
|
||||||
|
#include <wolv/utils/string.hpp>
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@ -639,6 +641,44 @@ namespace hex {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool updateImHex(UpdateType updateType) {
|
||||||
|
// Get the path of the updater executable
|
||||||
|
std::fs::path executablePath;
|
||||||
|
|
||||||
|
for (const auto &entry : std::fs::directory_iterator(wolv::io::fs::getExecutablePath()->parent_path())) {
|
||||||
|
if (entry.path().filename().string().starts_with("imhex-updater")) {
|
||||||
|
executablePath = entry.path();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (executablePath.empty() || !wolv::io::fs::exists(executablePath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string updateTypeString;
|
||||||
|
switch (updateType) {
|
||||||
|
case UpdateType::Stable:
|
||||||
|
updateTypeString = "latest";
|
||||||
|
break;
|
||||||
|
case UpdateType::Nightly:
|
||||||
|
updateTypeString = "nightly";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventManager::subscribe<EventImHexClosing>([executablePath, updateTypeString] {
|
||||||
|
hex::executeCommand(
|
||||||
|
hex::format("{} {}",
|
||||||
|
wolv::util::toUTF8String(executablePath),
|
||||||
|
updateTypeString
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ImHexApi::System::closeImHex();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ImHexApi::Messaging {
|
namespace ImHexApi::Messaging {
|
||||||
|
@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
#include <wolv/io/file.hpp>
|
#include <wolv/io/file.hpp>
|
||||||
|
|
||||||
|
#if defined(OS_WINDOWS)
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace hex::log::impl {
|
namespace hex::log::impl {
|
||||||
|
|
||||||
static wolv::io::File s_loggerFile;
|
static wolv::io::File s_loggerFile;
|
||||||
@ -36,6 +40,19 @@ namespace hex::log::impl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void enableColorPrinting() {
|
||||||
|
#if defined(OS_WINDOWS)
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<LogEntry>& getLogEntries() {
|
std::vector<LogEntry>& getLogEntries() {
|
||||||
static std::vector<LogEntry> logEntries;
|
static std::vector<LogEntry> logEntries;
|
||||||
|
@ -313,7 +313,7 @@ namespace hex {
|
|||||||
return std::to_string(value).substr(0, 5) + Suffixes[suffixIndex];
|
return std::to_string(value).substr(0, 5) + Suffixes[suffixIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
void runCommand(const std::string &command) {
|
void startProgram(const std::string &command) {
|
||||||
|
|
||||||
#if defined(OS_WINDOWS)
|
#if defined(OS_WINDOWS)
|
||||||
hex::unused(system(hex::format("start {0}", command).c_str()));
|
hex::unused(system(hex::format("start {0}", command).c_str()));
|
||||||
@ -326,6 +326,10 @@ namespace hex {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int executeCommand(const std::string &command) {
|
||||||
|
return ::system(command.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
void openWebpage(std::string url) {
|
void openWebpage(std::string url) {
|
||||||
if (!url.contains("://"))
|
if (!url.contains("://"))
|
||||||
url = "https://" + url;
|
url = "https://" + url;
|
||||||
|
@ -8,3 +8,7 @@ add_subdirectory(gui)
|
|||||||
if (WIN32)
|
if (WIN32)
|
||||||
add_subdirectory(forwarder)
|
add_subdirectory(forwarder)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
if (NOT EMSCRIPTEN)
|
||||||
|
add_subdirectory(updater)
|
||||||
|
endif ()
|
@ -28,8 +28,11 @@
|
|||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
void handleConsoleWindow() {
|
void setupConsoleWindow() {
|
||||||
|
// Get the handle of the console window
|
||||||
HWND consoleWindow = ::GetConsoleWindow();
|
HWND consoleWindow = ::GetConsoleWindow();
|
||||||
|
|
||||||
|
// Get console process ID
|
||||||
DWORD processId = 0;
|
DWORD processId = 0;
|
||||||
::GetWindowThreadProcessId(consoleWindow, &processId);
|
::GetWindowThreadProcessId(consoleWindow, &processId);
|
||||||
|
|
||||||
@ -48,6 +51,11 @@ void handleConsoleWindow() {
|
|||||||
::SetConsoleMode(hConsole, mode);
|
::SetConsoleMode(hConsole, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the __IMHEX_FORWARD_CONSOLE__ environment variable,
|
||||||
|
// to let ImHex know that it was launched from the forwarder
|
||||||
|
// and that it should forward it's console output to us
|
||||||
|
::SetEnvironmentVariableA("__IMHEX_FORWARD_CONSOLE__", "1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,23 +65,26 @@ int launchExecutable() {
|
|||||||
auto executableFullPath = executablePath->parent_path() / "imhex-gui.exe";
|
auto executableFullPath = executablePath->parent_path() / "imhex-gui.exe";
|
||||||
|
|
||||||
::PROCESS_INFORMATION process = { };
|
::PROCESS_INFORMATION process = { };
|
||||||
::STARTUPINFOW startupInfo = { };
|
::STARTUPINFOW startupInfo = { .cb = sizeof(::STARTUPINFOW) };
|
||||||
startupInfo.cb = sizeof(STARTUPINFOW);
|
|
||||||
|
|
||||||
// Create a new process for imhex-gui.exe with the same command line as the current process
|
// 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) {
|
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
|
// Handle error if the process could not be created
|
||||||
|
|
||||||
|
// Get formatted error message from the OS
|
||||||
auto errorCode = ::GetLastError();
|
auto errorCode = ::GetLastError();
|
||||||
auto errorMessageString = std::system_category().message(errorCode);
|
auto errorMessageString = std::system_category().message(errorCode);
|
||||||
|
|
||||||
|
// Generate error message
|
||||||
auto errorMessage = fmt::format("Failed to start ImHex:\n\nError code: 0x{:08X}\n\n{}", errorCode, errorMessageString);
|
auto errorMessage = fmt::format("Failed to start ImHex:\n\nError code: 0x{:08X}\n\n{}", errorCode, errorMessageString);
|
||||||
|
|
||||||
|
// Display a message box with the error
|
||||||
::MessageBoxA(nullptr, errorMessage.c_str(), "ImHex Forwarder", MB_OK | MB_ICONERROR);
|
::MessageBoxA(nullptr, errorMessage.c_str(), "ImHex Forwarder", MB_OK | MB_ICONERROR);
|
||||||
|
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the main ImHex process to exit
|
||||||
::WaitForSingleObject(process.hProcess, INFINITE);
|
::WaitForSingleObject(process.hProcess, INFINITE);
|
||||||
::CloseHandle(process.hProcess);
|
::CloseHandle(process.hProcess);
|
||||||
|
|
||||||
@ -81,8 +92,7 @@ int launchExecutable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
handleConsoleWindow();
|
setupConsoleWindow();
|
||||||
auto result = launchExecutable();
|
|
||||||
|
|
||||||
return result;
|
return launchExecutable();
|
||||||
}
|
}
|
@ -32,7 +32,7 @@ namespace hex::crash {
|
|||||||
void resetCrashHandlers();
|
void resetCrashHandlers();
|
||||||
|
|
||||||
static void sendNativeMessage(const std::string& message) {
|
static void sendNativeMessage(const std::string& message) {
|
||||||
hex::nativeErrorMessage(hex::format("ImHex crashed during its loading.\nError: {}", message));
|
hex::nativeErrorMessage(hex::format("ImHex crashed during initial setup!\nError: {}", message));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function that decides what should happen on a crash
|
// Function that decides what should happen on a crash
|
||||||
@ -136,15 +136,14 @@ namespace hex::crash {
|
|||||||
// Reset crash handlers, so we can't have a recursion if this code crashes
|
// Reset crash handlers, so we can't have a recursion if this code crashes
|
||||||
resetCrashHandlers();
|
resetCrashHandlers();
|
||||||
|
|
||||||
handleCrash("Uncaught exception!");
|
|
||||||
|
|
||||||
// Print the current exception info
|
// Print the current exception info
|
||||||
try {
|
try {
|
||||||
std::rethrow_exception(std::current_exception());
|
std::rethrow_exception(std::current_exception());
|
||||||
} catch (std::exception &ex) {
|
} catch (std::exception &ex) {
|
||||||
std::string exceptionStr = hex::format("{}()::what() -> {}", llvm::itaniumDemangle(typeid(ex).name(), nullptr, nullptr, nullptr), ex.what());
|
std::string exceptionStr = hex::format("{}()::what() -> {}", llvm::itaniumDemangle(typeid(ex).name(), nullptr, nullptr, nullptr), ex.what());
|
||||||
log::fatal("Program terminated with uncaught exception: {}", exceptionStr);
|
|
||||||
|
|
||||||
|
handleCrash(exceptionStr);
|
||||||
|
log::fatal("Program terminated with uncaught exception: {}", exceptionStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerSafeShutdown();
|
triggerSafeShutdown();
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include <wolv/io/fs.hpp>
|
#include <wolv/io/fs.hpp>
|
||||||
#include <wolv/utils/guards.hpp>
|
#include <wolv/utils/guards.hpp>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
#if defined(OS_WEB)
|
#if defined(OS_WEB)
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include <windowsx.h>
|
#include <windowsx.h>
|
||||||
#include <shobjidl.h>
|
#include <shobjidl.h>
|
||||||
#include <wrl/client.h>
|
#include <wrl/client.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@ -231,22 +232,52 @@ namespace hex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void reopenConsoleHandle(u32 stdHandleNumber, i32 stdFileDescriptor, FILE *stdStream) {
|
||||||
|
// Get the Windows handle for the standard stream
|
||||||
|
HANDLE handle = ::GetStdHandle(stdHandleNumber);
|
||||||
|
|
||||||
|
// Redirect the standard stream to the relevant console stream
|
||||||
|
if (stdFileDescriptor == STDIN_FILENO)
|
||||||
|
freopen("CONIN$", "rt", stdStream);
|
||||||
|
else
|
||||||
|
freopen("CONOUT$", "wt", stdStream);
|
||||||
|
|
||||||
|
// Disable buffering
|
||||||
|
setvbuf(stdStream, nullptr, _IONBF, 0);
|
||||||
|
|
||||||
|
// Reopen the standard stream as a file descriptor
|
||||||
|
auto unboundFd = [stdFileDescriptor, handle]{
|
||||||
|
if (stdFileDescriptor == STDIN_FILENO)
|
||||||
|
return _open_osfhandle(intptr_t(handle), _O_RDONLY);
|
||||||
|
else
|
||||||
|
return _open_osfhandle(intptr_t(handle), _O_WRONLY);
|
||||||
|
}();
|
||||||
|
|
||||||
|
// Duplicate the file descriptor to the standard stream
|
||||||
|
dup2(unboundFd, stdFileDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Window::initNative() {
|
void Window::initNative() {
|
||||||
HWND consoleWindow = ::GetConsoleWindow();
|
// Check for the __IMHEX_FORWARD_CONSOLE__ environment variable that was set by the forwarder application
|
||||||
DWORD processId = 0;
|
// If it's present attach to its console window
|
||||||
::GetWindowThreadProcessId(consoleWindow, &processId);
|
if (hex::getEnvironmentVariable("__IMHEX_FORWARD_CONSOLE__") == "1") {
|
||||||
if (GetCurrentProcessId() == processId) {
|
::AttachConsole(ATTACH_PARENT_PROCESS);
|
||||||
ShowWindow(consoleWindow, SW_HIDE);
|
|
||||||
log::impl::redirectToFile();
|
// Reopen stdin, stdout and stderr to the console
|
||||||
|
reopenConsoleHandle(STD_INPUT_HANDLE, STDIN_FILENO, stdin);
|
||||||
|
reopenConsoleHandle(STD_OUTPUT_HANDLE, STDOUT_FILENO, stdout);
|
||||||
|
|
||||||
|
// Explicitly don't forward stderr because some libraries like to write to it
|
||||||
|
// with no way to disable it (e.g., libmagic)
|
||||||
|
/*
|
||||||
|
reopenConsoleHandle(STD_ERROR_HANDLE, STDERR_FILENO, stderr);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Enable ANSI colors in the console
|
||||||
|
log::impl::enableColorPrinting();
|
||||||
} else {
|
} else {
|
||||||
auto hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE);
|
log::impl::redirectToFile();
|
||||||
if (hConsole != INVALID_HANDLE_VALUE) {
|
|
||||||
DWORD mode = 0;
|
|
||||||
if (::GetConsoleMode(hConsole, &mode) == TRUE) {
|
|
||||||
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT;
|
|
||||||
::SetConsoleMode(hConsole, mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImHexApi::System::impl::setBorderlessWindowMode(true);
|
ImHexApi::System::impl::setBorderlessWindowMode(true);
|
||||||
@ -256,11 +287,6 @@ namespace hex {
|
|||||||
if (std::fs::exists(path))
|
if (std::fs::exists(path))
|
||||||
AddDllDirectory(path.c_str());
|
AddDllDirectory(path.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Various libraries sadly directly print to stderr with no way to disable it
|
|
||||||
// We redirect stderr to NUL to prevent this
|
|
||||||
freopen("NUL:", "w", stderr);
|
|
||||||
setvbuf(stderr, nullptr, _IONBF, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::setupNativeWindow() {
|
void Window::setupNativeWindow() {
|
||||||
|
15
main/updater/CMakeLists.txt
Normal file
15
main/updater/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
project(updater)
|
||||||
|
|
||||||
|
add_executable(updater ${APPLICATION_TYPE}
|
||||||
|
source/main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_compile_definitions(IMHEX_PROJECT_NAME="${PROJECT_NAME}")
|
||||||
|
target_link_libraries(updater PRIVATE libimhex libwolv-io ${FMT_LIBRARIES})
|
||||||
|
add_dependencies(imhex_all updater)
|
||||||
|
set_target_properties(updater PROPERTIES
|
||||||
|
OUTPUT_NAME "imhex-updater"
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/../..
|
||||||
|
CXX_VISIBILITY_PRESET hidden
|
||||||
|
POSITION_INDEPENDENT_CODE ON
|
||||||
|
)
|
167
main/updater/source/main.cpp
Normal file
167
main/updater/source/main.cpp
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
#include <hex/api_urls.hpp>
|
||||||
|
#include <hex/api/imhex_api.hpp>
|
||||||
|
|
||||||
|
#include <hex/helpers/http_requests.hpp>
|
||||||
|
#include <hex/helpers/utils.hpp>
|
||||||
|
|
||||||
|
using namespace std::literals::string_literals;
|
||||||
|
|
||||||
|
std::string getUpdateUrl(std::string_view versionType, std::string_view operatingSystem) {
|
||||||
|
// Get the latest version info from the ImHex API
|
||||||
|
auto response = hex::HttpRequest("GET",
|
||||||
|
ImHexApiURL + fmt::format("/update/{}/{}",
|
||||||
|
versionType,
|
||||||
|
operatingSystem
|
||||||
|
)
|
||||||
|
).execute().get();
|
||||||
|
|
||||||
|
const auto &data = response.getData();
|
||||||
|
|
||||||
|
// Make sure we got a valid response
|
||||||
|
if (!response.isSuccess()) {
|
||||||
|
hex::log::error("Failed to get latest version info: ({}) {}", response.getStatusCode(), data);
|
||||||
|
|
||||||
|
return { };
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::fs::path> downloadUpdate(const std::string &url, const std::string &type) {
|
||||||
|
// Download the update
|
||||||
|
auto response = hex::HttpRequest("GET", url).downloadFile().get();
|
||||||
|
|
||||||
|
// Make sure we got a valid response
|
||||||
|
if (!response.isSuccess()) {
|
||||||
|
hex::log::error("Failed to download update");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &data = response.getData();
|
||||||
|
|
||||||
|
// Write the update to a file
|
||||||
|
std::fs::path filePath;
|
||||||
|
{
|
||||||
|
constexpr static auto UpdateFileName = "update.hexupd";
|
||||||
|
|
||||||
|
// Loop over all available paths
|
||||||
|
wolv::io::File file;
|
||||||
|
for (const auto &path : hex::fs::getDefaultPaths(hex::fs::ImHexPath::Config)) {
|
||||||
|
// Remove any existing update files
|
||||||
|
wolv::io::fs::remove(path / UpdateFileName);
|
||||||
|
|
||||||
|
// If a valid location hasn't been found already, try to create a new file
|
||||||
|
if (!file.isValid())
|
||||||
|
file = wolv::io::File(path / UpdateFileName, wolv::io::File::Mode::Create);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the update data can't be written to any of the default paths, the update cannot continue
|
||||||
|
if (!file.isValid()) {
|
||||||
|
hex::log::error("Failed to create update file");
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the downloaded update data to the file
|
||||||
|
file.writeVector(data);
|
||||||
|
|
||||||
|
// Save the path to the update file
|
||||||
|
filePath = file.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getUpdateType() {
|
||||||
|
#if defined (OS_WINDOWS)
|
||||||
|
if (!hex::ImHexApi::System::isPortableVersion())
|
||||||
|
return "win-msi";
|
||||||
|
#elif defined (OS_MACOS)
|
||||||
|
return "mac-dmg";
|
||||||
|
#elif defined (OS_LINUX)
|
||||||
|
if (hex::executeCommand("lsb_release -a | grep Ubuntu") == 0) {
|
||||||
|
if (hex::executeCommand("lsb_release -a | grep 22.") == 0)
|
||||||
|
return "linux-deb-22.04";
|
||||||
|
else if (hex::executeCommand("lsb_release -a | grep 23.") == 0)
|
||||||
|
return "linux-deb-23.04";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int installUpdate(const std::string &type, std::fs::path updatePath) {
|
||||||
|
struct UpdateHandler {
|
||||||
|
const char *type;
|
||||||
|
const char *extension;
|
||||||
|
const char *command;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr static auto UpdateHandlers = {
|
||||||
|
UpdateHandler { "win-msi", ".msi", "msiexec /fa {} /passive" },
|
||||||
|
UpdateHandler { "macos-dmg", ".dmg", "hdiutil attach {}" },
|
||||||
|
UpdateHandler { "linux-deb-22.04", ".deb", "sudo apt update && sudo apt install -y --fix-broken {}" },
|
||||||
|
UpdateHandler { "linux-deb-23.04", ".deb", "sudo apt update && sudo apt install -y --fix-broken {}" },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &handler : UpdateHandlers) {
|
||||||
|
if (type == handler.type) {
|
||||||
|
// Rename the update file to the correct extension
|
||||||
|
updatePath.replace_extension(handler.extension);
|
||||||
|
std::fs::rename(updatePath, updatePath);
|
||||||
|
|
||||||
|
// Install the update using the correct command
|
||||||
|
hex::startProgram(hex::format(handler.command, updatePath.string()));
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the installation type isn't handled here, the detected installation type doesn't support updates through the updater
|
||||||
|
hex::log::error("Install type cannot be updated");
|
||||||
|
|
||||||
|
// Open the latest release page in the default browser to allow the user to manually update
|
||||||
|
hex::openWebpage("https://github.com/WerWolv/ImHex/releases/latest");
|
||||||
|
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
hex::log::impl::enableColorPrinting();
|
||||||
|
|
||||||
|
// Check we have the correct number of arguments
|
||||||
|
if (argc != 2) {
|
||||||
|
hex::log::error("Failed to start updater: Invalid arguments");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the version type from the arguments
|
||||||
|
const auto versionType = argv[1];
|
||||||
|
hex::log::info("Updater started with version type: {}", versionType);
|
||||||
|
|
||||||
|
// Query the update type
|
||||||
|
const auto updateType = getUpdateType();
|
||||||
|
hex::log::info("Detected OS String: {}", updateType);
|
||||||
|
|
||||||
|
// Make sure we got a valid update type
|
||||||
|
if (updateType.empty()) {
|
||||||
|
hex::log::error("Failed to detect installation type");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the url to the requested update from the ImHex API
|
||||||
|
const auto updateUrl = getUpdateUrl(versionType, updateType);
|
||||||
|
if (updateUrl.empty()) {
|
||||||
|
hex::log::error("Failed to get update URL");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
hex::log::info("Update URL found: {}", updateUrl);
|
||||||
|
|
||||||
|
// Download the update file
|
||||||
|
auto updatePath = downloadUpdate(updateUrl, updateType);
|
||||||
|
if (!updatePath.has_value())
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
// Install the update
|
||||||
|
return installUpdate(updateType, *updatePath);
|
||||||
|
}
|
@ -26,7 +26,7 @@ namespace hex::plugin::builtin {
|
|||||||
void handleVersionCommand(const std::vector<std::string> &args) {
|
void handleVersionCommand(const std::vector<std::string> &args) {
|
||||||
hex::unused(args);
|
hex::unused(args);
|
||||||
|
|
||||||
hex::print(romfs::get("logo.ans").string(),
|
hex::log::print(std::string(romfs::get("logo.ans").string()),
|
||||||
ImHexApi::System::getImHexVersion(),
|
ImHexApi::System::getImHexVersion(),
|
||||||
ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash(),
|
ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash(),
|
||||||
__DATE__, __TIME__,
|
__DATE__, __TIME__,
|
||||||
@ -38,7 +38,7 @@ namespace hex::plugin::builtin {
|
|||||||
void handleHelpCommand(const std::vector<std::string> &args) {
|
void handleHelpCommand(const std::vector<std::string> &args) {
|
||||||
hex::unused(args);
|
hex::unused(args);
|
||||||
|
|
||||||
hex::print(
|
hex::log::print(
|
||||||
"ImHex - A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM.\n"
|
"ImHex - A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"usage: imhex [subcommand] [options]\n"
|
"usage: imhex [subcommand] [options]\n"
|
||||||
@ -54,7 +54,7 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
for (const auto &plugin : PluginManager::getPlugins()) {
|
for (const auto &plugin : PluginManager::getPlugins()) {
|
||||||
for (const auto &subCommand : plugin.getSubCommands()) {
|
for (const auto &subCommand : plugin.getSubCommands()) {
|
||||||
hex::println(" --{}{: <{}} {}", subCommand.commandKey, "", longestCommand - subCommand.commandKey.size(), subCommand.commandDesc);
|
hex::log::println(" --{}{: <{}} {}", subCommand.commandKey, "", longestCommand - subCommand.commandKey.size(), subCommand.commandDesc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
void handleOpenCommand(const std::vector<std::string> &args) {
|
void handleOpenCommand(const std::vector<std::string> &args) {
|
||||||
if (args.empty()) {
|
if (args.empty()) {
|
||||||
hex::println("No files provided to open.");
|
hex::log::println("No files provided to open.");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,8 +89,8 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
void handleCalcCommand(const std::vector<std::string> &args) {
|
void handleCalcCommand(const std::vector<std::string> &args) {
|
||||||
if (args.empty()) {
|
if (args.empty()) {
|
||||||
hex::println("No expression provided!");
|
hex::log::println("No expression provided!");
|
||||||
hex::println("Example: imhex --calc \"5 * 7\"");
|
hex::log::println("Example: imhex --calc \"5 * 7\"");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,9 +100,9 @@ namespace hex::plugin::builtin {
|
|||||||
auto result = evaluator.evaluate(input);
|
auto result = evaluator.evaluate(input);
|
||||||
|
|
||||||
if (!result.has_value())
|
if (!result.has_value())
|
||||||
hex::println("{}\n> '{}'", evaluator.getLastError().value(), input);
|
hex::log::println("{}\n> '{}'", evaluator.getLastError().value(), input);
|
||||||
else
|
else
|
||||||
hex::println("{}", result.value());
|
hex::log::println("{}", result.value());
|
||||||
|
|
||||||
std::exit(EXIT_SUCCESS);
|
std::exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
@ -110,19 +110,19 @@ namespace hex::plugin::builtin {
|
|||||||
void handlePluginsCommand(const std::vector<std::string> &args) {
|
void handlePluginsCommand(const std::vector<std::string> &args) {
|
||||||
hex::unused(args);
|
hex::unused(args);
|
||||||
|
|
||||||
hex::println("Loaded plugins:");
|
hex::log::println("Loaded plugins:");
|
||||||
|
|
||||||
for (const auto &plugin : PluginManager::getPlugins()) {
|
for (const auto &plugin : PluginManager::getPlugins()) {
|
||||||
hex::print("- ");
|
hex::log::print("- ");
|
||||||
|
|
||||||
if (plugin.isBuiltinPlugin())
|
if (plugin.isBuiltinPlugin())
|
||||||
hex::print("\033[1;43m{}\033[0m", plugin.getPluginName());
|
hex::log::print("\033[1;43m{}\033[0m", plugin.getPluginName());
|
||||||
else
|
else
|
||||||
hex::print("\033[1m{}\033[0m", plugin.getPluginName());
|
hex::log::print("\033[1m{}\033[0m", plugin.getPluginName());
|
||||||
|
|
||||||
hex::println(" by {}", plugin.getPluginAuthor());
|
hex::log::println(" by {}", plugin.getPluginAuthor());
|
||||||
|
|
||||||
hex::println(" \033[2;3m{}\033[0m", plugin.getPluginDescription());
|
hex::log::println(" \033[2;3m{}\033[0m", plugin.getPluginDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::exit(EXIT_SUCCESS);
|
std::exit(EXIT_SUCCESS);
|
||||||
@ -130,8 +130,8 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
void handleHashCommand(const std::vector<std::string> &args) {
|
void handleHashCommand(const std::vector<std::string> &args) {
|
||||||
if (args.size() != 2) {
|
if (args.size() != 2) {
|
||||||
hex::println("usage: imhex --hash <algorithm> <file>");
|
hex::log::println("usage: imhex --hash <algorithm> <file>");
|
||||||
hex::println("Available algorithms: md5, sha1, sha224, sha256, sha384, sha512");
|
hex::log::println("Available algorithms: md5, sha1, sha224, sha256, sha384, sha512");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
wolv::io::File file(filePath, wolv::io::File::Mode::Read);
|
wolv::io::File file(filePath, wolv::io::File::Mode::Read);
|
||||||
if (!file.isValid()) {
|
if (!file.isValid()) {
|
||||||
hex::println("Failed to open file: {}", wolv::util::toUTF8String(filePath));
|
hex::log::println("Failed to open file: {}", wolv::util::toUTF8String(filePath));
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,20 +162,20 @@ namespace hex::plugin::builtin {
|
|||||||
} else if (algorithm == "sha512") {
|
} else if (algorithm == "sha512") {
|
||||||
result = toVector(hex::crypt::sha512(file.readVector()));
|
result = toVector(hex::crypt::sha512(file.readVector()));
|
||||||
} else {
|
} else {
|
||||||
hex::println("Unknown algorithm: {}", algorithm);
|
hex::log::println("Unknown algorithm: {}", algorithm);
|
||||||
hex::println("Available algorithms: md5, sha1, sha224, sha256, sha384, sha512");
|
hex::log::println("Available algorithms: md5, sha1, sha224, sha256, sha384, sha512");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
hex::println("{}({}) = {}", algorithm, wolv::util::toUTF8String(filePath.filename()), hex::crypt::encode16(result));
|
hex::log::println("{}({}) = {}", algorithm, wolv::util::toUTF8String(filePath.filename()), hex::crypt::encode16(result));
|
||||||
|
|
||||||
std::exit(EXIT_SUCCESS);
|
std::exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleEncodeCommand(const std::vector<std::string> &args) {
|
void handleEncodeCommand(const std::vector<std::string> &args) {
|
||||||
if (args.size() != 2) {
|
if (args.size() != 2) {
|
||||||
hex::println("usage: imhex --encode <algorithm> <string>");
|
hex::log::println("usage: imhex --encode <algorithm> <string>");
|
||||||
hex::println("Available algorithms: base64, hex");
|
hex::log::println("Available algorithms: base64, hex");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,19 +189,19 @@ namespace hex::plugin::builtin {
|
|||||||
} else if (algorithm == "hex") {
|
} else if (algorithm == "hex") {
|
||||||
result = hex::crypt::encode16(data);
|
result = hex::crypt::encode16(data);
|
||||||
} else {
|
} else {
|
||||||
hex::println("Unknown algorithm: {}", algorithm);
|
hex::log::println("Unknown algorithm: {}", algorithm);
|
||||||
hex::println("Available algorithms: base64, hex");
|
hex::log::println("Available algorithms: base64, hex");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
hex::println("encode_{}({}) = {}", algorithm, args[1], result);
|
hex::log::println("encode_{}({}) = {}", algorithm, args[1], result);
|
||||||
std::exit(EXIT_SUCCESS);
|
std::exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleDecodeCommand(const std::vector<std::string> &args) {
|
void handleDecodeCommand(const std::vector<std::string> &args) {
|
||||||
if (args.size() != 2) {
|
if (args.size() != 2) {
|
||||||
hex::println("usage: imhex --decode <algorithm> <string>");
|
hex::log::println("usage: imhex --decode <algorithm> <string>");
|
||||||
hex::println("Available algorithms: base64, hex");
|
hex::log::println("Available algorithms: base64, hex");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,24 +216,24 @@ namespace hex::plugin::builtin {
|
|||||||
auto base16 = hex::crypt::decode16(std::string(data.begin(), data.end()));
|
auto base16 = hex::crypt::decode16(std::string(data.begin(), data.end()));
|
||||||
result = std::string(base16.begin(), base16.end());
|
result = std::string(base16.begin(), base16.end());
|
||||||
} else {
|
} else {
|
||||||
hex::println("Unknown algorithm: {}", algorithm);
|
hex::log::println("Unknown algorithm: {}", algorithm);
|
||||||
hex::println("Available algorithms: base64, hex");
|
hex::log::println("Available algorithms: base64, hex");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
hex::print("decode_{}({}) = {}", algorithm, args[1], result);
|
hex::log::print("decode_{}({}) = {}", algorithm, args[1], result);
|
||||||
std::exit(EXIT_SUCCESS);
|
std::exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleMagicCommand(const std::vector<std::string> &args) {
|
void handleMagicCommand(const std::vector<std::string> &args) {
|
||||||
if (args.size() != 2) {
|
if (args.size() != 2) {
|
||||||
hex::println("usage: imhex --magic <operation> <file>");
|
hex::log::println("usage: imhex --magic <operation> <file>");
|
||||||
hex::println("Available operations: mime, desc");
|
hex::log::println("Available operations: mime, desc");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!magic::compile()) {
|
if (!magic::compile()) {
|
||||||
hex::print("Failed to compile magic database!");
|
hex::log::print("Failed to compile magic database!");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +242,7 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
wolv::io::File file(filePath, wolv::io::File::Mode::Read);
|
wolv::io::File file(filePath, wolv::io::File::Mode::Read);
|
||||||
if (!file.isValid()) {
|
if (!file.isValid()) {
|
||||||
hex::println("Failed to open file: {}", wolv::util::toUTF8String(filePath));
|
hex::log::println("Failed to open file: {}", wolv::util::toUTF8String(filePath));
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,13 +250,13 @@ namespace hex::plugin::builtin {
|
|||||||
|
|
||||||
if (operation == "mime") {
|
if (operation == "mime") {
|
||||||
auto result = magic::getMIMEType(data);
|
auto result = magic::getMIMEType(data);
|
||||||
hex::println("{}", result);
|
hex::log::println("{}", result);
|
||||||
} else if (operation == "desc") {
|
} else if (operation == "desc") {
|
||||||
auto result = magic::getDescription(data);
|
auto result = magic::getDescription(data);
|
||||||
hex::println("{}", result);
|
hex::log::println("{}", result);
|
||||||
} else {
|
} else {
|
||||||
hex::println("Unknown operation: {}", operation);
|
hex::log::println("Unknown operation: {}", operation);
|
||||||
hex::println("Available operations: mime, desc");
|
hex::log::println("Available operations: mime, desc");
|
||||||
std::exit(EXIT_FAILURE);
|
std::exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ namespace hex::plugin::builtin {
|
|||||||
return hex::format("hex.builtin.command.cmd.result"_lang, input.data());
|
return hex::format("hex.builtin.command.cmd.result"_lang, input.data());
|
||||||
},
|
},
|
||||||
[](auto input) {
|
[](auto input) {
|
||||||
hex::runCommand(input);
|
hex::executeCommand(input);
|
||||||
});
|
});
|
||||||
|
|
||||||
ContentRegistry::CommandPaletteCommands::addHandler(
|
ContentRegistry::CommandPaletteCommands::addHandler(
|
||||||
|
@ -211,7 +211,7 @@ namespace hex::plugin::builtin {
|
|||||||
ImGui::UnderlinedText("hex.builtin.welcome.header.update"_lang);
|
ImGui::UnderlinedText("hex.builtin.welcome.header.update"_lang);
|
||||||
{
|
{
|
||||||
if (ImGui::DescriptionButton("hex.builtin.welcome.update.title"_lang, hex::format("hex.builtin.welcome.update.desc"_lang, ImHexApi::System::getInitArguments()["update-available"]).c_str(), ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0)))
|
if (ImGui::DescriptionButton("hex.builtin.welcome.update.title"_lang, hex::format("hex.builtin.welcome.update.desc"_lang, ImHexApi::System::getInitArguments()["update-available"]).c_str(), ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0)))
|
||||||
hex::openWebpage("hex.builtin.welcome.update.link"_lang);
|
ImHexApi::System::updateImHex(ImHexApi::System::UpdateType::Stable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,9 +19,8 @@ GLFW_ICON ICON dist/windows/icon.ico
|
|||||||
BEGIN
|
BEGIN
|
||||||
BLOCK "080904b0"
|
BLOCK "080904b0"
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "FileDescription", "🔍 A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM."
|
|
||||||
VALUE "CompanyName", "WerWolv"
|
VALUE "CompanyName", "WerWolv"
|
||||||
VALUE "FileDescription", "ImHex Hex Editor"
|
VALUE "FileDescription", "ImHex Hex Editor 🔍"
|
||||||
VALUE "LegalCopyright", "WerWolv 2020-2023"
|
VALUE "LegalCopyright", "WerWolv 2020-2023"
|
||||||
VALUE "OriginalFilename", "imhex.exe"
|
VALUE "OriginalFilename", "imhex.exe"
|
||||||
VALUE "ProductName", "ImHex"
|
VALUE "ProductName", "ImHex"
|
||||||
|
Loading…
Reference in New Issue
Block a user