code:
#pragma once

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
#include <string>
#include <fstream>
#include <vector>
#include <algorithm>

#include "ConfigOption.h"
#include "PluginConfig.h"

int (__cdecl* divaMain)(int argc, const char** argv, const char** envp) = (int(__cdecl*)(int argc, const char** argv, const char** envp))0x140194D90;

using namespace std;

string arg;

wstring ExePath() {
	WCHAR buffer[MAX_PATH];
	GetModuleFileNameW(NULL, buffer, MAX_PATH);
	return wstring(buffer);
}

wstring DirPath() {
	wstring exepath = ExePath();
	wstring::size_type pos = exepath.find_last_of(L"\\/");
	return exepath.substr(0, pos);
}

wstring DIVA_EXECUTABLE_STRING = ExePath();
LPCWSTR DIVA_EXECUTABLE = DIVA_EXECUTABLE_STRING.c_str();

wstring DIVA_EXECUTABLE_LAUNCH_STRING = DIVA_EXECUTABLE_STRING + L" --launch";
LPWSTR DIVA_EXECUTABLE_LAUNCH = const_cast<WCHAR*>(DIVA_EXECUTABLE_LAUNCH_STRING.c_str());

wstring PLUGINS_DIR = DirPath() + L"\\plugins";

wstring CONFIG_FILE_STRING = PLUGINS_DIR + L"\\config.ini";
LPCWSTR CONFIG_FILE = CONFIG_FILE_STRING.c_str();

wstring COMPONENTS_FILE_STRING = PLUGINS_DIR + L"\\components.ini";
LPCWSTR COMPONENTS_FILE = COMPONENTS_FILE_STRING.c_str();

wstring PLAYERDATA_FILE_STRING = PLUGINS_DIR + L"\\playerdata.ini";
LPCWSTR PLAYERDATA_FILE = PLAYERDATA_FILE_STRING.c_str();

wstring KEYCONFIG_FILE_STRING = PLUGINS_DIR + L"\\keyconfig.ini";
LPCWSTR KEYCONFIG_FILE = KEYCONFIG_FILE_STRING.c_str();

LPCWSTR PATCHES_SECTION = L"patches";
LPCWSTR GRAPHICS_SECTION = L"graphics";
LPCWSTR RESOLUTION_SECTION = L"resolution";
LPCWSTR LAUNCHER_SECTION = L"launcher";
LPCWSTR COMPONENTS_SECTION = L"components";
LPCWSTR PLAYERDATA_SECTION = L"playerdata";
LPCWSTR KEYCONFIG_SECTION = L"keyconfig";

int nSkipLauncher = GetPrivateProfileIntW(L"launcher", L"skip", FALSE, CONFIG_FILE);

std::vector<DEVMODEW> getScreenModes() {
	static std::vector<DEVMODEW> outVec = std::vector<DEVMODEW>();

	DEVMODEW dm = { 0 };
	dm.dmSize = sizeof(dm);
	for (int iModeNum = 0; EnumDisplaySettingsW(NULL, iModeNum, &dm) != 0; iModeNum++)
	{
		outVec.push_back(dm);
	}

	return outVec;
}

std::vector<resolution> getScreenResolutionsVec(std::vector<DEVMODEW> &screenModes) {
	static std::vector<resolution> outVec = std::vector<resolution>();

	for (DEVMODEW &dm : screenModes)
	{
		resolution res = resolution(dm.dmPelsWidth, dm.dmPelsHeight);

		if (std::find(outVec.begin(), outVec.end(), res) == outVec.end()) {
			outVec.push_back(res);
		}
	}

	std::sort(outVec.begin(), outVec.end());

	return outVec;
}

static std::vector<int> getScreenDepthsVec(std::vector<DEVMODEW> &screenModes) {
	static std::vector<int> outVec = std::vector<int>();

	for (DEVMODEW &dm : screenModes)
	{
		int depth = dm.dmBitsPerPel;

		if (depth < 24)
			continue;

		if (std::find(outVec.begin(), outVec.end(), depth) == outVec.end()) {
			outVec.push_back(depth);
		}
	}

	std::sort(outVec.begin(), outVec.end());

	return outVec;
}

static std::vector<int> getScreenRatesVec(std::vector<DEVMODEW> &screenModes) {
	static std::vector<int> outVec = std::vector<int>();

	for (DEVMODEW &dm : screenModes)
	{
		int rate = dm.dmDisplayFrequency;

		if (std::find(outVec.begin(), outVec.end(), rate) == outVec.end()) {
			outVec.push_back(rate);
		}
	}

	std::sort(outVec.begin(), outVec.end());

	return outVec;
}

std::vector<DEVMODEW> screenModes = getScreenModes();

DropdownOption* DisplayModeDropdown = new DropdownOption(L"display", RESOLUTION_SECTION, CONFIG_FILE, L"Display:", L"Sets the window/screen mode.", 0, std::vector<LPCWSTR>({ L"Windowed", L"Borderless", L"Fullscreen" }));
ResolutionOption* DisplayResolutionOption = new ResolutionOption(L"width", L"height", RESOLUTION_SECTION, CONFIG_FILE, L"Resolution:", L"Sets the display resolution.", resolution(1280, 720), getScreenResolutionsVec(screenModes), true);

ConfigOptionBase* screenResolutionArray[] = {
	DisplayModeDropdown,
	DisplayResolutionOption,
	//new DropdownNumberOption(L"bitdepth", RESOLUTION_SECTION, CONFIG_FILE, L"Bit Depth:", L"Sets the display bit depth.", 32, getScreenDepthsVec(screenModes), true),
	new DropdownNumberOption(L"refreshrate", RESOLUTION_SECTION, CONFIG_FILE, L"Refresh Rate:", L"Sets the display refresh rate.", 60, getScreenRatesVec(screenModes), true),
};

BooleanOption* InternalResolutionCheckbox = new BooleanOption(L"r.enable", RESOLUTION_SECTION, CONFIG_FILE, L"Enable", L"Enable or disable custom internal resolution.", false, false);
ResolutionOption* InternalResolutionOption = new ResolutionOption(L"r.width", L"r.height", RESOLUTION_SECTION, CONFIG_FILE, L"Resolution:", L"Sets the internal resolution (instead of 1280x720).", resolution(1920, 1080), std::vector<resolution>({ resolution(1,1), resolution(320,200), resolution(320,240), resolution(640,480), resolution(800,600), resolution(960,720), resolution(1360,768), resolution(1366,768), resolution(1600,900), resolution(1920,1080), resolution(2560,1440), resolution(3200,1800), resolution(3840,2160), resolution(5120,2880), resolution(6400,3600), resolution(7680,4320) }), true);

ConfigOptionBase* internalResolutionArray[] = {
	InternalResolutionCheckbox,
	InternalResolutionOption
};

ConfigOptionBase* optionsArray[] = {
	new BooleanOption(L"hide_freeplay", PATCHES_SECTION, CONFIG_FILE, L"Hide \"FREE PLAY\"/\"CREDIT(S)\"", L"Hide the \"FREE PLAY\"/\"CREDIT(S)\" text.", false, false),
	new BooleanOption(L"freeplay", PATCHES_SECTION, CONFIG_FILE, L"FREE PLAY", L"Show \"FREE PLAY\" instead of \"CREDIT(S)\".", true, false),
	new BooleanOption(L"pdloadertext", PATCHES_SECTION, CONFIG_FILE, L"PD Loader FREE PLAY", L"Show the version of PD Loader instead of \"FREE PLAY\".", true, false),
	new OptionMetaSpacer(8),

	new BooleanOption(L"no_movies", PATCHES_SECTION, CONFIG_FILE, L"Disable Movies", L"Disable movies (enable this if the game hangs when loading certain PVs).", false, false),
	new BooleanOption(L"mp4_movies", PATCHES_SECTION, CONFIG_FILE, L"Custom MP4 Adv Movies", L"Enable MP4 (instead of WMV) advertise/attract movies.", false, false),
	new OptionMetaSpacer(8),

	new BooleanOption(L"cursor", PATCHES_SECTION, CONFIG_FILE, L"Cursor", L"Enable or disable the mouse cursor.", true, false),
	new BooleanOption(L"hide_volume", PATCHES_SECTION, CONFIG_FILE, L"Hide Volume Buttons", L"Hide the volume and SE control buttons.", false, false),
	new BooleanOption(L"no_pv_ui", PATCHES_SECTION, CONFIG_FILE, L"Disable PV UI", L"Remove the photo controls during PV playback.", false, false),
	new BooleanOption(L"hide_pv_watermark", PATCHES_SECTION, CONFIG_FILE, L"Hide PV Watermark", L"Hide the watermark that's usually shown in PV viewing mode.", false, false),
	new BooleanOption(L"no_lyrics", PATCHES_SECTION, CONFIG_FILE, L"Disable Lyrics", L"Disable showing lyrics.", false, false),
	new BooleanOption(L"no_error", PATCHES_SECTION, CONFIG_FILE, L"Disable Error Banner", L"Disable the error banner on the attract screen.", true, false),
	new DropdownOption(L"status_icons", PATCHES_SECTION, CONFIG_FILE, L"Status Icons:", L"Set the state of card reader and network status icons.", 3, std::vector<LPCWSTR>({ L"Default", L"Hidden", L"Error", L"OK", L"Partial OK" })),
	new OptionMetaSpacer(8),

	new BooleanOption(L"Enhanced_Stage_Manager_Encore", PATCHES_SECTION, CONFIG_FILE, L"Encore Stages", L"Enable encore stages.", true, false),
	new NumericOption(L"Enhanced_Stage_Manager", PATCHES_SECTION, CONFIG_FILE, L"Number of stages:", L"Set the number of stages (0 = default).", 0, 0, INT_MAX),
	new OptionMetaSpacer(8),

	new BooleanOption(L"hardware_slider", PATCHES_SECTION, CONFIG_FILE, L"Use Hardware Slider", L"Enable this if using a real arcade slider.\n(set the slider to port COM11)", false, false),
	new OptionMetaSpacer(8),

	new BooleanOption(L"TAA", GRAPHICS_SECTION, CONFIG_FILE, L"TAA", L"Temporal Anti-Aliasing", true, false),
	new BooleanOption(L"MLAA", GRAPHICS_SECTION, CONFIG_FILE, L"MLAA", L"Morphological Anti-Aliasing", true, false),
	new BooleanOption(L"borderless.popup", RESOLUTION_SECTION, CONFIG_FILE, L"Popup Borderless Mode", L"Makes borderless mode use a popup window.\nPerformance may increase and latency should decrease, but screenshots may not work as expected.", true, false),
	new BooleanOption(L"FPS.Limit.LightMode", GRAPHICS_SECTION, CONFIG_FILE, L"Use Lightweight Limiter", L"Makes the FPS limiter use less CPU.\nMay have less consistent performance.", true, false),
	new NumericOption(L"FPS.Limit", GRAPHICS_SECTION, CONFIG_FILE, L"FPS Limit:", L"Allows you to set a frame rate cap. Set to -1 to unlock the frame rate.", 60, -1, INT_MAX),
	new NumericOption(L"frm.motion.rate", GRAPHICS_SECTION, CONFIG_FILE, L"FRM Motion Rate:", L"Sets the motion rate (fps) for the Frame Rate Manager component.\nLarger values should be smoother, but more CPU intensive and possibly buggier.", 300, 1, INT_MAX),
	new OptionMetaSpacer(8),

	new BooleanOption(L"rumble", KEYCONFIG_SECTION, KEYCONFIG_FILE, L"XInput Rumble", L"Enables rumble during chainslides.", true, true),
	new NumericOption(L"xinput_preferred", KEYCONFIG_SECTION, KEYCONFIG_FILE, L"XInput Controller Num:", L"Sets the preferred XInput controller.\nIf unavailable, the next connected controller is used.", 0, 0, 3),
	new OptionMetaSpacer(8),

	new StringOption(L"command_line", LAUNCHER_SECTION, CONFIG_FILE, L"Command Line:", L"Allows setting command line parameters for the game when using the launcher.\nDisabling the launcher will bypass this.", L"", false),
};

