Added project files
This commit is contained in:
parent
9e8523470d
commit
4878f70e58
@ -10,6 +10,7 @@ pkg_search_module(CRYPTO REQUIRED libcrypto)
|
||||
pkg_search_module(CAPSTONE REQUIRED capstone)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(LLVM REQUIRED CONFIG)
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
|
||||
llvm_map_components_to_libnames(demangler)
|
||||
|
||||
@ -32,6 +33,7 @@ add_executable(ImHex
|
||||
source/helpers/crypto.cpp
|
||||
source/helpers/patches.cpp
|
||||
source/helpers/math_evaluator.cpp
|
||||
source/helpers/project_file_handler.cpp
|
||||
|
||||
source/lang/preprocessor.cpp
|
||||
source/lang/lexer.cpp
|
||||
@ -69,9 +71,9 @@ add_executable(ImHex
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(ImHex libglfw3.a libgcc.a libstdc++.a libmagic.a libgnurx.a libtre.a libintl.a libiconv.a shlwapi.lib libcrypto.a libwinpthread.a libcapstone.a libLLVMDemangle.a)
|
||||
target_link_libraries(ImHex libglfw3.a libgcc.a libstdc++.a libmagic.a libgnurx.a libtre.a libintl.a libiconv.a shlwapi.lib libcrypto.a libwinpthread.a libcapstone.a libLLVMDemangle.a nlohmann_json::nlohmann_json)
|
||||
endif (WIN32)
|
||||
|
||||
if (UNIX)
|
||||
target_link_libraries(ImHex libglfw.so libmagic.so libcrypto.so libdl.so libcapstone.so libLLVMDemangle.so)
|
||||
target_link_libraries(ImHex libglfw.so libmagic.so libcrypto.so libdl.so libcapstone.so libLLVMDemangle.so nlohmann_json::nlohmann_json)
|
||||
endif (UNIX)
|
@ -10,11 +10,15 @@ namespace hex {
|
||||
DataChanged,
|
||||
PatternChanged,
|
||||
FileDropped,
|
||||
WindowClosing,
|
||||
RegionSelected,
|
||||
|
||||
SelectionChangeRequest,
|
||||
|
||||
AddBookmark
|
||||
AddBookmark,
|
||||
|
||||
ProjectFileStore,
|
||||
ProjectFileLoad
|
||||
};
|
||||
|
||||
struct EventHandler {
|
||||
|
46
include/helpers/project_file_handler.hpp
Normal file
46
include/helpers/project_file_handler.hpp
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "patches.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace hex {
|
||||
|
||||
class ProjectFile {
|
||||
public:
|
||||
ProjectFile() = delete;
|
||||
|
||||
static bool load(std::string_view filePath);
|
||||
static bool store(std::string_view filePath = "");
|
||||
|
||||
[[nodiscard]] static bool hasUnsavedChanges() { return ProjectFile::s_hasUnsavedChanged; }
|
||||
static void markDirty() { if (!ProjectFile::s_currProjectFilePath.empty()) ProjectFile::s_hasUnsavedChanged = true; }
|
||||
|
||||
[[nodiscard]] static std::string getProjectFilePath() { return ProjectFile::s_currProjectFilePath; }
|
||||
|
||||
[[nodiscard]] static std::string getFilePath() { return ProjectFile::s_filePath; }
|
||||
static void setFilePath(std::string_view filePath) { ProjectFile::s_hasUnsavedChanged = true; ProjectFile::s_filePath = filePath; }
|
||||
|
||||
[[nodiscard]] static std::string getPattern() { return ProjectFile::s_pattern; }
|
||||
static void setPattern(std::string_view pattern) { ProjectFile::s_hasUnsavedChanged = true; ProjectFile::s_pattern = pattern; }
|
||||
|
||||
[[nodiscard]] static const Patches& getPatches() { return ProjectFile::s_patches; }
|
||||
static void setPatches(const Patches &patches) { ProjectFile::s_hasUnsavedChanged = true; ProjectFile::s_patches = patches; }
|
||||
|
||||
[[nodiscard]] static const std::list<Bookmark>& getBookmarks() { return ProjectFile::s_bookmarks; }
|
||||
static void setBookmarks(const std::list<Bookmark> &bookmarks) { ProjectFile::s_hasUnsavedChanged = true; ProjectFile::s_bookmarks = bookmarks; }
|
||||
|
||||
private:
|
||||
static inline std::string s_currProjectFilePath;
|
||||
static inline bool s_hasUnsavedChanged = false;
|
||||
|
||||
static inline std::string s_filePath;
|
||||
static inline std::string s_pattern;
|
||||
static inline Patches s_patches;
|
||||
static inline std::list<Bookmark> s_bookmarks;
|
||||
};
|
||||
|
||||
}
|
@ -118,4 +118,11 @@ namespace hex {
|
||||
u64 address;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct Bookmark {
|
||||
Region region;
|
||||
|
||||
std::vector<char> name;
|
||||
std::vector<char> comment;
|
||||
};
|
||||
}
|
@ -11,13 +11,6 @@ namespace hex {
|
||||
|
||||
namespace prv { class Provider; }
|
||||
|
||||
struct Bookmark {
|
||||
Region region;
|
||||
|
||||
std::vector<char> name;
|
||||
std::vector<char> comment;
|
||||
};
|
||||
|
||||
class ViewBookmarks : public View {
|
||||
public:
|
||||
explicit ViewBookmarks(prv::Provider* &dataProvider);
|
||||
|
85
source/helpers/project_file_handler.cpp
Normal file
85
source/helpers/project_file_handler.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include "helpers/project_file_handler.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace hex {
|
||||
|
||||
void to_json(json& j, const hex::Bookmark& b) {
|
||||
j = json{ { "address", b.region.address }, { "size", b.region.size }, { "name", b.name.data() }, { "comment", b.comment.data() } };
|
||||
}
|
||||
|
||||
void from_json(const json& j, hex::Bookmark& b) {
|
||||
std::string name, comment;
|
||||
|
||||
j.at("address").get_to(b.region.address);
|
||||
j.at("size").get_to(b.region.size);
|
||||
j.at("name").get_to(name);
|
||||
j.at("comment").get_to(comment);
|
||||
|
||||
std::copy(name.begin(), name.end(), std::back_inserter(b.name));
|
||||
std::copy(comment.begin(), comment.end(), std::back_inserter(b.comment));
|
||||
}
|
||||
|
||||
|
||||
bool ProjectFile::load(std::string_view filePath) {
|
||||
ProjectFile::s_hasUnsavedChanged = false;
|
||||
|
||||
json projectFileData;
|
||||
|
||||
try {
|
||||
std::ifstream projectFile(filePath.data());
|
||||
projectFile >> projectFileData;
|
||||
|
||||
ProjectFile::s_filePath = projectFileData["filePath"];
|
||||
ProjectFile::s_pattern = projectFileData["pattern"];
|
||||
ProjectFile::s_patches = projectFileData["patches"].get<Patches>();
|
||||
|
||||
for (auto &element : projectFileData["bookmarks"].items()) {
|
||||
ProjectFile::s_bookmarks.push_back(element.value().get<Bookmark>());
|
||||
}
|
||||
|
||||
} catch (json::exception &e) {
|
||||
return false;
|
||||
} catch (std::ofstream::failure &e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectFile::s_currProjectFilePath = filePath;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProjectFile::store(std::string_view filePath) {
|
||||
ProjectFile::s_hasUnsavedChanged = false;
|
||||
|
||||
json projectFileData;
|
||||
|
||||
if (filePath.empty())
|
||||
filePath = ProjectFile::s_currProjectFilePath;
|
||||
|
||||
try {
|
||||
projectFileData["filePath"] = ProjectFile::s_filePath;
|
||||
projectFileData["pattern"] = ProjectFile::s_pattern;
|
||||
projectFileData["patches"] = ProjectFile::s_patches;
|
||||
|
||||
for (auto &bookmark : ProjectFile::s_bookmarks) {
|
||||
projectFileData["bookmarks"].push_back(bookmark);
|
||||
}
|
||||
|
||||
std::ofstream projectFile(filePath.data(), std::fstream::trunc);
|
||||
projectFile << projectFileData;
|
||||
} catch (json::exception &e) {
|
||||
return false;
|
||||
} catch (std::ifstream::failure &e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ProjectFile::s_currProjectFilePath = filePath;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
#include <time.h>
|
||||
|
||||
#include "helpers/utils.hpp"
|
||||
#include "helpers/project_file_handler.hpp"
|
||||
|
||||
namespace hex::prv {
|
||||
|
||||
@ -22,6 +23,9 @@ namespace hex::prv {
|
||||
this->m_file = fopen(path.data(), "rb");
|
||||
this->m_writable = false;
|
||||
}
|
||||
|
||||
if (this->m_file != nullptr)
|
||||
ProjectFile::setFilePath(path);
|
||||
}
|
||||
|
||||
FileProvider::~FileProvider() {
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "views/view_bookmarks.hpp"
|
||||
|
||||
#include "providers/provider.hpp"
|
||||
#include "helpers/project_file_handler.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@ -19,11 +20,21 @@ namespace hex {
|
||||
std::strcpy(bookmark.name.data(), ("Bookmark " + std::to_string(this->m_bookmarks.size() + 1)).c_str());
|
||||
|
||||
this->m_bookmarks.push_back(bookmark);
|
||||
ProjectFile::markDirty();
|
||||
});
|
||||
|
||||
View::subscribeEvent(Events::ProjectFileLoad, [this](const void*) {
|
||||
this->m_bookmarks = ProjectFile::getBookmarks();
|
||||
});
|
||||
View::subscribeEvent(Events::ProjectFileStore, [this](const void*) {
|
||||
ProjectFile::setBookmarks(this->m_bookmarks);
|
||||
});
|
||||
}
|
||||
|
||||
ViewBookmarks::~ViewBookmarks() {
|
||||
View::unsubscribeEvent(Events::AddBookmark);
|
||||
View::unsubscribeEvent(Events::ProjectFileLoad);
|
||||
View::unsubscribeEvent(Events::ProjectFileStore);
|
||||
}
|
||||
|
||||
void ViewBookmarks::createView() {
|
||||
@ -83,8 +94,10 @@ namespace hex {
|
||||
}
|
||||
}
|
||||
|
||||
if (bookmarkToRemove != this->m_bookmarks.end())
|
||||
if (bookmarkToRemove != this->m_bookmarks.end()) {
|
||||
this->m_bookmarks.erase(bookmarkToRemove);
|
||||
ProjectFile::markDirty();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "helpers/crypto.hpp"
|
||||
#include "helpers/patches.hpp"
|
||||
#include "helpers/project_file_handler.hpp"
|
||||
|
||||
#undef __STRICT_ANSI__
|
||||
#include <cstdio>
|
||||
@ -36,6 +37,7 @@ namespace hex {
|
||||
|
||||
_this->m_dataProvider->write(off, &d, sizeof(ImU8));
|
||||
_this->postEvent(Events::DataChanged);
|
||||
ProjectFile::markDirty();
|
||||
};
|
||||
|
||||
this->m_memoryEditor.HighlightFn = [](const ImU8 *data, size_t off, bool next) -> bool {
|
||||
@ -76,6 +78,19 @@ namespace hex {
|
||||
this->m_memoryEditor.DataPreviewAddrEnd = region.address + region.size - 1;
|
||||
View::postEvent(Events::RegionSelected, ®ion);
|
||||
});
|
||||
|
||||
View::subscribeEvent(Events::ProjectFileLoad, [this](const void *userData) {
|
||||
this->openFile(ProjectFile::getFilePath());
|
||||
});
|
||||
|
||||
View::subscribeEvent(Events::WindowClosing, [this](const void *userData) {
|
||||
auto window = const_cast<GLFWwindow*>(static_cast<const GLFWwindow*>(userData));
|
||||
|
||||
if (ProjectFile::hasUnsavedChanges()) {
|
||||
glfwSetWindowShouldClose(window, GLFW_FALSE);
|
||||
View::doLater([] { ImGui::OpenPopup("Save Changes"); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ViewHexEditor::~ViewHexEditor() {
|
||||
@ -118,6 +133,24 @@ namespace hex {
|
||||
}
|
||||
|
||||
|
||||
if (ImGui::BeginPopupModal("Save Changes", nullptr, ImGuiWindowFlags_NoResize)) {
|
||||
constexpr auto Message = "You have unsaved changes made to your Project. Are you sure you want to exit?";
|
||||
ImGui::NewLine();
|
||||
if (ImGui::BeginChild("##scrolling", ImVec2(300, 50))) {
|
||||
ImGui::SetCursorPosX(10);
|
||||
ImGui::TextWrapped("%s", Message);
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::SetCursorPosX(40);
|
||||
if (ImGui::Button("Yes", ImVec2(100, 20)))
|
||||
std::exit(0);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(160);
|
||||
if (ImGui::Button("No", ImVec2(100, 20)))
|
||||
ImGui::CloseCurrentPopup();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
|
||||
if (this->m_fileBrowser.showFileDialog("Open File", imgui_addons::ImGuiFileBrowser::DialogMode::OPEN)) {
|
||||
this->openFile(this->m_fileBrowser.selected_path);
|
||||
@ -138,6 +171,17 @@ namespace hex {
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (this->m_fileBrowser.showFileDialog("Open Project", imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, ImVec2(0, 0), ".hexproj")) {
|
||||
ProjectFile::load(this->m_fileBrowser.selected_path);
|
||||
View::postEvent(Events::ProjectFileLoad);
|
||||
}
|
||||
|
||||
if (this->m_fileBrowser.showFileDialog("Save Project", imgui_addons::ImGuiFileBrowser::DialogMode::SAVE, ImVec2(0, 0), ".hexproj")) {
|
||||
ProjectFile::store(this->m_fileBrowser.selected_path);
|
||||
}
|
||||
|
||||
|
||||
if (this->m_fileBrowser.showFileDialog("Export File", imgui_addons::ImGuiFileBrowser::DialogMode::SAVE)) {
|
||||
this->saveToFile(this->m_fileBrowser.selected_path, this->m_dataToSave);
|
||||
}
|
||||
@ -199,6 +243,24 @@ namespace hex {
|
||||
View::doLater([]{ ImGui::OpenPopup("Save As"); });
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("Open Project", "")) {
|
||||
this->getWindowOpenState() = true;
|
||||
View::doLater([]{ ImGui::OpenPopup("Open Project"); });
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Save Project", "", false, this->m_dataProvider != nullptr && this->m_dataProvider->isWritable())) {
|
||||
View::postEvent(Events::ProjectFileStore);
|
||||
|
||||
if (ProjectFile::getProjectFilePath() == "")
|
||||
View::doLater([] { ImGui::OpenPopup("Save Project"); });
|
||||
else
|
||||
ProjectFile::store();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::BeginMenu("Import...")) {
|
||||
if (ImGui::MenuItem("Base64 File")) {
|
||||
this->getWindowOpenState() = true;
|
||||
@ -315,7 +377,7 @@ namespace hex {
|
||||
for (const auto &[address, value] : this->m_dataProvider->getPatches())
|
||||
this->m_dataProvider->writeRaw(address, &value, sizeof(u8));
|
||||
return true;
|
||||
} else if (mods == GLFW_MOD_CONTROL | GLFW_MOD_SHIFT && key == GLFW_KEY_S) {
|
||||
} else if (mods == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT) && key == GLFW_KEY_S) {
|
||||
ImGui::OpenPopup("Save As");
|
||||
return true;
|
||||
} else if (mods == GLFW_MOD_CONTROL && key == GLFW_KEY_F) {
|
||||
@ -345,8 +407,13 @@ namespace hex {
|
||||
|
||||
this->m_dataProvider = new prv::FileProvider(path);
|
||||
this->m_memoryEditor.ReadOnly = !this->m_dataProvider->isWritable();
|
||||
|
||||
if (this->m_dataProvider->isAvailable())
|
||||
ProjectFile::setFilePath(path);
|
||||
|
||||
View::postEvent(Events::FileLoaded);
|
||||
View::postEvent(Events::DataChanged);
|
||||
ProjectFile::markDirty();
|
||||
}
|
||||
|
||||
bool ViewHexEditor::saveToFile(std::string path, const std::vector<u8>& data) {
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "providers/provider.hpp"
|
||||
|
||||
#include "helpers/utils.hpp"
|
||||
#include "helpers/project_file_handler.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -11,11 +12,20 @@ using namespace std::literals::string_literals;
|
||||
namespace hex {
|
||||
|
||||
ViewPatches::ViewPatches(prv::Provider* &dataProvider) : View("Patches"), m_dataProvider(dataProvider) {
|
||||
View::subscribeEvent(Events::ProjectFileStore, [this](const void*) {
|
||||
if (this->m_dataProvider != nullptr)
|
||||
ProjectFile::setPatches(this->m_dataProvider->getPatches());
|
||||
});
|
||||
|
||||
View::subscribeEvent(Events::ProjectFileLoad, [this](const void*) {
|
||||
if (this->m_dataProvider != nullptr)
|
||||
this->m_dataProvider->getPatches() = ProjectFile::getPatches();
|
||||
});
|
||||
}
|
||||
|
||||
ViewPatches::~ViewPatches() {
|
||||
|
||||
View::unsubscribeEvent(Events::ProjectFileStore);
|
||||
View::unsubscribeEvent(Events::ProjectFileLoad);
|
||||
}
|
||||
|
||||
void ViewPatches::createView() {
|
||||
@ -62,6 +72,7 @@ namespace hex {
|
||||
if (ImGui::BeginPopup("PatchContextMenu")) {
|
||||
if (ImGui::MenuItem("Remove")) {
|
||||
patches.erase(this->m_selectedPatch);
|
||||
ProjectFile::markDirty();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "lang/lexer.hpp"
|
||||
#include "lang/validator.hpp"
|
||||
#include "lang/evaluator.hpp"
|
||||
|
||||
#include "helpers/project_file_handler.hpp"
|
||||
#include "helpers/utils.hpp"
|
||||
|
||||
#include <magic.h>
|
||||
@ -76,9 +78,20 @@ namespace hex {
|
||||
this->m_textEditor.SetLanguageDefinition(PatternLanguage());
|
||||
this->m_textEditor.SetShowWhitespaces(false);
|
||||
|
||||
View::subscribeEvent(Events::FileLoaded, [this](const void* userData) {
|
||||
lang::Preprocessor preprocessor;
|
||||
View::subscribeEvent(Events::ProjectFileStore, [this](const void*) {
|
||||
ProjectFile::setPattern(this->m_textEditor.GetText());
|
||||
});
|
||||
|
||||
View::subscribeEvent(Events::ProjectFileLoad, [this](const void*) {
|
||||
this->m_textEditor.SetText(ProjectFile::getPattern());
|
||||
this->parsePattern(this->m_textEditor.GetText().data());
|
||||
});
|
||||
|
||||
View::subscribeEvent(Events::FileLoaded, [this](const void* userData) {
|
||||
if (!this->m_textEditor.GetText().empty())
|
||||
return;
|
||||
|
||||
lang::Preprocessor preprocessor;
|
||||
std::string magicFiles;
|
||||
|
||||
std::error_code error;
|
||||
@ -140,7 +153,8 @@ namespace hex {
|
||||
}
|
||||
|
||||
ViewPattern::~ViewPattern() {
|
||||
|
||||
View::unsubscribeEvent(Events::ProjectFileStore);
|
||||
View::unsubscribeEvent(Events::ProjectFileLoad);
|
||||
}
|
||||
|
||||
void ViewPattern::createMenu() {
|
||||
|
@ -107,15 +107,17 @@ namespace hex {
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
|
||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->GetWorkPos());
|
||||
ImGui::SetNextWindowSize(viewport->GetWorkSize());
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
||||
windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
|
||||
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking
|
||||
| ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse
|
||||
| ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBringToFrontOnFocus;
|
||||
|
||||
if (ImGui::Begin("DockSpace", nullptr, windowFlags)) {
|
||||
ImGui::PopStyleVar(2);
|
||||
@ -208,6 +210,7 @@ namespace hex {
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
|
||||
this->m_window = glfwCreateWindow(1280, 720, "ImHex", nullptr, nullptr);
|
||||
|
||||
|
||||
@ -229,7 +232,12 @@ namespace hex {
|
||||
View::postEvent(Events::FileDropped, paths[0]);
|
||||
});
|
||||
|
||||
glfwSetWindowSizeLimits(this->m_window, 720, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
|
||||
glfwSetWindowCloseCallback(this->m_window, [](GLFWwindow *window) {
|
||||
View::postEvent(Events::WindowClosing, window);
|
||||
});
|
||||
|
||||
|
||||
glfwSetWindowSizeLimits(this->m_window, 720, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
|
||||
|
||||
if (gladLoadGL() == 0)
|
||||
throw std::runtime_error("Failed to initialize OpenGL loader!");
|
||||
|
Loading…
Reference in New Issue
Block a user