ConfigOptionBase* playerdataArray[] = {
	new StringOption(L"player_name", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Player Name:", L"Player name shown in game.", L"ＮＯ－ＮＡＭＥ", true),
	new StringOption(L"level_name", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Level Name:", L"Level (plate) name shown in game.", L"忘れないでね私の声を", true),

	new NumericOption(L"level_plate_id", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Level Plate:", L"Sets the level background image (plate).", 0, 0, INT_MAX),
	new NumericOption(L"level_plate_effect", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Level Plate Effect:", L"Sets the effect on the level background image (plate).", -1, -1, 2),
	new NumericOption(L"skin_equip ", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Skin:", L"Sets the gameplay UI skin.", 0, 0, INT_MAX),

	new NumericOption(L"btn_se_equip", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Button Sound:", L"Sets the sound effect for buttons.\n-1 = song default", -1, -1, INT_MAX),
	new NumericOption(L"slide_se_equip", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Slide Sound:", L"Sets the sound effect for slides.\n-1 = song default", -1, -1, INT_MAX),
	new NumericOption(L"chainslide_se_equip", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Chainslide Sound:", L"Sets the sound effect for chain slides.\n-1 = song default", -1, -1, INT_MAX),
	new NumericOption(L"slidertouch_se_equip", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Slider Touch Sound:", L"Sets the sound effect for touching the slider with no note.\n-1 = song default", -1, -1, INT_MAX),
	new BooleanOption(L"act_toggle", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Button SE", L"Enables button/slider sounds.", true, true),

	new BooleanOption(L"border_great", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Clear Border (Great)", L"Shows the clear border for a great rating on the progress bar.", true, true),
	new BooleanOption(L"border_excellent", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Clear Border (Excellent)", L"Shows the clear border for an excellent rating on the progress bar.", true, true),
	new BooleanOption(L"border_rival", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Clear Border (Rival)", L"Shows the clear border for beating your rival on the progress bar.", false, true),

	new BooleanOption(L"use_card", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Use Card", L"Enables IC card. This allows module selection.", false, true),
	new BooleanOption(L"module_card_workaround", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Module Selection Workaround", L"Allows module selection without card and tries to improve menu performance.\n(BETA)", true, true),

	new BooleanOption(L"gamemode_options", PLAYERDATA_SECTION, PLAYERDATA_FILE, L"Game Modifiers", L"Allows use of game mode modifiers (hi-speed, hidden, and sudden).", true, true),
};

ConfigOptionBase* componentsArray[] = {
	new BooleanOption(L"input_emulator", COMPONENTS_SECTION, COMPONENTS_FILE, L"Input Emulator", L"Emulates input through keyboard and/or mouse.", false, true),
	new BooleanOption(L"touch_slider_emulator", COMPONENTS_SECTION, COMPONENTS_FILE, L"Slider Emulator", L"Emulates slider through keyboard and/or mouse.", false, true),
	new BooleanOption(L"touch_panel_emulator", COMPONENTS_SECTION, COMPONENTS_FILE, L"Touch Panel Emulator", L"Emulates touch panel through mouse.", false, true),

	new BooleanOption(L"sys_timer", COMPONENTS_SECTION, COMPONENTS_FILE, L"Timer Freeze", L"Freezes the PV select timer at 39 seconds.", false, true),

	new BooleanOption(L"player_data_manager", COMPONENTS_SECTION, COMPONENTS_FILE, L"Player Data Manager", L"Loads user-defined values into the PlayerData struct.\nRequired for modules and game mode modifiers.", false, true),

	new BooleanOption(L"frame_rate_manager", COMPONENTS_SECTION, COMPONENTS_FILE, L"Frame Rate Manager", L"Adjusts animations to the correct speed at different frame rates.\nOnly needed when FPS isn't locket at 60.", false, true),

	new BooleanOption(L"stage_manager", COMPONENTS_SECTION, COMPONENTS_FILE, L"Stage Manager", L"Allows for playing unlimited songs per session.", false, true),

	new BooleanOption(L"fast_loader", COMPONENTS_SECTION, COMPONENTS_FILE, L"Fast Loader", L"Skip or speed up unnecessary loading steps.", false, true),

	new BooleanOption(L"camera_controller", COMPONENTS_SECTION, COMPONENTS_FILE, L"Camera Controller", L"Enables freecam (toggled using F3).\nWASD to move, SPACE/CTRL for up/down, Q/R to rotate, R/F for zoom.\nHolding SHIFT/ALT changes control speed.", false, true),

	new BooleanOption(L"scale_component", COMPONENTS_SECTION, COMPONENTS_FILE, L"Scale Component", L"Scales the graphics output framebuffer to fill the screen/window.", false, true),

	new BooleanOption(L"debug_component", COMPONENTS_SECTION, COMPONENTS_FILE, L"Debug Component", L"Allows for changing game state (F4-F8 keys), using dev GUI and tests, and speeding up 2d animations/menus (hold SHIFT+TAB).", false, true),

	new BooleanOption(L"target_inspector", COMPONENTS_SECTION, COMPONENTS_FILE, L"Target Inspector", L"Enables hold transfers.", false, true),

	new BooleanOption(L"score_saver", COMPONENTS_SECTION, COMPONENTS_FILE, L"Score Saver", L"Saves high scores to plugins/scores.ini.", false, true),
};

bool IsLineInFile(LPCSTR searchLine, LPCWSTR fileName)
{
	bool result = false;

	std::ifstream fileStream(fileName);

	if (!fileStream.is_open())
		return false;

	std::string line;

	// check for BOM
	std::getline(fileStream, line);
	if (line.size() >= 3 && line.rfind("\xEF\xBB\xBF", 0) == 0)
		fileStream.seekg(3);
	else
		fileStream.seekg(0);

	while (std::getline(fileStream, line))
	{
		if (line.compare(searchLine) == 0)
		{
			result = true;
			break;
		}
	}

	fileStream.close();
	return result;
}

void PrependFile(LPCSTR newStr, LPCWSTR fileName)
{
	std::fstream fileStream(fileName);

	if (!fileStream.is_open())
		return;

	std::string origStr;

	// this is apparently more efficient than just going straight into the string
	fileStream.seekg(0, std::ios::end);
	origStr.reserve(fileStream.tellg());
	fileStream.seekg(0, std::ios::beg);

	// check for BOM
	std::string BOMcheckLine;
	std::getline(fileStream, BOMcheckLine);
	if (BOMcheckLine.size() >= 3 && BOMcheckLine.rfind("\xEF\xBB\xBF", 0) == 0)
		fileStream.seekg(3);
	else
		fileStream.seekg(0);

	origStr.assign((std::istreambuf_iterator<char>(fileStream)), std::istreambuf_iterator<char>());

	std::string outStr = newStr;
	outStr += origStr;

	fileStream.seekg(0, std::ios::beg);
	fileStream << outStr;
	fileStream.close();
}

struct PluginInfo {
	HMODULE handle;
	std::wstring filename;
	std::wstring name;
	std::wstring description;
	std::vector<ConfigOptionBase*> configopts;
};
std::vector<PluginInfo> LoadPlugins()
{
	std::vector<PluginInfo> outvec;

	HANDLE hFind;
	WIN32_FIND_DATAW ffd;

	hFind = FindFirstFileW((PLUGINS_DIR + L"\\*.dva").c_str(), &ffd);
	if (hFind != INVALID_HANDLE_VALUE) {
		do {
			if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
			{
				auto pos = wcslen(ffd.cFileName);

				if (ffd.cFileName[pos - 4] == '.' &&
					(ffd.cFileName[pos - 3] == 'd' || ffd.cFileName[pos - 3] == 'D') &&
					(ffd.cFileName[pos - 2] == 'v' || ffd.cFileName[pos - 2] == 'V') &&
					(ffd.cFileName[pos - 1] == 'a' || ffd.cFileName[pos - 1] == 'A'))
				{
					PluginInfo thisplugin;
					thisplugin.handle = LoadLibraryW((PLUGINS_DIR + L"\\" + ffd.cFileName).c_str());
					thisplugin.filename = ffd.cFileName;

					if (thisplugin.handle == NULL)
						continue;

					auto nameFunc = (LPCWSTR(*)())GetProcAddress(thisplugin.handle, "GetPluginName");
					auto descFunc = (LPCWSTR(*)())GetProcAddress(thisplugin.handle, "GetPluginDescription");
					auto optsFunc = (PluginConfig::PluginConfigArray(*)())GetProcAddress(thisplugin.handle, "GetPluginOptions");

					if (nameFunc != NULL)
						thisplugin.name = nameFunc();
					else
						thisplugin.name = thisplugin.filename;

					if (descFunc != NULL)
						thisplugin.description = descFunc();
					else
						thisplugin.description = (thisplugin.filename + L" Plugin").c_str();

					if (optsFunc != NULL)
						thisplugin.configopts = PluginConfig::GetConfigOptionVec(optsFunc());

					// sometimes this might screw with custom button config, so let's just not free stuff
					// FreeLibrary(thisplugin.handle);

					outvec.push_back(thisplugin);
				}
			}
		} while (FindNextFileW(hFind, &ffd));
		FindClose(hFind);
	}

	return outvec;
}
// don't load until actually needed to avoid loading disabled plugins
std::vector<PluginInfo> AllPlugins; // = LoadPlugins();

std::vector<PluginOption*> GetPluginOptions(std::vector<PluginInfo>* plugins)
{
	std::vector<PluginOption*> outvec;

	for (PluginInfo &pi : *plugins)
	{
		PluginOption* opt = new PluginOption(pi.filename.c_str(), L"plugins", CONFIG_FILE, pi.name.c_str(), pi.description.c_str(), true, pi.configopts);
		outvec.push_back(opt);
	}

	return outvec;
}
// don't load until actually needed to avoid loading disabled plugins
std::vector<PluginOption*> AllPluginOpts; // = GetPluginOptions(&AllPlugins);

// used to trick Optimus into switching to the NVIDIA GPU
HMODULE nvcudaModule = LoadLibraryW(L"nvcuda.dll");
// cuInit actually returns a CUresult, but we don't really care about it
void(WINAPI * cuInit)(unsigned int flags) = (void(WINAPI*)(unsigned int flags))GetProcAddress(nvcudaModule, "cuInit");

// needed to close the OpenGL window (freeglut only)
HMODULE glutModule = LoadLibraryW(L"glut32.dll");
void(__stdcall * glutMainLoopEventDynamic)() = (void(__stdcall*)())GetProcAddress(glutModule, "glutMainLoopEvent");


code:
#pragma once
#include "framework.h"
#include <msclr\marshal_cppstd.h>
#include <cstdlib>
#include "TabPadding.h"
#include <GL\freeglut.h>
#include <GL\GL.h>
#include "SkinnedMessageBox.h"
#include "PluginConfig.h"

namespace Launcher {

	using namespace System;
	using namespace System::ComponentModel;
	using namespace System::Collections;
	using namespace System::Windows::Forms;
	using namespace System::Data;
	using namespace System::Drawing;
	using namespace msclr::interop;

	/// <summary>
	/// Summary for ui
	/// </summary>
	public ref class ui : public System::Windows::Forms::Form
	{
	public:
		ui(void)
		{
			InitializeComponent();
			this->button_Discord->Cursor = Cursors::Hand;
			this->ClientSize = Drawing::Size(444, 323);
			TabPadding^ tabpad = gcnew TabPadding(tabControl);

			// if components.ini has no section name, add one
			if (!IsLineInFile("[components]", COMPONENTS_FILE))
			{
				PrependFile("[components]\n", COMPONENTS_FILE);
			}

			// if playerdata.ini has no section name, add one
			if (!IsLineInFile("[playerdata]", PLAYERDATA_FILE))
			{
				PrependFile("[playerdata]\n", PLAYERDATA_FILE);
			}

			// if keyconfig.ini has no section name, add one
			if (!IsLineInFile("[keyconfig]", KEYCONFIG_FILE))
			{
				PrependFile("[keyconfig]\n", KEYCONFIG_FILE);
			}

			this->panel_ScreenRes->SuspendLayout();

			// populate options (patches) from array in framework
			// (easier than manually setting everything up)
			int screenresY = 3;
			for (ConfigOptionBase* option : screenResolutionArray)
			{
				option->hasChanged = ConfigHasChanged;
				screenresY += option->AddToPanel(panel_ScreenRes, 12, screenresY, toolTip1);
			}
			((ComboBox^)ComboBox::FromHandle(DisplayModeDropdown->mainControlHandle))->SelectedIndexChanged += gcnew System::EventHandler(this, &ui::DisplayTypeChangedHandler);
			DisplayTypeChangedHandler(this, gcnew EventArgs); // run handler now
			this->panel_ScreenRes->ResumeLayout(false);
			this->panel_ScreenRes->PerformLayout();

			this->panel_IntRes->SuspendLayout();

			// populate options (patches) from array in framework
			// (easier than manually setting everything up)
			int intresY = 3;
			for (ConfigOptionBase* option : internalResolutionArray)
			{
				option->hasChanged = ConfigHasChanged;
				intresY += option->AddToPanel(panel_IntRes, 12, intresY, toolTip1);
			}
			((CheckBox^)CheckBox::FromHandle(InternalResolutionCheckbox->mainControlHandle))->CheckedChanged += gcnew System::EventHandler(this, &ui::InternalResEnabledChangedHandler);
			InternalResEnabledChangedHandler(this, gcnew EventArgs); // run handler now
			this->panel_IntRes->ResumeLayout(false);
			this->panel_IntRes->PerformLayout();

			this->panel_Patches->SuspendLayout();

			// populate options (patches) from array in framework
			// (easier than manually setting everything up)
			int optionsY = 3;
			for (ConfigOptionBase* option : optionsArray)
			{
				option->hasChanged = ConfigHasChanged;
				optionsY += option->AddToPanel(panel_Patches, 12, optionsY, toolTip1);
			}
			this->panel_Patches->ResumeLayout(false);
			this->panel_Patches->PerformLayout();

			this->panel_Playerdata->SuspendLayout();

			// populate playerdata options from array in framework
			// (easier than manually setting everything up)
			int playerdataY = 3;
			for (ConfigOptionBase* option : playerdataArray)
			{
				option->hasChanged = ConfigHasChanged;
				playerdataY += option->AddToPanel(panel_Playerdata, 12, playerdataY, toolTip1);
			}
			this->panel_Playerdata->ResumeLayout(false);
			this->panel_Playerdata->PerformLayout();

			this->panel_Components->SuspendLayout();

			// populate components from array in framework
			// (easier than manually setting everything up)
			int componentsY = 3;
			for (ConfigOptionBase* component : componentsArray)
			{
				component->hasChanged = ConfigHasChanged;
				componentsY += component->AddToPanel(panel_Components, 12, componentsY, toolTip1);
			}
			this->panel_Components->ResumeLayout(false);
			this->panel_Components->PerformLayout();

			AllPlugins = LoadPlugins();
			AllPluginOpts = GetPluginOptions(&AllPlugins);

			this->panel_Plugins->SuspendLayout();

			int pluginsY = 3;
			for (ConfigOptionBase* option : AllPluginOpts)
			{
				option->hasChanged = ConfigHasChanged;
				pluginsY += option->AddToPanel(panel_Plugins, 12, pluginsY, toolTip1);
			}
			this->panel_Plugins->ResumeLayout(false);
			this->panel_Plugins->PerformLayout();

			// trick Optimus into switching to the NVIDIA GPU
			if (cuInit != NULL) cuInit(0);

			int argc = 1;
			char* argv[1] = { (char*)"" };

			glutInit(&argc, argv);
			int window = glutCreateWindow("glut"); // a context must be created to use glGetString

			String^ vendor = gcnew String((char*)glGetString(GL_VENDOR));
			String^ renderer = gcnew String((char*)glGetString(GL_RENDERER));
			String^ version = gcnew String((char*)glGetString(GL_VERSION));

			glutDestroyWindow(window); // destroy the window so it doesn't remain on screen
			if (glutMainLoopEventDynamic != NULL) glutMainLoopEventDynamic(); // freeglut needs this

			this->labelGPU->Text = "Vendor: " + vendor + "\nRenderer: " + renderer + "\nOpenGL: " + version;
			if (!vendor->Contains("NVIDIA"))
			{
				this->labelGPU->Text += "\nIssues: NVIDIA GPU REQUIRED!";
				this->labelGPU->ForeColor = System::Drawing::Color::Red;
				SkinnedMessageBox::Show(this, "Your graphics card is not supported! Only NVIDIA GPUs are currently supported.", "PD Launcher", MessageBoxButtons::OK, MessageBoxIcon::Warning);
			}
			else if (version[0] < '4')
			{
				this->labelGPU->Text += "\nIssues: GPU too old! 3D rendering might be broken.";
				this->labelGPU->ForeColor = System::Drawing::Color::Orange;
			}
			else if (renderer->Contains("RTX") || renderer->Contains("GTX 16"))
			{
				this->labelGPU->Text += "\nIssues: Turing GPU detected! Possible noise.";
				this->labelGPU->ForeColor = System::Drawing::Color::Yellow;
			}
			else this->labelGPU->Text += "\nIssues: None.";
		}

	protected:
		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		~ui()
		{
			if (components)
			{
				delete components;
			}
		}
	private: System::Windows::Forms::Button^ button_Launch;
	private: System::Windows::Forms::Button^ button_Help;
	private: System::Windows::Forms::GroupBox^ groupBox_ScreenRes;
	private: System::Windows::Forms::TabControl^ tabControl;
	private: System::Windows::Forms::TabPage^ tabPage_Resolution;
	private: System::Windows::Forms::GroupBox^ groupBox_InternalRes;
	private: System::Windows::Forms::TabPage^ tabPage_Components;
	private: System::Windows::Forms::Panel^ panel_Components;
	private: System::Windows::Forms::TabPage^ tabPage_Patches;
	private: System::Windows::Forms::Panel^ panel_Patches;
	private: System::Windows::Forms::TabPage^ tabPage_Plugins;
	private: System::Windows::Forms::Panel^ panel_Plugins;
	private: System::Windows::Forms::Button^ button_Discord;
	private: System::Windows::Forms::Button^ button_github;
	private: System::Windows::Forms::TabPage^  tabPage_Playerdata;
	private: System::Windows::Forms::Panel^  panel_Playerdata;
	private: System::Windows::Forms::ToolTip^  toolTip1;
	private: System::Windows::Forms::Panel^  panel_ScreenRes;
	private: System::Windows::Forms::Panel^  panel_IntRes;
	private: System::Windows::Forms::Label^ labelGPU;
	private: System::Windows::Forms::Button^ button_Apply;

	private: System::ComponentModel::IContainer^ components;

	private:
		/// <summary>
		/// Required designer variable.
		/// </summary>

#pragma region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		void InitializeComponent(void)
		{
			this->components = (gcnew System::ComponentModel::Container());
			System::ComponentModel::ComponentResourceManager^ resources = (gcnew System::ComponentModel::ComponentResourceManager(ui::typeid));
			this->button_Launch = (gcnew System::Windows::Forms::Button());
			this->button_Help = (gcnew System::Windows::Forms::Button());
			this->groupBox_ScreenRes = (gcnew System::Windows::Forms::GroupBox());
			this->panel_ScreenRes = (gcnew System::Windows::Forms::Panel());
			this->tabControl = (gcnew System::Windows::Forms::TabControl());
			this->tabPage_Resolution = (gcnew System::Windows::Forms::TabPage());
			this->labelGPU = (gcnew System::Windows::Forms::Label());
			this->groupBox_InternalRes = (gcnew System::Windows::Forms::GroupBox());
			this->panel_IntRes = (gcnew System::Windows::Forms::Panel());
			this->tabPage_Patches = (gcnew System::Windows::Forms::TabPage());
			this->panel_Patches = (gcnew System::Windows::Forms::Panel());
			this->tabPage_Playerdata = (gcnew System::Windows::Forms::TabPage());
			this->panel_Playerdata = (gcnew System::Windows::Forms::Panel());
			this->tabPage_Components = (gcnew System::Windows::Forms::TabPage());
			this->panel_Components = (gcnew System::Windows::Forms::Panel());
			this->tabPage_Plugins = (gcnew System::Windows::Forms::TabPage());
			this->panel_Plugins = (gcnew System::Windows::Forms::Panel());
			this->button_Discord = (gcnew System::Windows::Forms::Button());
			this->button_github = (gcnew System::Windows::Forms::Button());
			this->toolTip1 = (gcnew System::Windows::Forms::ToolTip(this->components));
			this->button_Apply = (gcnew System::Windows::Forms::Button());
			this->groupBox_ScreenRes->SuspendLayout();
			this->tabControl->SuspendLayout();
			this->tabPage_Resolution->SuspendLayout();
			this->groupBox_InternalRes->SuspendLayout();
			this->tabPage_Patches->SuspendLayout();
			this->tabPage_Playerdata->SuspendLayout();
			this->tabPage_Components->SuspendLayout();
			this->tabPage_Plugins->SuspendLayout();
			this->SuspendLayout();
			//
			// button_Launch
			//
			this->button_Launch->FlatAppearance->BorderColor = System::Drawing::SystemColors::Control;
			this->button_Launch->FlatStyle = System::Windows::Forms::FlatStyle::Flat;
			this->button_Launch->Location = System::Drawing::Point(4, 473);
			this->button_Launch->Margin = System::Windows::Forms::Padding(4);
			this->button_Launch->Name = L"button_Launch";
			this->button_Launch->Size = System::Drawing::Size(102, 34);
			this->button_Launch->TabIndex = 20;
			this->button_Launch->Text = L"Launch";
			this->button_Launch->Click += gcnew System::EventHandler(this, &ui::Button_Launch_Click);
			//
			// button_Help
			//
			this->button_Help->DialogResult = System::Windows::Forms::DialogResult::Cancel;
			this->button_Help->FlatAppearance->BorderColor = System::Drawing::SystemColors::Control;
			this->button_Help->FlatStyle = System::Windows::Forms::FlatStyle::Flat;
			this->button_Help->Location = System::Drawing::Point(114, 473);
			this->button_Help->Margin = System::Windows::Forms::Padding(4);
			this->button_Help->Name = L"button_Help";
			this->button_Help->Size = System::Drawing::Size(103, 34);
			this->button_Help->TabIndex = 21;
			this->button_Help->Text = L"Get help";
			this->button_Help->Click += gcnew System::EventHandler(this, &ui::Button_Help_Click);
			//
			// groupBox_ScreenRes
			//
			this->groupBox_ScreenRes->Controls->Add(this->panel_ScreenRes);
			this->groupBox_ScreenRes->FlatStyle = System::Windows::Forms::FlatStyle::Flat;
			this->groupBox_ScreenRes->ForeColor = System::Drawing::Color::White;
			this->groupBox_ScreenRes->Location = System::Drawing::Point(8, 9);
			this->groupBox_ScreenRes->Margin = System::Windows::Forms::Padding(4);
			this->groupBox_ScreenRes->Name = L"groupBox_ScreenRes";
			this->groupBox_ScreenRes->Padding = System::Windows::Forms::Padding(4);
			this->groupBox_ScreenRes->Size = System::Drawing::Size(417, 169);
			this->groupBox_ScreenRes->TabIndex = 10;
			this->groupBox_ScreenRes->TabStop = false;
			this->groupBox_ScreenRes->Text = L"Screen Resolution";
			//
			// panel_ScreenRes
			//
			this->panel_ScreenRes->AutoScroll = true;
			this->panel_ScreenRes->Location = System::Drawing::Point(6, 28);
			this->panel_ScreenRes->Margin = System::Windows::Forms::Padding(4);
			this->panel_ScreenRes->Name = L"panel_ScreenRes";
			this->panel_ScreenRes->Size = System::Drawing::Size(403, 133);
			this->panel_ScreenRes->TabIndex = 0;
			//
			// tabControl
			//
			this->tabControl->Appearance = System::Windows::Forms::TabAppearance::FlatButtons;
			this->tabControl->Controls->Add(this->tabPage_Resolution);
			this->tabControl->Controls->Add(this->tabPage_Patches);
			this->tabControl->Controls->Add(this->tabPage_Playerdata);
			this->tabControl->Controls->Add(this->tabPage_Components);
			this->tabControl->Controls->Add(this->tabPage_Plugins);
			this->tabControl->Location = System::Drawing::Point(0, 0);
			this->tabControl->Margin = System::Windows::Forms::Padding(4);
			this->tabControl->Name = L"tabControl";
			this->tabControl->SelectedIndex = 0;
			this->tabControl->Size = System::Drawing::Size(441, 465);
			this->tabControl->TabIndex = 10;
			//
			// tabPage_Resolution
			//
			this->tabPage_Resolution->BackColor = System::Drawing::Color::Transparent;
			this->tabPage_Resolution->BackgroundImage = (cli::safe_cast<System::Drawing::Image^>(resources->GetObject(L"tabPage_Resolution.BackgroundImage")));
			this->tabPage_Resolution->BackgroundImageLayout = System::Windows::Forms::ImageLayout::Stretch;
			this->tabPage_Resolution->Controls->Add(this->labelGPU);
			this->tabPage_Resolution->Controls->Add(this->groupBox_InternalRes);
			this->tabPage_Resolution->Controls->Add(this->groupBox_ScreenRes);
			this->tabPage_Resolution->Location = System::Drawing::Point(4, 32);
			this->tabPage_Resolution->Margin = System::Windows::Forms::Padding(4);
			this->tabPage_Resolution->Name = L"tabPage_Resolution";
			this->tabPage_Resolution->Padding = System::Windows::Forms::Padding(4);
			this->tabPage_Resolution->Size = System::Drawing::Size(433, 429);
			this->tabPage_Resolution->TabIndex = 0;
			this->tabPage_Resolution->Text = L"Resolution";
			//
			// labelGPU
			//
			this->labelGPU->AutoSize = true;
			this->labelGPU->ForeColor = System::Drawing::Color::Lime;
			this->labelGPU->Location = System::Drawing::Point(12, 319);
			this->labelGPU->MinimumSize = System::Drawing::Size(410, 20);
			this->labelGPU->Name = L"labelGPU";
			this->labelGPU->Size = System::Drawing::Size(410, 20);
			this->labelGPU->TabIndex = 21;
			this->labelGPU->TextAlign = System::Drawing::ContentAlignment::MiddleLeft;
			//
			// groupBox_InternalRes
			//
			this->groupBox_InternalRes->Controls->Add(this->panel_IntRes);
			this->groupBox_InternalRes->FlatStyle = System::Windows::Forms::FlatStyle::Flat;
			this->groupBox_InternalRes->ForeColor = System::Drawing::Color::White;
			this->groupBox_InternalRes->Location = System::Drawing::Point(8, 186);
			this->groupBox_InternalRes->Margin = System::Windows::Forms::Padding(4);
			this->groupBox_InternalRes->Name = L"groupBox_InternalRes";
			this->groupBox_InternalRes->Padding = System::Windows::Forms::Padding(4);
			this->groupBox_InternalRes->Size = System::Drawing::Size(417, 124);
			this->groupBox_InternalRes->TabIndex = 20;
			this->groupBox_InternalRes->TabStop = false;
			this->groupBox_InternalRes->Text = L"Internal Resolution";
			//
			// panel_IntRes
			//
			this->panel_IntRes->AutoScroll = true;
			this->panel_IntRes->Location = System::Drawing::Point(6, 28);
			this->panel_IntRes->Margin = System::Windows::Forms::Padding(4);
			this->panel_IntRes->Name = L"panel_IntRes";
			this->panel_IntRes->Size = System::Drawing::Size(403, 88);
			this->panel_IntRes->TabIndex = 1;
			//
			// tabPage_Patches
			//
			this->tabPage_Patches->BackColor = System::Drawing::Color::FromArgb(static_cast<System::Int32>(static_cast<System::Byte>(64)), static_cast<System::Int32>(static_cast<System::Byte>(64)),
				static_cast<System::Int32>(static_cast<System::Byte>(64)));
			this->tabPage_Patches->Controls->Add(this->panel_Patches);
			this->tabPage_Patches->Location = System::Drawing::Point(4, 32);
			this->tabPage_Patches->Margin = System::Windows::Forms::Padding(4);
			this->tabPage_Patches->Name = L"tabPage_Patches";
			this->tabPage_Patches->Size = System::Drawing::Size(433, 429);
			this->tabPage_Patches->TabIndex = 1;
			this->tabPage_Patches->Text = L"Options";
			//
			// panel_Patches
			//
			this->panel_Patches->AutoScroll = true;
			this->panel_Patches->Location = System::Drawing::Point(0, 0);
			this->panel_Patches->Margin = System::Windows::Forms::Padding(4);
			this->panel_Patches->Name = L"panel_Patches";
			this->panel_Patches->Size = System::Drawing::Size(429, 425);
			this->panel_Patches->TabIndex = 9;
			//
			// tabPage_Playerdata
			//
			this->tabPage_Playerdata->BackColor = System::Drawing::Color::FromArgb(static_cast<System::Int32>(static_cast<System::Byte>(64)),
				static_cast<System::Int32>(static_cast<System::Byte>(64)), static_cast<System::Int32>(static_cast<System::Byte>(64)));
			this->tabPage_Playerdata->Controls->Add(this->panel_Playerdata);
			this->tabPage_Playerdata->Location = System::Drawing::Point(4, 32);
			this->tabPage_Playerdata->Margin = System::Windows::Forms::Padding(4);
			this->tabPage_Playerdata->Name = L"tabPage_Playerdata";
			this->tabPage_Playerdata->Size = System::Drawing::Size(433, 429);
			this->tabPage_Playerdata->TabIndex = 3;
			this->tabPage_Playerdata->Text = L"Player";
			//
			// panel_Playerdata
			//
			this->panel_Playerdata->AutoScroll = true;
			this->panel_Playerdata->Location = System::Drawing::Point(0, 0);
			this->panel_Playerdata->Margin = System::Windows::Forms::Padding(4);
			this->panel_Playerdata->Name = L"panel_Playerdata";
			this->panel_Playerdata->Size = System::Drawing::Size(429, 425);
			this->panel_Playerdata->TabIndex = 1;
			//
			// tabPage_Components
			//
			this->tabPage_Components->BackColor = System::Drawing::Color::FromArgb(static_cast<System::Int32>(static_cast<System::Byte>(64)),
				static_cast<System::Int32>(static_cast<System::Byte>(64)), static_cast<System::Int32>(static_cast<System::Byte>(64)));
			this->tabPage_Components->Controls->Add(this->panel_Components);
			this->tabPage_Components->Location = System::Drawing::Point(4, 32);
			this->tabPage_Components->Margin = System::Windows::Forms::Padding(4);
			this->tabPage_Components->Name = L"tabPage_Components";
			this->tabPage_Components->Padding = System::Windows::Forms::Padding(4);
			this->tabPage_Components->Size = System::Drawing::Size(433, 429);
			this->tabPage_Components->TabIndex = 2;
			this->tabPage_Components->Text = L"Components";
			//
			// panel_Components
			//
			this->panel_Components->AutoScroll = true;
			this->panel_Components->Location = System::Drawing::Point(0, 0);
			this->panel_Components->Margin = System::Windows::Forms::Padding(4);
			this->panel_Components->Name = L"panel_Components";
			this->panel_Components->Size = System::Drawing::Size(429, 425);
			this->panel_Components->TabIndex = 0;
			//
			// tabPage_Plugins
			//
			this->tabPage_Plugins->BackColor = System::Drawing::Color::FromArgb(static_cast<System::Int32>(static_cast<System::Byte>(64)),
				static_cast<System::Int32>(static_cast<System::Byte>(64)), static_cast<System::Int32>(static_cast<System::Byte>(64)));
			this->tabPage_Plugins->Controls->Add(this->panel_Plugins);
			this->tabPage_Plugins->Location = System::Drawing::Point(4, 32);
			this->tabPage_Plugins->Margin = System::Windows::Forms::Padding(4);
			this->tabPage_Plugins->Name = L"tabPage_Plugins";
			this->tabPage_Plugins->Padding = System::Windows::Forms::Padding(4);
			this->tabPage_Plugins->Size = System::Drawing::Size(433, 429);
			this->tabPage_Plugins->TabIndex = 3;
			this->tabPage_Plugins->Text = L"Plugins";
			//
			// panel_Plugins
			//
			this->panel_Plugins->AutoScroll = true;
			this->panel_Plugins->Location = System::Drawing::Point(0, 0);
			this->panel_Plugins->Margin = System::Windows::Forms::Padding(4);
			this->panel_Plugins->Name = L"panel_Plugins";
			this->panel_Plugins->Size = System::Drawing::Size(429, 425);
			this->panel_Plugins->TabIndex = 0;
			//
			// button_Discord
			//
			this->button_Discord->BackColor = System::Drawing::Color::Transparent;
			this->button_Discord->FlatAppearance->BorderSize = 0;
			this->button_Discord->FlatAppearance->MouseDownBackColor = System::Drawing::Color::Transparent;
			this->button_Discord->FlatAppearance->MouseOverBackColor = System::Drawing::Color::Transparent;
			this->button_Discord->FlatStyle = System::Windows::Forms::FlatStyle::Flat;
			this->button_Discord->Image = (cli::safe_cast<System::Drawing::Image^>(resources->GetObject(L"button_Discord.Image")));
			this->button_Discord->Location = System::Drawing::Point(337, 466);
			this->button_Discord->Margin = System::Windows::Forms::Padding(4);
			this->button_Discord->Name = L"button_Discord";
			this->button_Discord->Size = System::Drawing::Size(48, 48);
			this->button_Discord->TabIndex = 31;
			this->button_Discord->UseVisualStyleBackColor = false;
			this->button_Discord->Click += gcnew System::EventHandler(this, &ui::button_Discord_Click);
			//
			// button_github
			//
			this->button_github->BackColor = System::Drawing::Color::Transparent;
			this->button_github->Cursor = System::Windows::Forms::Cursors::Hand;
			this->button_github->FlatAppearance->BorderSize = 0;
			this->button_github->FlatAppearance->MouseDownBackColor = System::Drawing::Color::Transparent;
			this->button_github->FlatAppearance->MouseOverBackColor = System::Drawing::Color::Transparent;
			this->button_github->FlatStyle = System::Windows::Forms::FlatStyle::Flat;
			this->button_github->Image = (cli::safe_cast<System::Drawing::Image^>(resources->GetObject(L"button_github.Image")));
			this->button_github->Location = System::Drawing::Point(393, 466);
			this->button_github->Margin = System::Windows::Forms::Padding(4);
			this->button_github->Name = L"button_github";
			this->button_github->Size = System::Drawing::Size(48, 48);
			this->button_github->TabIndex = 32;
			this->button_github->UseVisualStyleBackColor = false;
			this->button_github->Click += gcnew System::EventHandler(this, &ui::button_github_Click);
			//
			// button_Apply
			//
			this->button_Apply->FlatAppearance->BorderColor = System::Drawing::SystemColors::Control;
			this->button_Apply->FlatStyle = System::Windows::Forms::FlatStyle::Flat;
			this->button_Apply->Location = System::Drawing::Point(225, 473);
			this->button_Apply->Margin = System::Windows::Forms::Padding(4);
			this->button_Apply->Name = L"button_Apply";
			this->button_Apply->Size = System::Drawing::Size(104, 34);
			this->button_Apply->TabIndex = 33;
			this->button_Apply->Text = L"Apply";
			this->button_Apply->Click += gcnew System::EventHandler(this, &ui::button_Apply_Click);
			//
			// ui
			//
			this->AcceptButton = this->button_Launch;
			this->AutoScaleDimensions = System::Drawing::SizeF(144, 144);
			this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Dpi;
			this->AutoSize = true;
			this->AutoSizeMode = System::Windows::Forms::AutoSizeMode::GrowAndShrink;
			this->BackColor = System::Drawing::Color::FromArgb(static_cast<System::Int32>(static_cast<System::Byte>(64)), static_cast<System::Int32>(static_cast<System::Byte>(64)),
				static_cast<System::Int32>(static_cast<System::Byte>(64)));
			this->BackgroundImageLayout = System::Windows::Forms::ImageLayout::Stretch;
			this->ClientSize = System::Drawing::Size(440, 514);
			this->Controls->Add(this->button_Apply);
			this->Controls->Add(this->tabControl);
			this->Controls->Add(this->button_Help);
			this->Controls->Add(this->button_Launch);
			this->Controls->Add(this->button_github);
			this->Controls->Add(this->button_Discord);
			this->DoubleBuffered = true;
			this->ForeColor = System::Drawing::Color::White;
			this->FormBorderStyle = System::Windows::Forms::FormBorderStyle::FixedDialog;
			this->Icon = (cli::safe_cast<System::Drawing::Icon^>(resources->GetObject(L"$this.Icon")));
			this->KeyPreview = true;
			this->Margin = System::Windows::Forms::Padding(4);
			this->MaximizeBox = false;
			this->MinimizeBox = false;
			this->Name = L"ui";
			this->StartPosition = System::Windows::Forms::FormStartPosition::CenterScreen;
			this->Text = L"PD Launcher";
			this->FormClosing += gcnew System::Windows::Forms::FormClosingEventHandler(this, &ui::Ui_FormClosing);
			this->FormClosed += gcnew System::Windows::Forms::FormClosedEventHandler(this, &ui::Ui_FormClosed);
			this->Load += gcnew System::EventHandler(this, &ui::Ui_Load);
			this->groupBox_ScreenRes->ResumeLayout(false);
			this->tabControl->ResumeLayout(false);
			this->tabPage_Resolution->ResumeLayout(false);
			this->tabPage_Resolution->PerformLayout();
			this->groupBox_InternalRes->ResumeLayout(false);
			this->tabPage_Patches->ResumeLayout(false);
			this->tabPage_Playerdata->ResumeLayout(false);
			this->tabPage_Components->ResumeLayout(false);
			this->tabPage_Plugins->ResumeLayout(false);
			this->ResumeLayout(false);

		}
#pragma endregion
private: System::Void SaveSettings() {
	for (ConfigOptionBase* option : screenResolutionArray)
	{
		option->SaveOption();
	}

	for (ConfigOptionBase* option : internalResolutionArray)
	{
		option->SaveOption();
	}

	for (ConfigOptionBase* option : optionsArray)
	{
		option->SaveOption();
	}

	for (ConfigOptionBase* option : playerdataArray)
	{
		option->SaveOption();
	}

	for (ConfigOptionBase* component : componentsArray)
	{
		component->SaveOption();
	}

	for (ConfigOptionBase* option : AllPluginOpts)
	{
		option->SaveOption();
	}

	*ConfigHasChanged = false;
}
private: System::Void Ui_Load(System::Object^ sender, System::EventArgs^ e){
}
private: System::Void Button_Help_Click(System::Object^ sender, System::EventArgs^ e) {
	System::Diagnostics::Process::Start("https://notabug.org/nastys/PD-Loader/wiki");
}
private: System::Void Button_Launch_Click(System::Object^ sender, System::EventArgs^ e) {

	SaveSettings();

	// read the command line here so it'll be up to date even if the user changed it
	WCHAR stringBuf[256];
	GetPrivateProfileStringW(LAUNCHER_SECTION, L"command_line", L"", stringBuf, 256, CONFIG_FILE);

	DIVA_EXECUTABLE_LAUNCH_STRING += L" " + wstring(stringBuf);
	DIVA_EXECUTABLE_LAUNCH = const_cast<WCHAR*>(DIVA_EXECUTABLE_LAUNCH_STRING.c_str());

	STARTUPINFOW si;
	PROCESS_INFORMATION pi;

	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);
	ZeroMemory(&pi, sizeof(pi));
	CreateProcessW(DIVA_EXECUTABLE, DIVA_EXECUTABLE_LAUNCH, NULL, NULL, false, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

	// this->Close won't work in here since it will prompt the user to save the settings
	TerminateProcess(GetCurrentProcess(), EXIT_SUCCESS);
}
private: System::Void InternalResEnabledChangedHandler(System::Object^ sender, System::EventArgs^ e) {
	if (((CheckBox^)CheckBox::FromHandle(InternalResolutionCheckbox->mainControlHandle))->Checked)
	{
		((Control^)Control::FromHandle(InternalResolutionOption->mainControlHandle))->Enabled = true;
	}
	else
	{
		((Control^)Control::FromHandle(InternalResolutionOption->mainControlHandle))->Enabled = false;
	}
}
private: System::Void DisplayTypeChangedHandler(System::Object^ sender, System::EventArgs^ e) {
	int idx = ((ComboBox^)ComboBox::FromHandle(DisplayModeDropdown->mainControlHandle))->SelectedIndex;

	if (idx == 2) // fullscreen
	{
		for (ConfigOptionBase* option : screenResolutionArray)
		{
			if (option != DisplayModeDropdown && option != DisplayResolutionOption)
			{
				((Control^)Control::FromHandle(option->mainControlHandle))->Enabled = true;
			}
		}
	}
	else
	{
		for (ConfigOptionBase* option : screenResolutionArray)
		{
			if (option != DisplayModeDropdown && option != DisplayResolutionOption)
			{
				((Control^)Control::FromHandle(option->mainControlHandle))->Enabled = false;
			}
		}
	}
}
private: System::Void Ui_FormClosing(System::Object^ sender, System::Windows::Forms::FormClosingEventArgs^ e) {
	if (!*ConfigHasChanged)
		return;

	switch (SkinnedMessageBox::Show(this, "Do you want to save your settings?", "PD Launcher", MessageBoxButtons::YesNoCancel, MessageBoxIcon::Question))
	{
	case System::Windows::Forms::DialogResult::Yes:
		SaveSettings();
		break;

	case System::Windows::Forms::DialogResult::No:
		break;

	case System::Windows::Forms::DialogResult::Cancel:
		e->Cancel = true;
		break;
	}
}
private: System::Void Ui_FormClosed(System::Object^ sender, System::Windows::Forms::FormClosedEventArgs^ e) {
	// Prevents abnormal termination messages, remember that the game is still technically running and must be killed!
	TerminateProcess(GetCurrentProcess(), EXIT_SUCCESS);
}
private: System::Void button_Discord_Click(System::Object^ sender, System::EventArgs^ e) {
	System::Diagnostics::Process::Start("https://discord.gg/cvBVGDZ");
}
private: System::Void button_github_Click(System::Object^ sender, System::EventArgs^ e) {
	System::Diagnostics::Process::Start("https://notabug.org/nastys/PD-Loader");
}
private: System::Void button_Apply_Click(System::Object^ sender, System::EventArgs^ e) {
	SaveSettings();
}
private: System::Void Ui_KeyDown(System::Object^ sender, System::Windows::Forms::KeyEventArgs^ e) {
	if (e->KeyCode == Keys::Escape) this->Close();
}

private: bool* ConfigHasChanged = new bool(false);/*

private: System::Void ConfigChanged(System::Object^ sender, System::EventArgs^ e) {
	*ConfigHasChanged = true;
}*/
};
}

repo_name: twistero/PD

license: mit


code:
<?xml version="1.0" encoding="utf-8"?>
<root>
  <!--
    Microsoft ResX Schema

    Version 2.0

    The primary goals of this format is to allow a simple XML format
    that is mostly human readable. The generation and parsing of the
    various data types are done through the TypeConverter classes
    associated with the data types.

    Example:

    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>

    There are any number of "resheader" rows that contain simple
    name/value pairs.

    Each data row contains a name, and value. The row also contains a
    type or mimetype. Type corresponds to a .NET class that support
    text/value conversion through the TypeConverter architecture.
    Classes that don't support this are serialized and stored with the
    mimetype set.

    The mimetype is used for serialized objects, and tells the
    ResXResourceReader how to depersist the object. This is currently not
    extensible. For a given mimetype the value must be set accordingly:

    Note - application/x-microsoft.net.object.binary.base64 is the format
    that the ResXResourceWriter will generate, however the reader can
    read any of the formats listed below.

    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
  <data name="tabPage_Resolution.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
    <value>
[encoded data, 1205732 characters]
</value>
  </data>
  <data name="button_Discord.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
    <value>
        iVBORw0KGgoAAAANSUhEUgAAACoAAAApCAYAAABDV7v1AAAABGdBTUEAALGPC/xhBQAAA7dJREFUWEft
        mElME2EUxwdDgh6NVw8e9OzZGwcv3ryZePKmUQodREAN1Ah0BmQTVJZgQFABE1kEl7iAEQWjAiIEOtMW
        aJGyFWgpbWG6PL+vnSmFDrL0KyHaf/LPtPNm3vtN37dMSkUVlYwuqMYOJmRz52g1n0UzfF4knKTmmERG
        p1CotEfFsjsTTU8colmuKTmXh70wzfL6S6qxY2L57SsxZ0SBEng2JoyklSzXp1Bo40SE7Qm1xSaXLOJm
        uZMiwtaKj++MlU2yB1aqtadEjK0VBd2G/2/QtAItFFSPQ2HNOKQX+s9dzeMh5Y7/c2o+D8W141BWb4Rb
        93Xr7t3MxEFLHxugd8gCtS2T0PJhBj73LsCwfhkMJicYp5y+Y9+wFd53m6Gh3QSD/BJ0fJ2HG0Va2XyS
        iYLWtZpgbn4FBjkrCIIHtpLb7QXNKH4IB3oYm2xOyURBdQY7rKxuDbhRHo8XvF4vlKBuyOXFJgaaXa4X
        y+5eliXBN5bl8hMDHUDtDleCywO5VWOy+YmAXkcTYWPLcSttdhcsLbt8rQ2W3ekCq83lA8PXScKf2z7O
        huTHJgJaXGsAFyoqCRcc0FjRcBiFzFIdfPqxIEYA5i0C5FePwc1iLTS8nl53H1YXWiXkahABbe+cC/ll
        1BVrLax89luMANQ0TwbOq+7pYcEqiBG/8CogxYNNBPTtF7NYxi8MipcqKf7w+RpoT/9i4PztB6Ngd7jE
        iF+rgjsQDzYR0K6g1mJhUDz+Wjtm4enLKVhddYsRf6y7f9Fb0zLpnTE713VCUlpBaA0ioD0DFrEEoEnl
        BtOMM2QCbSbHitu34OPFX1JGSei2SgT0TdfaGPWg4y/eCnUvTGgDWN4UGK8SPT8XoR5to0vLQuB+86IA
        19C7wMYaREBTUas6vy2AxeYviD1jXoHiR+OQiSZMeeMENL6agqZ30/CkzQR364yQjl5c8Nh2OP3DwuX2
        gH7CDiV18rsTEVDJORWj0IpeREbQvv0dvZjgtyi56yQX1RhAb7T7JlhZg9G3ZMldh00UNJKOgpL2vwlK
        UaoDNMs75RKFYzqX96K8NnyUi2PvEJSiaDVXKJcoXCsZTaWS5Zo3g90x6Pl0w2Ga5Yb+9vS7Mc1q6uNV
        nbFKhm+Qy71jUKyLWboTKGFGMstXo6S1RMxyVTj3FfXIkSRGU4bOCWGDBimGooCgA4pRMhyLAAN/xoUL
        GjGdUWjj0JhNQZNsfl+DSkpQc2fpXM6x70GxFAx3+jIzdFz8GlVUFEVRfwDiv42+8CmffAAAAABJRU5E
        rkJggg==
</value>
  </data>
  <data name="button_github.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
    <value>
        iVBORw0KGgoAAAANSUhEUgAAACoAAAAnCAYAAAB5XdqFAAABgGlDQ1BJQ0MgcHJvZmlsZQAAKM+VkU8o
        BFEcxz8GESsHmySHOeBECclRS6Rsaa2yOJiZtUvtjG1m5eKoXJWDPxeLg4szVwdXpZQ/JRdXJ+Iijd+b
        VbupVV693qfve99v730faPmMZXtVPWA7OTc2FtFnErN6zTO1aDRTD4blZaNTo3HKjo9bKtR6062y+N9o
        SC56FlTowkNW1s0JLwgPrOWyineFw9aSkRQ+Fe5y5YLC90o3C/yiOB2wpjLDbjw2LBwW1tMlbJawteTa
        wv3C7UnbkXxtpsBJxeuK7cyq9XNP9cLQojM9pXSZbYwxTpRJdExWWSZDjm5ZHVE8YrIfKeNvDfyT4jLF
        tYwljhFWsDECP+oPfnfrpfp6C0mhCFQ/+f5bB9Rsw9eW738e+v7XEVQ+woVT9K/kYfBd9K2i1n4AjRtw
        dlnUzB0434SWh6zhGoFUKVNLpeD1RL4pAU3XUDdX6O1nn+M7iEtXE1ewtw+dacmeL/Pu2tLe/jwT9Efk
        G/NTcnSGxsDZAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAB3RJTUUH4wgCFgswG/t2YwAABb5JREFUWEfF
        mHlIZWUYh9Vcs2zazWm3orLG9qKVKa1sj6agjGnRVqwo2qCdpFLaCNoksj3BjKKmzVbK9oWQIK0kYeyP
        qEgTpGz6eh7zytF77qI2+oMHvPeee877vfs1Zy1qK9j1vz8XVrmTZKMSuAlugzzfWEgVweawzsSr1PIw
        O8Ib8BAUwoJKA6vhSEj38AI4A56Ge6AUFlybwo1wKaQydhN4HM6Bq2E9WHDprX3hVaiFmTlrPh4OnbAM
        6mBdWBRtDJfD65N/R2Ue3wlng56tgmxz1ENbhLvDbpOv56V82BM+gJUQreqNQG/qVQ1cApmq3s/9nrl/
        B9wNHvYgmGasL2Zr/YbQBO9AhW9Majt4EzxIunv6mQfZHhrgObgPTgA9qtHXwF4wJcNXCcfAEXAg7AM7
        wwYQJ71qWJ+C0yBxWG/8Htie4mRqbAsnwSPQAxbeqbADWHjWwTagLeeCh5+QX94aDoHj4XS4EKxuT3k0
        xBWE4fJGj0Ex2L4M+buwFKIyvLa2F+Br+AIehFNgJ1gfoinivbaEg8HoTH2WCIU5tQUYEj3mg28GDd4D
        9GRCXu/h9KDe0BN65i2IFpn39qHPw6Ngj9UAPWuPTZXDvl8OHsR7x8qb+6GhNwXOg9fgXtgPvIkYYqv/
        TLBiL4ZVUAYJGc5boBv2ByPhIX1GJnmN99LjGaUXXTBOhk9gNZjsGuoAsEJfnvz7djDnNFppUD0Y6mNh
        rlMqGsmU8kQXwQNwLRjqPjAtDM0V8CNo/LOg4eZ9Ime/Ar+fqjDnLd3umtYBH8OT8C28Ahr7A3wGP8Ma
        +AmG4RlwebEDfArmtzmaTahnLU/vdPkc3IDsBuamufol/AIaF2IYB439EMxXR6mhE7uH3p63THxzycQ3
        rwzrAaCHbEFWtzmYysgEft4LHtBu4YJiz30fbFHHgUU1Ky97scVi/3wbDKFj0FZj6C2AiRsWFBQsr6io
        +LWwsDDOuCh/gimiYd+DKeBotPr1tqnzEtj67NO2IKdd2qKx5zm3/wBDfSUcCo7GiS+WlpbmNDQ0LOns
        7FzV29sb2tvbQ2VlZcjLywvV1dWhqqoq5ObmRg39CzSwHS4Ai8rGrXedfBaeE80iuxV0jFE0n/V0rBxX
        nvB6MOzTvKgwIqerq2vF6Ojo+MjISGhubg51dXWhpaUlDAwMhJ6enonXXJqgH54A91ZHoCllqxJTyMFi
        FN2SNNyV0V7cCpdB7K+IXeBhcL7bC5MuKisry8GbK8fGxtagMDQ0FFpbW8Pg4GAYHx8PbW1tobGxMZSU
        lGjkb+AQcEJ9B47OOOkII+YhnIYuI853I2oBJmlvuAucz7HJrUfr6+uXYezqvr6+gHdDbW1t6O/vD2p4
        eDh0dHSEoqKiMS7XK65r5p4/RRwCsR6KyOfaDSzWGrAIk76j611AXHTTaSnF9FF5eXnIz8+fyMmmpqbQ
        3d0dOECoqan5m2teBIvQInFfcMO3RemtbGRa6E2jkGToYWAbmrmtz5S91amjQVP5WFxcbFFZiF2gN5xe
        l4APdRe4AczXbMenxWSdJHUAK9LKzDTiXFLMITuDxv4Dv4ON/SpwI9cwW5xFqUccv1a4O4IzPza1Zsgd
        wpy18KbJluHSnFgk0snGfyJcB4b3fDgK9IBF4UG+AcOnUT7UHun1dharPRtpZNJq5xy2TWRKeOU1Gqsh
        9kWT34f7vqGyxenRzSAhD7AcXGqm/bSYrXRz9MbZSMMkGkoNuh9s4tHoeI292m3+LN+Yq0zelBv0LOTv
        LovGVDLkUVlYetOlJin3stWcvxiRhq0A/zMSNwL1qr/LzOe5Ls//i3y4Td5Wlyo6dhVXxUX7z4myuBwa
        KacbcvJYtItmqGF337R3moup5AH86ZLumrUqR6//1bCyU3kzITvFzEJbMLl1ubEvau5lkk3eEezoXDRP
        ZSNnuf0xq38QLKYcn/7WyZSb81ROzr/0gUC6FNUQmwAAAABJRU5ErkJggg==
</value>
  </data>
  <metadata name="toolTip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
    <value>17, 17</value>
  </metadata>
  <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
    <value>
[encoded data, 90196 characters]
</value>
  </data>
</root>


code:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup Label="ProjectConfigurations">
    <ProjectConfiguration Include="Debug|Win32">
      <Configuration>Debug</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|Win32">
      <Configuration>Release</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Debug|x64">
      <Configuration>Debug</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|x64">
      <Configuration>Release</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
  </ItemGroup>
  <PropertyGroup Label="Globals">
    <VCProjectVersion>16.0</VCProjectVersion>
    <ProjectGuid>{3FD6ACA9-E613-4FD6-BDA2-55A91C2CF65C}</ProjectGuid>
    <Keyword>Win32Proj</Keyword>
    <RootNamespace>Patches</RootNamespace>
    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v142</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v142</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v142</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v142</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
  <ImportGroup Label="ExtensionSettings">
  </ImportGroup>
  <ImportGroup Label="Shared">
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <LinkIncremental>false</LinkIncremental>
    <TargetExt>.dva</TargetExt>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <LinkIncremental>true</LinkIncremental>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <LinkIncremental>true</LinkIncremental>
    <TargetExt>.dva</TargetExt>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <LinkIncremental>false</LinkIncremental>
  </PropertyGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <ClCompile>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <Optimization>MaxSpeed</Optimization>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>NDEBUG;PATCHES_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
      <LanguageStandard>stdcpp17</LanguageStandard>
      <AdditionalIncludeDirectories>..\..\..\dependencies\freeglut\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
      <AdditionalLibraryDirectories>..\..\..\dependencies\freeglut\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <ClCompile>
      <PrecompiledHeader>Use</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <Optimization>Disabled</Optimization>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>WIN32;_DEBUG;PATCHES_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <ClCompile>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <Optimization>Disabled</Optimization>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>_DEBUG;PATCHES_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeaderFile>
      </PrecompiledHeaderFile>
      <AdditionalIncludeDirectories>..\..\..\dependencies\freeglut\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
      <LanguageStandard>stdcpp17</LanguageStandard>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
      <AdditionalLibraryDirectories>..\..\..\dependencies\freeglut\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <ClCompile>
      <PrecompiledHeader>Use</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <Optimization>MaxSpeed</Optimization>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>WIN32;NDEBUG;PATCHES_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
    </Link>
  </ItemDefinitionGroup>
  <ItemGroup>
    <ClCompile Include="dllmain.cpp" />
  </ItemGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
  <ImportGroup Label="ExtensionTargets">
  </ImportGroup>
</Project>


code:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Filter Include="Source Files">
      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
    </Filter>
    <Filter Include="Header Files">
      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
      <Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
    </Filter>
    <Filter Include="Resource Files">
      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
    </Filter>
  </ItemGroup>
  <ItemGroup>
    <ClCompile Include="dllmain.cpp">
      <Filter>Source Files</Filter>
    </ClCompile>
  </ItemGroup>
</Project>


code:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup Label="ProjectConfigurations">
    <ProjectConfiguration Include="Debug|Win32">
      <Configuration>Debug</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|Win32">
      <Configuration>Release</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Debug|x64">
      <Configuration>Debug</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|x64">
      <Configuration>Release</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
  </ItemGroup>
  <PropertyGroup Label="Globals">
    <VCProjectVersion>16.0</VCProjectVersion>
    <ProjectGuid>{89F87459-768F-4638-9267-0F90CD74452D}</ProjectGuid>
    <Keyword>Win32Proj</Keyword>
    <RootNamespace>Render</RootNamespace>
    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v142</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v142</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v142</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v142</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
  <ImportGroup Label="ExtensionSettings">
  </ImportGroup>
  <ImportGroup Label="Shared">
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <LinkIncremental>false</LinkIncremental>
    <TargetExt>.dva</TargetExt>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <LinkIncremental>true</LinkIncremental>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <LinkIncremental>true</LinkIncremental>
    <TargetExt>.dva</TargetExt>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <LinkIncremental>false</LinkIncremental>
  </PropertyGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <ClCompile>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <Optimization>MaxSpeed</Optimization>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>NDEBUG;RENDER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeaderFile>
      </PrecompiledHeaderFile>
      <AdditionalIncludeDirectories>..\..\..\dependencies\freeglut\include;..\..\..\dependencies\detours\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
      <LanguageStandard>stdcpp17</LanguageStandard>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
      <AdditionalLibraryDirectories>..\..\..\dependencies\detours\lib;..\..\..\dependencies\freeglut\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <ClCompile>
      <PrecompiledHeader>Use</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <Optimization>Disabled</Optimization>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>WIN32;_DEBUG;RENDER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <ClCompile>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <Optimization>Disabled</Optimization>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>_DEBUG;RENDER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeaderFile>
      </PrecompiledHeaderFile>
      <LanguageStandard>stdcpp17</LanguageStandard>
      <AdditionalIncludeDirectories>..\..\..\dependencies\freeglut\include;..\..\..\dependencies\detours\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
      <AdditionalLibraryDirectories>..\..\..\dependencies\detours\lib;..\..\..\dependencies\freeglut\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <ClCompile>
      <PrecompiledHeader>Use</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <Optimization>MaxSpeed</Optimization>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>WIN32;NDEBUG;RENDER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
    </Link>
  </ItemDefinitionGroup>
  <ItemGroup>
    <ClInclude Include="framework.h" />
  </ItemGroup>
  <ItemGroup>
    <ClCompile Include="dllmain.cpp" />
  </ItemGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
  <ImportGroup Label="ExtensionTargets">
  </ImportGroup>
</Project>


code:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Filter Include="Source Files">
      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
    </Filter>
    <Filter Include="Header Files">
      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
      <Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
    </Filter>
    <Filter Include="Resource Files">
      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
    </Filter>
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="framework.h">
      <Filter>Header Files</Filter>
    </ClInclude>
  </ItemGroup>
  <ItemGroup>
    <ClCompile Include="dllmain.cpp">
      <Filter>Source Files</Filter>
    </ClCompile>
  </ItemGroup>
</Project>


code:
#include "framework.h"
#include <detours.h>
#pragma comment(lib, "detours.lib")
#include <GL\freeglut.h>
#include <GL\GL.h>
#include <wingdi.h>
#include <windows.h>
#include <timeapi.h>
#include <iostream>
#include <thread>

#include <chrono>

using namespace std::chrono;

int hookedCreateWindow(const char* title, void(__cdecl* exit_function)(int))
{
	SetProcessDPIAware();

	// trick Optimus into switching to the NVIDIA GPU
	if (cuInit != NULL) cuInit(0);

	if (nDisplay == 2) // fullscreen
	{
		char GameModeString[24];
		sprintf_s(GameModeString, sizeof(GameModeString), "%dx%d:%d@%d", nWidth, nHeight, nBitDepth, nRefreshRate);
		glutGameModeString(GameModeString);

		if (glutGameModeGet(GLUT_GAME_MODE_POSSIBLE)) {
			printf("[Render Manager] Game mode (exclusive fullscreen) enabled.\n");
			printf(GameModeString);
			printf("\n");
			glutEnterGameMode();
		}
		else {
			printf("[Render Manager] Requested display mode not supported. Using non-exclusive fullscreen instead.\n");
			printf(GameModeString);
			printf("\n");
			*fullScreenFlag = 1;
			glutCreateWindow(title);
		}
	}
	else // windowed or borderless
	{
		*fullScreenFlag = 0;

		RECT xy;
		SystemParametersInfo(SPI_GETWORKAREA, 0, &xy, 0);
		int screenWidth = xy.right - xy.left;
		int screenHeight = xy.bottom - xy.top;
		if (nWidth > screenWidth)
		{
			screenWidth = GetSystemMetrics(SM_CXSCREEN);
			xy.left = 0;
		}

		if (nHeight > screenHeight)
		{
			screenHeight = GetSystemMetrics(SM_CYSCREEN);
			xy.top = 0;
		}
		int wndX = xy.left + (screenWidth - nWidth) / 2;
		int wndY = xy.top + (screenHeight - nHeight) / 2;

		glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);

		glutInitWindowSize(nWidth, nHeight);
		glutInitWindowPosition(wndX, wndY);
		glutCreateWindow(title);

		if (nDisplay == 1) // borderless mode
		{
			// support borderless mode even with original copy of glut
			HDC hDev = wglGetCurrentDC(); // get handle to current opengl device context
			HWND hWnd = WindowFromDC(hDev); // convert it to a window handle

			LONG_PTR wndStyle = WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;  // set no border (visible to ensure something is shown immediately)

			if (nPopupBorderless)
				wndStyle |= WS_POPUP;

			SetWindowLongPtr(hWnd, GWL_STYLE, wndStyle);
			SetWindowPos(hWnd, HWND_TOP, wndX, wndY, nWidth, nHeight, SWP_FRAMECHANGED); // adjust position to apply new style

			printf("[Render Manager] Borderless mode.\n");
		}
		else
		{
			printf("[Render Manager] Windowed mode.\n");
		}
	}

	return true;
}

__int64 hookedParseParameters(int a1, __int64* a2)
{
	// Force -wqhd if Custom Internal Resolution is enabled
	if (nIntRes)
		*resolutionType = 15;
	// Return to the original function
	return divaParseParameters(a1, a2);
}

time_point<high_resolution_clock> sleepUntil = high_resolution_clock::now();
time_point<high_resolution_clock> nextUpdate = high_resolution_clock::now();
nanoseconds expectedFrameDuration;
nanoseconds sleepWindow(2500000);

__int64 __fastcall limiterFuncNormal(__int64 a1)
{
	auto timeNow = high_resolution_clock::now();

	if (timeNow < nextUpdate)
		return 0;

	const auto result = divaEngineUpdate(a1);

	timeNow = high_resolution_clock::now();

	// increment nextUpdate by fixed interval to unsure timing accuracy between frames
	// (ignore time spent outside the update)
	nextUpdate += expectedFrameDuration;

	// if dropping frames, run as fast as possible
	// and also change nextUpdate's timing to match current time
	// (so frame n+2 will be at least 1/nFpsLimit seconds from frame n+1)
	if (nextUpdate < timeNow)
		nextUpdate = timeNow;

	return result;
}

__int64 __fastcall limiterFuncLight(__int64 a1)
{
	auto timeNow = high_resolution_clock::now();

	// sleep until the approximate correct time to save CPU usage,
	// then change to instantly returning when timing is more critical
	if (timeNow < sleepUntil)
	{
		std::this_thread::sleep_until(sleepUntil);

		if (nVerboseLimiter)
		{
			timeNow = high_resolution_clock::now();
			nanoseconds timeDifference = (timeNow - sleepUntil);
			if (timeDifference > sleepWindow) // no need for abs because it doesn't matter if this is early
			{
				printf("FPS LIMITER SLEEP TIME OUTSIDE ALLOWED WINDOW\n");
				printf(" Target sleep until time: %lld, Actual time: %lld\n", sleepUntil.time_since_epoch().count(), timeNow.time_since_epoch().count());
				printf(" (Difference: %lld, Window: %lld)\n", timeDifference.count(), sleepWindow.count());
			}
		}
	}

	if (timeNow < nextUpdate)
		return 0;

	const auto result = divaEngineUpdate(a1);

	timeNow = high_resolution_clock::now();

	// increment nextUpdate by fixed interval to unsure timing accuracy between frames
	// (ignore time spent outside the update)
	nextUpdate += expectedFrameDuration;

	// if dropping frames, run as fast as possible
	// and also change nextUpdate's timing to match current time
	// (so frame n+2 will be at least 1/nFpsLimit seconds from frame n+1)
	if (nextUpdate < timeNow)
		nextUpdate = timeNow;

	sleepUntil = nextUpdate - sleepWindow;

	return result;
}

__int64 __fastcall hookedEngineUpdate(__int64 a1)
{
	if (nFpsLimit < 1)
		return divaEngineUpdate(a1);
	else if (nUseLightLimiter)
		return limiterFuncLight(a1);
	else
		return limiterFuncNormal(a1);
}

extern "C" __declspec(dllexport) int getFramerateLimit(void)
{
	return nFpsLimit;
}

extern "C" __declspec(dllexport) void setFramerateLimit(int framerate)
{
	nFpsLimit = framerate;
	if (nFpsLimit < 1)
		expectedFrameDuration = nanoseconds(0);
	else
		expectedFrameDuration = nanoseconds(1000000000 / nFpsLimit);
	return;
}

BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	if (ul_reason_for_call == DLL_PROCESS_ATTACH)
	{
		DisableThreadLibraryCalls(hModule);
		DetourTransactionBegin();
		DetourUpdateThread(GetCurrentThread());
		DetourAttach(&(PVOID&)divaCreateWindow, hookedCreateWindow);
		DetourTransactionCommit();

		DetourTransactionBegin();
		DetourUpdateThread(GetCurrentThread());
		DetourAttach(&(PVOID&)divaParseParameters, hookedParseParameters);
		DetourTransactionCommit();

		// set sleep time resolution to 2ms or device minimum (whichever's lower)
		TIMECAPS tc;

		if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) == MMSYSERR_NOERROR)
		{
			UINT wTimerRes = min(max(tc.wPeriodMin, 2), tc.wPeriodMax);
			timeBeginPeriod(wTimerRes);
		}

		setFramerateLimit(nFpsLimit);
		DetourTransactionBegin();
		DetourUpdateThread(GetCurrentThread());
		DetourAttach(&(PVOID&)divaEngineUpdate, hookedEngineUpdate);
		DetourTransactionCommit();
	}
	return TRUE;
}


code:
#pragma once

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
#include <string>

static int(__cdecl* divaCreateWindow)(const char* title, void(__cdecl* exitfunc)(int)) = (int(__cdecl*)(const char* title, void(__cdecl * exitfunc)(int)))0x140194D00;
__int64 (__fastcall* divaParseParameters)(int a1, __int64* a2) = (__int64(__fastcall*)(int a1, __int64* a2))0x140193630;
__int64(__fastcall* divaEngineUpdate)(__int64 a1) = (__int64(__fastcall*)(__int64 a1))0x140194CD0;

uint8_t* fullScreenFlag = (uint8_t*)0x140EDA5D1;
DWORD* resolutionType = (DWORD*)0x140EDA5D4;

using namespace std;

wstring DirPath() {
	WCHAR buffer[MAX_PATH];
	GetModuleFileNameW(NULL, buffer, MAX_PATH);
	wstring::size_type pos = wstring(buffer).find_last_of(L"\\/");
	return wstring(buffer).substr(0, pos);
}

wstring CONFIG_FILE_STRING = DirPath() + L"\\plugins\\config.ini";
LPCWSTR CONFIG_FILE = CONFIG_FILE_STRING.c_str();

int nDisplay = GetPrivateProfileIntW(L"resolution", L"display", 0, CONFIG_FILE);
int nWidth = GetPrivateProfileIntW(L"resolution", L"width", 1280, CONFIG_FILE);
int nHeight = GetPrivateProfileIntW(L"resolution", L"height", 720, CONFIG_FILE);

int nIntRes = GetPrivateProfileIntW(L"resolution", L"r.enable", FALSE, CONFIG_FILE);
int nIntResWidth = GetPrivateProfileIntW(L"resolution", L"r.width", 1280, CONFIG_FILE);
int nIntResHeight = GetPrivateProfileIntW(L"resolution", L"r.height", 720, CONFIG_FILE);

int nBitDepth = GetPrivateProfileIntW(L"resolution", L"bitdepth", 32, CONFIG_FILE);
int nRefreshRate = GetPrivateProfileIntW(L"resolution", L"refreshrate", 60, CONFIG_FILE);

int nPopupBorderless = GetPrivateProfileIntW(L"resolution", L"borderless.popup", TRUE, CONFIG_FILE);

int nFpsLimit = GetPrivateProfileIntW(L"graphics", L"fps.limit", 60, CONFIG_FILE);
int nUseLightLimiter = GetPrivateProfileIntW(L"graphics", L"fps.limit.lightmode", TRUE, CONFIG_FILE);
int nVerboseLimiter = GetPrivateProfileIntW(L"graphics", L"fps.limit.verbose", FALSE, CONFIG_FILE);

// used to trick Optimus into switching to the NVIDIA GPU
HMODULE nvcudaModule = LoadLibraryW(L"nvcuda.dll");
// cuInit actually returns a CUresult, but we don't really care about it
void(WINAPI * cuInit)(unsigned int flags) = (void(WINAPI*)(unsigned int flags))GetProcAddress(nvcudaModule, "cuInit");


code:
#include "CameraController.h"
#include <algorithm>
#include "Input/InputState.h"
#include "ComponentsManager.h"
#include "../framework.h"
#include "../Input/Mouse/Mouse.h"
#include "../Input/Keyboard/Keyboard.h"
#include "../Input/Bindings/KeyboardBinding.h"

#define  GLUT_CURSOR_RIGHT_ARROW	0x0000
#define  GLUT_CURSOR_NONE			0x0065

using namespace TLAC::Input;
using namespace TLAC::Utilities;

namespace TLAC::Components
{
	CameraController::CameraController()
	{
	}

	CameraController::~CameraController()
	{
		delete ToggleBinding;

		delete ForwardBinding;
		delete BackwardBinding;
		delete LeftBinding;
		delete RightBinding;

		delete UpBinding;
		delete DownBinding;
		delete ClockwiseBinding;
		delete CounterClockwiseBinding;
		delete ZoomInBinding;
		delete ZoomOutBinding;

		delete FastBinding;
		delete SlowBinding;
	}

	const char* CameraController::GetDisplayName()
	{
		return "camera_controller";
	}

	void CameraController::Initialize(ComponentsManager* manager)
	{
		componentsManager = manager;

		printf("[TLAC] CameraController::Initialize(): Initialized\n");

		for (int i = 0; i < sizeof(cameraSetterAddresses) / sizeof(void*); i++)
		{
			DWORD oldProtect;
			VirtualProtect((void*)cameraSetterAddresses[i], sizeof(uint8_t), PAGE_EXECUTE_READWRITE, &oldProtect);

			originalSetterBytes[i] = *(uint8_t*)cameraSetterAddresses[i];
		}

		ToggleBinding = new Binding();
		ToggleBinding->AddBinding(new KeyboardBinding(VK_F3));

		ForwardBinding = new Binding();
		ForwardBinding->AddBinding(new KeyboardBinding('W'));
		BackwardBinding = new Binding();
		BackwardBinding->AddBinding(new KeyboardBinding('S'));
		LeftBinding = new Binding();
		LeftBinding->AddBinding(new KeyboardBinding('A'));
		RightBinding = new Binding();
		RightBinding->AddBinding(new KeyboardBinding('D'));

		UpBinding = new Binding();
		UpBinding->AddBinding(new KeyboardBinding(VK_SPACE));
		DownBinding = new Binding();
		DownBinding->AddBinding(new KeyboardBinding(VK_CONTROL));

		ClockwiseBinding = new Binding();
		ClockwiseBinding->AddBinding(new KeyboardBinding('E'));
		CounterClockwiseBinding = new Binding();
		CounterClockwiseBinding->AddBinding(new KeyboardBinding('Q'));

		ZoomInBinding = new Binding();
		ZoomInBinding->AddBinding(new KeyboardBinding('R'));
		ZoomOutBinding = new Binding();
		ZoomOutBinding->AddBinding(new KeyboardBinding('F'));

		FastBinding = new Binding();
		FastBinding->AddBinding(new KeyboardBinding(VK_SHIFT));
		SlowBinding = new Binding();
		SlowBinding->AddBinding(new KeyboardBinding(VK_MENU));

		camera = (Camera*)CAMERA_ADDRESS;
	}

	void CameraController::Update()
	{
		return;
	}

	void CameraController::UpdateInput()
	{
		if (ToggleBinding->AnyTapped())
		{
			SetControls(!GetIsEnabled());
			return;
		}

		if (!GetIsEnabled())
			return;

		auto keyboard = Keyboard::GetInstance();
		auto mouse = Mouse::GetInstance();

		bool forward = ForwardBinding->AnyDown();
		bool backward = BackwardBinding->AnyDown();
		bool left = LeftBinding->AnyDown();
		bool right = RightBinding->AnyDown();

		bool up = UpBinding->AnyDown();
		bool down = DownBinding->AnyDown();

		bool fast = FastBinding->AnyDown();
		bool slow = SlowBinding->AnyDown();

		bool clockwise = ClockwiseBinding->AnyDown();
		bool counterclockwise = CounterClockwiseBinding->AnyDown();

		bool zoomin = ZoomInBinding->AnyDown();
		bool zoomout = ZoomOutBinding->AnyDown();

		float speed = GetElapsedTime() * (fast ? fastSpeed : slow ? slowSpeed : normalSpeed);

		if (forward ^ backward)
			camera->Position += PointFromAngle(verticalRotation + (forward ? +0.0f : -180.0f), speed);

		if (left ^ right)
			camera->Position += PointFromAngle(verticalRotation + (right ? +90.0f : -90.0f), speed);

		if (up ^ down)
			camera->Position.Y += speed * (up ? +0.25f : -0.25f);

		if (clockwise ^ counterclockwise)
			camera->Rotation += speed * (clockwise ? -1.0f : +1.0f);

		if (zoomin ^ zoomout)
		{
			camera->HorizontalFov += speed * (zoomin ? -1.0f : +1.0f);
			camera->HorizontalFov = std::clamp(camera->HorizontalFov, +1.0f, +170.0f);
		}

		if (mouse->HasMoved())
		{
			SetMouseWindowCenter();

			auto delta = mouse->GetDeltaPosition();

			verticalRotation += delta.x * sensitivity;
			horizontalRotation -= delta.y * (sensitivity / 5.0f);

			horizontalRotation = std::clamp(horizontalRotation, -75.0f, +75.0f);
		}

		((InputState*)*(uint64_t*)INPUT_STATE_PTR_ADDRESS)->HideCursor();

		Vec2 focus = PointFromAngle(verticalRotation, 1.0f);
		camera->Focus.X = camera->Position.X + focus.X;
		camera->Focus.Z = camera->Position.Z + focus.Y;

		camera->Focus.Y = camera->Position.Y + PointFromAngle(horizontalRotation, 5.0f).X;
	}

	void CameraController::SetControls(bool value)
	{
		if (GetIsEnabled() == value)
			return;

		SetIsEnabled(value);
		componentsManager->SetUpdateGameInput(!value);

		printf("[TLAC] CameraController::SetControls(): enabled = %s\n", GetIsEnabled() ? "true" : "false");

		typedef void __stdcall _glutSetCursor(int);
		auto glutSetCursor = (_glutSetCursor*)GLUT_SET_CURSOR_ADDRESS;

		// hide cursor
		glutSetCursor(value ? GLUT_CURSOR_NONE : GLUT_CURSOR_RIGHT_ARROW);

		if (value)
		{
			// disable camera setters
			for (int i = 0; i < sizeof(cameraSetterAddresses) / sizeof(void*); i++)
				*(uint8_t*)cameraSetterAddresses[i] = RET_OPCODE;

			// set initial camera angle
			Vec2 camXz = Vec2(camera->Position.X, camera->Position.Z);
			Vec2 focusXz = Vec2(camera->Focus.X, camera->Focus.Z);
			verticalRotation = AngleFromPoints(camXz, focusXz);

			horizontalRotation = 0;
			camera->Rotation = defaultRotation;
			camera->HorizontalFov = defaultFov;
		}
		else
		{
			// restore camera setters
			for (int i = 0; i < sizeof(cameraSetterAddresses) / sizeof(void*); i++)
				*(uint8_t*)cameraSetterAddresses[i] = originalSetterBytes[i];
		}
	}

	void CameraController::SetMouseWindowCenter()
	{
		RECT windowRect = framework::GetWindowBounds();

		int centerX = windowRect.left + (windowRect.right - windowRect.left) / 2;
		int centerY = windowRect.top + (windowRect.bottom - windowRect.top) / 2;

		Mouse::GetInstance()->SetPosition(centerX, centerY);
	}

	bool CameraController::GetIsEnabled()
	{
		return isEnabled;
	}

	void CameraController::SetIsEnabled(bool value)
	{
		isEnabled = value;
	}
}


code:
#pragma once
#include "EmulatorComponent.h"
#include "../Constants.h"
#include "../Input/Bindings/Binding.h"
#include "../Utilities/Math.h"

namespace TLAC::Components
{
	struct Camera
	{
		Utilities::Vec3 Position;
		Utilities::Vec3 Focus;
		float Rotation;
		float HorizontalFov;
		float VerticalFov;
	};

	class CameraController : public EmulatorComponent
	{
	public:
		Input::Binding* ToggleBinding;

		Input::Binding* ForwardBinding;
		Input::Binding* BackwardBinding;
		Input::Binding* LeftBinding;
		Input::Binding* RightBinding;

		Input::Binding* UpBinding;
		Input::Binding* DownBinding;
		Input::Binding* FastBinding;
		Input::Binding* SlowBinding;

		Input::Binding* ClockwiseBinding;
		Input::Binding* CounterClockwiseBinding;

		Input::Binding* ZoomInBinding;
		Input::Binding* ZoomOutBinding;

		CameraController();
		~CameraController();

		virtual const char* GetDisplayName() override;

		virtual void Initialize(ComponentsManager*) override;
		virtual void Update() override;
		virtual void UpdateInput() override;

		void SetControls(bool value);

		bool GetIsEnabled();

	private:
		const float fastSpeed = 0.1f;
		const float slowSpeed = 0.0005f;
		const float normalSpeed = 0.005f;

		const float defaultRotation = 0.0f;
		const float defaultFov = 70.0f;
		const float sensitivity = 0.25f;

		ComponentsManager* componentsManager;
		float verticalRotation;
		float horizontalRotation;

		bool isEnabled;
		Camera* camera;

		uint8_t originalSetterBytes[4];
		void* cameraSetterAddresses[4] =
		{
			(void*)CAMERA_POS_SETTER_ADDRESS,
			(void*)CAMERA_INTR_SETTER_ADDRESS,
			(void*)CAMERA_ROT_SETTER_ADDRESS,
			(void*)CAMERA_PERS_SETTER_ADDRESS,
		};

		void SetMouseWindowCenter();
		void SetIsEnabled(bool value);
	};
}


code:
#include "ComponentsManager.h"
#include "../FileSystem/ConfigFile.h"
#include "../framework.h"
#include "Input/InputEmulator.h"
#include "Input/TouchSliderEmulator.h"
#include "Input/TouchPanelEmulator.h"
#include "SysTimer.h"
#include "PlayerDataManager.h"
#include "FrameRateManager.h"
#include "FastLoader.h"
#include "StageManager.h"
#include "CameraController.h"
#include "DebugComponent.h"
#include "ScaleComponent.h"
#include "ScoreSaver.h"
#include "GameTargets/TargetInspector.h"

using ConfigFile = TLAC::FileSystem::ConfigFile;

namespace TLAC::Components
{
	typedef void EngineUpdateInput(void*);

	ComponentsManager::ComponentsManager()
	{
	}

	ComponentsManager::~ComponentsManager()
	{
	}

	void ComponentsManager::ParseAddComponents()
	{
		EmulatorComponent* allComponents[]
		{
			new TargetInspector(),
			new InputEmulator(),
			new TouchSliderEmulator(),
			new TouchPanelEmulator(),
			new SysTimer(),
			new PlayerDataManager(),
			new FrameRateManager(),
			new FastLoader(),
			new ScaleComponent(),
			new ScoreSaver(),
			new StageManager(),
			new CameraController(),
			new DebugComponent(),
		};

		ConfigFile componentsConfig(framework::GetModuleDirectory(), COMPONENTS_CONFIG_FILE_NAME);
		bool success = componentsConfig.OpenRead();

		if (!success)
		{
			printf("ComponentsManager::ParseAddComponents(): Unable to parse %s\n", COMPONENTS_CONFIG_FILE_NAME.c_str());
			return;
		}

		size_t componentCount = sizeof(allComponents) / sizeof(EmulatorComponent*);
		components.reserve(componentCount);

		std::string trueString = "true", falseString = "false";

		for (int i = 0; i < componentCount; i++)
		{
			std::string* value;

			auto name = allComponents[i]->GetDisplayName();
			//printf("ComponentsManager::ParseAddComponents(): searching name: %s\n", name);

			if (componentsConfig.TryGetValue(name, &value))
			{
				//printf("ComponentsManager::ParseAddComponents(): %s found\n", name);

				if (*value == trueString)
				{
					//printf("ComponentsManager::ParseAddComponents(): enabling %s...\n", name);
					components.push_back(allComponents[i]);
				}
				else if (*value == falseString)
				{
					//printf("ComponentsManager::ParseAddComponents(): disabling %s...\n", name);
				}
				else
				{
					//printf("ComponentsManager::ParseAddComponents(): invalid value %s for component %s\n", value, name);
				}

				delete value;
			}
			else
			{
				//printf("ParseAddComponents(): component %s not found\n", name);
				delete allComponents[i];
			}
		}
	}

	void ComponentsManager::Initialize()
	{
		dwGuiDisplay = (DwGuiDisplay*) * (uint64_t*)DW_GUI_DISPLAY_INSTANCE_PTR_ADDRESS;

		ParseAddComponents();
		updateStopwatch.Start();

		for (auto& component : components)
			component->Initialize(this);
	}

	void ComponentsManager::Update()
	{
		elpasedTime = updateStopwatch.Restart();

		for (auto& component : components)
		{
			component->SetElapsedTime(elpasedTime);
			component->Update();
		}
	}

	void ComponentsManager::UpdateInput()
	{
		if (!GetIsInputEmulatorUsed())
		{
			uint64_t* inputStatePtr = (uint64_t*)INPUT_STATE_PTR_ADDRESS;

			// poll input using the original PollInput function we overwrote with the update hook instead
			if (inputStatePtr != nullptr)
				((EngineUpdateInput*)ENGINE_UPDATE_INPUT_ADDRESS)((void*)* inputStatePtr);
		}

		for (auto& component : components)
			component->UpdateInput();
	}

	void ComponentsManager::OnFocusGain()
	{
		for (auto& component : components)
			component->OnFocusGain();
	}

	void ComponentsManager::OnFocusLost()
	{
		for (auto& component : components)
			component->OnFocusLost();
	}

	void ComponentsManager::Dispose()
	{
		for (auto& component : components)
			delete component;
	}
}