diff --git a/src/client/launcher/html/dispatch.hpp b/src/client/launcher/html/dispatch.hpp
new file mode 100644
index 00000000..e6f0bde2
--- /dev/null
+++ b/src/client/launcher/html/dispatch.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+
+class dispatch : public IDispatch
+{
+public:
+ virtual ~dispatch() = default;
+
+ HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT* pctinfo) override
+ {
+ return S_FALSE;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) override
+ {
+ return S_FALSE;
+ }
+
+};
diff --git a/src/client/launcher/html/doc_host_ui_handler.hpp b/src/client/launcher/html/doc_host_ui_handler.hpp
new file mode 100644
index 00000000..fcd5a707
--- /dev/null
+++ b/src/client/launcher/html/doc_host_ui_handler.hpp
@@ -0,0 +1,92 @@
+#pragma once
+
+class doc_host_ui_handler /*_boilerplate*/ : public IDocHostUIHandler
+{
+public:
+ virtual ~doc_host_ui_handler() = default;
+
+ HRESULT STDMETHODCALLTYPE ShowContextMenu(
+ DWORD,
+ POINT*,
+ IUnknown*,
+ IDispatch*) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE ShowUI(
+ DWORD,
+ IOleInPlaceActiveObject*,
+ IOleCommandTarget*,
+ IOleInPlaceFrame*,
+ IOleInPlaceUIWindow*) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE HideUI() override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE UpdateUI() override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE EnableModeless(BOOL) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnDocWindowActivate(BOOL) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnFrameWindowActivate(BOOL) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE ResizeBorder(
+ LPCRECT,
+ IOleInPlaceUIWindow*,
+ BOOL) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE TranslateAccelerator(
+ LPMSG,
+ const GUID*,
+ DWORD) override
+ {
+ return S_FALSE;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetOptionKeyPath(LPOLESTR __RPC_FAR* pchKey, DWORD dw) override
+ {
+ return S_FALSE;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetDropTarget(
+ IDropTarget*,
+ IDropTarget**) override
+ {
+ return S_FALSE;
+ }
+
+
+ HRESULT STDMETHODCALLTYPE TranslateUrl(DWORD, OLECHAR*, OLECHAR** ppchURLOut) override
+ {
+ *ppchURLOut = nullptr;
+ return S_FALSE;
+ }
+
+ HRESULT STDMETHODCALLTYPE FilterDataObject(IDataObject*, IDataObject** ppDORet) override
+ {
+ *ppDORet = nullptr;
+ return S_FALSE;
+ }
+};
diff --git a/src/client/launcher/html/html_argument.cpp b/src/client/launcher/html/html_argument.cpp
new file mode 100644
index 00000000..30d72fa8
--- /dev/null
+++ b/src/client/launcher/html/html_argument.cpp
@@ -0,0 +1,79 @@
+#include
+#include "html_argument.hpp"
+
+html_argument::html_argument(VARIANT* val)
+ : html_argument(*val)
+{
+}
+
+html_argument::html_argument(CComVariant val)
+ : value_(std::move(val))
+{
+}
+
+bool html_argument::is_empty() const
+{
+ return this->value_.vt == VT_EMPTY;
+}
+
+bool html_argument::is_string() const
+{
+ if (this->is_empty()) return false;
+ return this->value_.vt == VT_BSTR;
+}
+
+bool html_argument::is_number() const
+{
+ if (this->is_empty()) return false;
+ return this->value_.vt == VT_I4;
+}
+
+bool html_argument::is_bool() const
+{
+ if (this->is_empty()) return false;
+ return this->value_.vt == VT_BOOL;
+}
+
+std::string html_argument::get_string() const
+{
+ if (!this->is_string()) return {};
+ std::wstring wide_string(this->value_.bstrVal);
+ return std::string(wide_string.begin(), wide_string.end());
+}
+
+int html_argument::get_number() const
+{
+ if (!this->is_number()) return 0;
+ return this->value_.intVal;
+}
+
+bool html_argument::get_bool() const
+{
+ if (!this->is_bool()) return false;
+ return this->value_.boolVal != FALSE;
+}
+
+void html_argument::copy_to(VARIANT& var) const
+{
+ if (this->is_empty())
+ {
+ VariantInit(&var);
+ }
+ else
+ {
+ (void)VariantCopy(&var, &this->value_);
+ }
+}
+
+void html_argument::move_to(VARIANT* var)
+{
+ if (!var)
+ {
+ return;
+ }
+
+ VARIANT& src_var = this->value_;
+ memcpy(var, &src_var, sizeof(*var));
+
+ VariantInit(&this->value_);
+}
diff --git a/src/client/launcher/html/html_argument.hpp b/src/client/launcher/html/html_argument.hpp
new file mode 100644
index 00000000..26007ad3
--- /dev/null
+++ b/src/client/launcher/html/html_argument.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#pragma once
+
+class html_argument final
+{
+public:
+ html_argument() = default;
+ html_argument(VARIANT* val);
+ html_argument(CComVariant val);
+
+ bool is_empty() const;
+
+ bool is_string() const;
+ bool is_number() const;
+ bool is_bool() const;
+
+ std::string get_string() const;
+ int get_number() const;
+ bool get_bool() const;
+
+ const CComVariant& get() const
+ {
+ return this->value_;
+ }
+
+ void copy_to(VARIANT& var) const;
+ void move_to(VARIANT* var);
+
+private:
+ CComVariant value_{};
+};
diff --git a/src/client/launcher/html/html_frame.cpp b/src/client/launcher/html/html_frame.cpp
new file mode 100644
index 00000000..21d19fc8
--- /dev/null
+++ b/src/client/launcher/html/html_frame.cpp
@@ -0,0 +1,371 @@
+#include
+#include "html_frame.hpp"
+#include "utils/nt.hpp"
+#include "utils/hook.hpp"
+
+
+namespace
+{
+ void* original_func{};
+ GUID browser_emulation_guid{0xac969931, 0x3566, 0x4b50, {0xae, 0x48, 0x71, 0xb9, 0x6a, 0x75, 0xc8, 0x79}};
+
+ int WINAPI co_internet_feature_value_internal_stub(const GUID* guid, uint32_t* result)
+ {
+ const auto res = static_cast(original_func)(guid, result);
+
+ if (IsEqualGUID(*guid, browser_emulation_guid))
+ {
+ *result = 11000;
+ return 0;
+ }
+
+ return res;
+ }
+
+ void setup_ie_hook()
+ {
+ static const auto _ = []
+ {
+ const auto urlmon = utils::nt::library::load("urlmon.dll"s);
+ const auto target = urlmon.get_iat_entry("iertutil.dll", MAKEINTRESOURCEA(700));
+
+ original_func = *target;
+ utils::hook::set(target, co_internet_feature_value_internal_stub);
+
+ return 0;
+ }();
+ (void)_;
+ }
+
+ void setup_ole()
+ {
+ static struct ole_initialzer
+ {
+ ole_initialzer()
+ {
+ if (OleInitialize(nullptr) != S_OK)
+ {
+ throw std::runtime_error("Unable to initialize the OLE library");
+ }
+ }
+
+ ~ole_initialzer()
+ {
+ OleUninitialize();
+ }
+ } init;
+ (void)init;
+ }
+}
+
+html_frame::html_frame()
+{
+ setup_ie_hook();
+ setup_ole();
+}
+
+HRESULT html_frame::GetHostInfo(DOCHOSTUIINFO* pInfo)
+{
+ pInfo->cbSize = sizeof(DOCHOSTUIINFO);
+ pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER | DOCHOSTUIFLAG_DPI_AWARE | DOCHOSTUIFLAG_SCROLL_NO;
+ pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
+
+ return S_OK;
+}
+
+HRESULT html_frame::GetWindow(HWND* lphwnd)
+{
+ *lphwnd = this->window_;
+ return S_OK;
+}
+
+HRESULT html_frame::QueryInterface(REFIID riid, void** ppvObject)
+{
+ if (IsEqualGUID(riid, IID_IDispatch))
+ {
+ *ppvObject = static_cast(this);
+ return S_OK;
+ }
+
+ if (IsEqualGUID(riid, IID_IDispatch))
+ {
+ const auto d = get_dispatch();
+ if (!d)
+ {
+ return E_NOINTERFACE;
+ }
+
+ (*d).AddRef();
+ *ppvObject = &*d;
+ return S_OK;
+ }
+
+ if (IsEqualGUID(riid, IID_IServiceProvider))
+ {
+ *ppvObject = static_cast(this);
+ return S_OK;
+ }
+
+ if (IsEqualGUID(riid, IID_IInternetSecurityManager))
+ {
+ *ppvObject = static_cast(this);
+ return S_OK;
+ }
+
+ if (IsEqualGUID(riid, IID_IUnknown))
+ {
+ *ppvObject = static_cast(static_cast(this));
+ return S_OK;
+ }
+
+ if (IsEqualGUID(riid, IID_IOleClientSite))
+ {
+ *ppvObject = static_cast(this);
+ return S_OK;
+ }
+
+ if (IsEqualGUID(riid, IID_IOleInPlaceSite))
+ {
+ *ppvObject = static_cast(this);
+ return S_OK;
+ }
+
+ if (IsEqualGUID(riid, IID_IOleInPlaceFrame))
+ {
+ *ppvObject = static_cast(this);
+ return S_OK;
+ }
+
+ if (IsEqualGUID(riid, IID_IDocHostUIHandler))
+ {
+ *ppvObject = static_cast(this);
+ return S_OK;
+ }
+
+ if (IsEqualGUID(riid, IID_IOleInPlaceObject) && this->browser_object_)
+ {
+ return this->browser_object_->QueryInterface(riid, ppvObject);
+ }
+
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+}
+
+HWND html_frame::get_window() const
+{
+ return this->window_;
+}
+
+CComPtr html_frame::get_browser_object() const
+{
+ return this->browser_object_;
+}
+
+CComPtr html_frame::get_web_browser() const
+{
+ CComPtr web_browser{};
+ if (!this->browser_object_ || FAILED(this->browser_object_.QueryInterface(&web_browser)))
+ {
+ return {};
+ }
+
+ return web_browser;
+}
+
+CComPtr html_frame::get_dispatch() const
+{
+ const auto web_browser = this->get_web_browser();
+
+ CComPtr dispatch{};
+ if (!web_browser || FAILED(web_browser->get_Document(&dispatch)))
+ {
+ return {};
+ }
+
+ return dispatch;
+}
+
+CComPtr html_frame::get_document() const
+{
+ const auto dispatch = this->get_dispatch();
+
+ CComPtr document{};
+ if (!dispatch || FAILED(dispatch.QueryInterface(&document)))
+ {
+ return {};
+ }
+
+ return document;
+}
+
+void html_frame::initialize(const HWND window)
+{
+ if (this->window_) return;
+ this->window_ = window;
+
+ this->create_browser();
+ this->initialize_browser();
+}
+
+void html_frame::create_browser()
+{
+ CComPtr class_factory{};
+ if (FAILED(
+ CoGetClassObject(CLSID_WebBrowser, CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, nullptr, IID_IClassFactory,
+ reinterpret_cast(&class_factory))))
+ {
+ throw std::runtime_error("Unable to get the class factory");
+ }
+
+ class_factory->CreateInstance(nullptr, IID_IOleObject, reinterpret_cast(&this->browser_object_));
+
+ if (!this->browser_object_)
+ {
+ throw std::runtime_error("Unable to create browser object");
+ }
+}
+
+void html_frame::initialize_browser()
+{
+ this->browser_object_->SetClientSite(this);
+ this->browser_object_->SetHostNames(L"Hostname", nullptr);
+
+ RECT rect;
+ GetClientRect(this->get_window(), &rect);
+ OleSetContainedObject(this->browser_object_, TRUE);
+
+ this->browser_object_->DoVerb(OLEIVERB_SHOW, nullptr, this, -1, this->get_window(), &rect);
+ this->resize(rect.right, rect.bottom);
+}
+
+void html_frame::resize(const DWORD width, const DWORD height) const
+{
+ const auto web_browser = this->get_web_browser();
+ if (web_browser)
+ {
+ web_browser->put_Left(0);
+ web_browser->put_Top(0);
+ web_browser->put_Width(width);
+ web_browser->put_Height(height);
+ }
+}
+
+bool html_frame::load_url(const std::string& url) const
+{
+ auto web_browser = this->get_web_browser();
+ if (!web_browser) return false;
+
+ CComVariant my_url(url.data());
+ return SUCCEEDED(web_browser->Navigate2(&my_url, nullptr, nullptr, nullptr, nullptr));
+}
+
+bool html_frame::load_html(const std::string& html) const
+{
+ if (!this->load_url("about:blank")) return false;
+
+ const auto document = this->get_document();
+ if (!document) return false;
+
+ CComSafeArrayBound bound{};
+ bound.SetCount(1);
+ bound.SetLowerBound(0);
+
+ CComSafeArray array(&bound, 1);
+ array[0] = CComVariant(html.data());
+
+ document->write(array);
+ document->close();
+
+ return true;
+}
+
+html_argument html_frame::evaluate(const std::string& javascript) const
+{
+ auto dispDoc = this->get_dispatch();
+
+ CComPtr htmlDoc;
+ dispDoc->QueryInterface(&htmlDoc);
+
+ CComPtr htmlWindow;
+ htmlDoc->get_parentWindow(&htmlWindow);
+
+ CComDispatchDriver dispWindow;
+ htmlWindow->QueryInterface(&dispWindow);
+
+ CComPtr dispexWindow;
+ htmlWindow->QueryInterface(&dispexWindow);
+
+ DISPID dispidEval = -1;
+ dispexWindow->GetDispID(CComBSTR("eval"), fdexNameCaseSensitive, &dispidEval);
+
+ CComVariant result{};
+ CComVariant code(javascript.data());
+ (void)dispWindow.Invoke1(dispidEval, &code, &result);
+
+ return result;
+}
+
+int html_frame::get_callback_id(const std::string& name) const
+{
+ for (auto i = 0u; i < this->callbacks_.size(); ++i)
+ {
+ if (this->callbacks_[i].first == name)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+html_argument html_frame::invoke_callback(const int id, const std::vector& params) const
+{
+ if (id >= 0 && static_cast(id) < this->callbacks_.size())
+ {
+ return this->callbacks_[id].second(params);
+ }
+
+ return {};
+}
+
+void html_frame::register_callback(const std::string& name, const std::function&)>& callback)
+{
+ this->callbacks_.emplace_back(name, callback);
+}
+
+HRESULT html_frame::GetIDsOfNames(const IID& /*riid*/, LPOLESTR* rgszNames, UINT cNames, LCID /*lcid*/,
+ DISPID* rgDispId)
+{
+ for (unsigned int i = 0; i < cNames; ++i)
+ {
+ std::wstring wide_name(rgszNames[i]);
+ std::string name(wide_name.begin(), wide_name.end());
+
+ rgDispId[i] = this->get_callback_id(name);
+ }
+
+ return S_OK;
+}
+
+HRESULT html_frame::Invoke(const DISPID dispIdMember, const IID& /*riid*/, LCID /*lcid*/, WORD /*wFlags*/,
+ DISPPARAMS* pDispParams,
+ VARIANT* pVarResult, EXCEPINFO* /*pExcepInfo*/, UINT* /*puArgErr*/)
+{
+ std::vector params{};
+ for (auto i = pDispParams->cArgs; i > 0; --i)
+ {
+ auto& param = pDispParams->rgvarg[i - 1];
+ params.emplace_back(param);
+ }
+
+ auto res = this->invoke_callback(dispIdMember, params);
+ res.move_to(pVarResult);
+
+ return S_OK;
+}
+
+HRESULT html_frame::GetExternal(IDispatch** ppDispatch)
+{
+ *ppDispatch = this;
+ return *ppDispatch ? S_OK : S_FALSE;
+}
diff --git a/src/client/launcher/html/html_frame.hpp b/src/client/launcher/html/html_frame.hpp
new file mode 100644
index 00000000..290db0ae
--- /dev/null
+++ b/src/client/launcher/html/html_frame.hpp
@@ -0,0 +1,82 @@
+#pragma once
+#include "ole_in_place_frame.hpp"
+#include "ole_in_place_site.hpp"
+#include "doc_host_ui_handler.hpp"
+#include "ole_client_site.hpp"
+#include "service_provider.hpp"
+#include "internet_security_manager.hpp"
+#include "dispatch.hpp"
+#include "html_argument.hpp"
+
+class html_frame
+ : doc_host_ui_handler
+ , service_provider
+ , internet_security_manager
+ , ole_client_site
+ , ole_in_place_frame
+ , ole_in_place_site
+ , dispatch
+{
+public:
+ html_frame();
+ html_frame(const html_frame&) = delete;
+ html_frame& operator=(const html_frame&) = delete;
+ html_frame(html_frame&&) = delete;
+ html_frame& operator=(html_frame&&) = delete;
+
+ ~html_frame() override = default;
+
+ void initialize(HWND window);
+
+ void resize(DWORD width, DWORD height) const;
+ bool load_url(const std::string& url) const;
+ bool load_html(const std::string& html) const;
+
+ html_argument evaluate(const std::string& javascript) const;
+
+ HWND get_window() const;
+
+ CComPtr get_browser_object() const;
+ CComPtr get_web_browser() const;
+ CComPtr get_dispatch() const;
+ CComPtr get_document() const;
+
+ int get_callback_id(const std::string& name) const;
+ html_argument invoke_callback(int id, const std::vector& params) const;
+
+ void register_callback(const std::string& name, const std::function&)>& callback);
+
+ HRESULT STDMETHODCALLTYPE QueryInterface(
+ REFIID riid,
+ void** ppvObject) override;
+
+private:
+ HWND window_ = nullptr;
+ CComPtr browser_object_;
+
+ std::vector&)>>> callbacks_;
+
+ void create_browser();
+ void initialize_browser();
+
+ HRESULT STDMETHODCALLTYPE GetHostInfo(DOCHOSTUIINFO* pInfo) override;
+ HRESULT STDMETHODCALLTYPE GetWindow(HWND* lphwnd) override;
+
+ ULONG STDMETHODCALLTYPE AddRef() override
+ {
+ return 1;
+ }
+
+ ULONG STDMETHODCALLTYPE Release() override
+ {
+ return 1;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId)
+ override;
+ HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams,
+ VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) override;
+
+
+ HRESULT STDMETHODCALLTYPE GetExternal(IDispatch** ppDispatch) override;
+};
diff --git a/src/client/launcher/html/html_window.cpp b/src/client/launcher/html/html_window.cpp
new file mode 100644
index 00000000..35bdd3eb
--- /dev/null
+++ b/src/client/launcher/html/html_window.cpp
@@ -0,0 +1,37 @@
+#include
+#include "html_window.hpp"
+
+html_window::html_window(const std::string& title, int width, int height, long flags)
+ : window_(title, width, height,
+ [this](window*, const UINT message, const WPARAM w_param, const LPARAM l_param) -> std::optional {
+ return this->processor(message, w_param, l_param);
+ }, flags)
+{
+}
+
+window* html_window::get_window()
+{
+ return &this->window_;
+}
+
+html_frame* html_window::get_html_frame()
+{
+ return &this->frame_;
+}
+
+std::optional html_window::processor(const UINT message, const WPARAM w_param, const LPARAM l_param)
+{
+ if (message == WM_SIZE)
+ {
+ this->frame_.resize(LOWORD(l_param), HIWORD(l_param));
+ return 0;
+ }
+
+ if (message == WM_CREATE)
+ {
+ this->frame_.initialize(this->window_);
+ return 0;
+ }
+
+ return {};
+}
diff --git a/src/client/launcher/html/html_window.hpp b/src/client/launcher/html/html_window.hpp
new file mode 100644
index 00000000..e809065a
--- /dev/null
+++ b/src/client/launcher/html/html_window.hpp
@@ -0,0 +1,21 @@
+#pragma once
+#include "../window.hpp"
+#include "html_frame.hpp"
+
+class html_window final
+{
+public:
+ html_window(const std::string& title, int width, int height,
+ long flags = (WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX)));
+
+ ~html_window() = default;
+
+ window* get_window();
+ html_frame* get_html_frame();
+
+private:
+ html_frame frame_{};
+ window window_;
+
+ std::optional processor(UINT message, WPARAM w_param, LPARAM l_param);
+};
diff --git a/src/client/launcher/html/internet_security_manager.hpp b/src/client/launcher/html/internet_security_manager.hpp
new file mode 100644
index 00000000..a8c8f732
--- /dev/null
+++ b/src/client/launcher/html/internet_security_manager.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+class internet_security_manager : public IInternetSecurityManager
+{
+public:
+ HRESULT STDMETHODCALLTYPE SetSecuritySite(
+ IInternetSecurityMgrSite*) override
+ {
+ return INET_E_DEFAULT_ACTION;
+ }
+
+ virtual HRESULT STDMETHODCALLTYPE GetSecuritySite(
+ IInternetSecurityMgrSite**) override
+ {
+ return INET_E_DEFAULT_ACTION;
+ }
+
+ virtual HRESULT STDMETHODCALLTYPE MapUrlToZone(
+ LPCWSTR,
+ DWORD* pdwZone,
+ DWORD) override
+ {
+ *pdwZone = URLZONE_TRUSTED;
+ return S_OK;
+ }
+
+ virtual HRESULT STDMETHODCALLTYPE GetSecurityId(
+ LPCWSTR,
+ BYTE*,
+ DWORD*,
+ DWORD_PTR) override
+ {
+ return INET_E_DEFAULT_ACTION;
+ }
+
+ virtual HRESULT STDMETHODCALLTYPE ProcessUrlAction(
+ LPCWSTR,
+ DWORD,
+ BYTE*,
+ DWORD,
+ BYTE*,
+ DWORD,
+ DWORD,
+ DWORD) override
+ {
+ return INET_E_DEFAULT_ACTION;
+ }
+
+ virtual HRESULT STDMETHODCALLTYPE QueryCustomPolicy(
+ LPCWSTR,
+ REFGUID,
+ BYTE**,
+ DWORD*,
+ BYTE*,
+ DWORD,
+ DWORD) override
+ {
+ return INET_E_DEFAULT_ACTION;
+ }
+
+ HRESULT STDMETHODCALLTYPE SetZoneMapping(
+ DWORD,
+ LPCWSTR,
+ DWORD) override
+ {
+ return INET_E_DEFAULT_ACTION;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetZoneMappings(
+ DWORD,
+ IEnumString**,
+ DWORD) override
+ {
+ return INET_E_DEFAULT_ACTION;
+ }
+};
diff --git a/src/client/launcher/html/ole_client_site.hpp b/src/client/launcher/html/ole_client_site.hpp
new file mode 100644
index 00000000..7c661292
--- /dev/null
+++ b/src/client/launcher/html/ole_client_site.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+class ole_client_site : public IOleClientSite
+{
+public:
+ virtual ~ole_client_site() = default;
+
+ HRESULT STDMETHODCALLTYPE SaveObject() override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetMoniker(DWORD, DWORD, IMoniker**) override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetContainer(LPOLECONTAINER* ppContainer) override
+ {
+ *ppContainer = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ HRESULT STDMETHODCALLTYPE ShowObject() override
+ {
+ return NOERROR;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnShowWindow(BOOL) override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE RequestNewObjectLayout() override
+ {
+ return E_NOTIMPL;
+ }
+};
diff --git a/src/client/launcher/html/ole_in_place_frame.hpp b/src/client/launcher/html/ole_in_place_frame.hpp
new file mode 100644
index 00000000..7b20b361
--- /dev/null
+++ b/src/client/launcher/html/ole_in_place_frame.hpp
@@ -0,0 +1,62 @@
+#pragma once
+
+class ole_in_place_frame : public IOleInPlaceFrame
+{
+public:
+ virtual ~ole_in_place_frame() = default;
+
+ HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL) override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetBorder(LPRECT) override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE RequestBorderSpace(LPCBORDERWIDTHS) override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE SetBorderSpace(LPCBORDERWIDTHS) override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE SetActiveObject(IOleInPlaceActiveObject* pActiveObject, LPCOLESTR pszObjName) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE InsertMenus(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths) override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE SetMenu(HMENU, HOLEMENU, HWND) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE RemoveMenus(HMENU hmenuShared) override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE SetStatusText(LPCOLESTR) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE EnableModeless(BOOL) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE TranslateAccelerator(LPMSG lpmsg, WORD wID) override
+ {
+ return E_NOTIMPL;
+ }
+};
diff --git a/src/client/launcher/html/ole_in_place_site.hpp b/src/client/launcher/html/ole_in_place_site.hpp
new file mode 100644
index 00000000..3bdf3c3b
--- /dev/null
+++ b/src/client/launcher/html/ole_in_place_site.hpp
@@ -0,0 +1,89 @@
+#pragma once
+
+class ole_in_place_site : public IOleInPlaceSite
+{
+public:
+ virtual ~ole_in_place_site() = default;
+
+ HRESULT STDMETHODCALLTYPE ContextSensitiveHelp(BOOL) override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE CanInPlaceActivate() override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnInPlaceActivate() override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnUIActivate() override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE GetWindowContext(LPOLEINPLACEFRAME* lplpFrame, LPOLEINPLACEUIWINDOW* lplpDoc,
+ const LPRECT lprcPosRect, const LPRECT lprcClipRect,
+ const LPOLEINPLACEFRAMEINFO lpFrameInfo) override
+ {
+ ZeroMemory(lprcPosRect, sizeof(*lprcPosRect));
+ ZeroMemory(lprcClipRect, sizeof(*lprcClipRect));
+
+ CComPtr ole_in_place_frame{};
+ if (FAILED(QueryInterface(IID_IOleInPlaceFrame, reinterpret_cast(&ole_in_place_frame))))
+ {
+ *lplpFrame = nullptr;
+ *lplpDoc = nullptr;
+ return E_FAIL;
+ }
+
+ *lplpFrame = ole_in_place_frame;
+ *lplpDoc = nullptr;
+
+ lpFrameInfo->fMDIApp = FALSE;
+ lpFrameInfo->haccel = nullptr;
+ lpFrameInfo->cAccelEntries = 0;
+ GetWindow(&lpFrameInfo->hwndFrame);
+
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE Scroll(SIZE) override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnUIDeactivate(BOOL) override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnInPlaceDeactivate() override
+ {
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE DiscardUndoState() override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE DeactivateAndUndo() override
+ {
+ return E_NOTIMPL;
+ }
+
+ HRESULT STDMETHODCALLTYPE OnPosRectChange(const LPCRECT lprcPosRect) override
+ {
+ CComPtr in_place{};
+ if (SUCCEEDED(QueryInterface(IID_IOleInPlaceObject, reinterpret_cast(&in_place))))
+ {
+ in_place->SetObjectRects(lprcPosRect, lprcPosRect);
+ }
+
+ return S_OK;
+ }
+};
diff --git a/src/client/launcher/html/service_provider.hpp b/src/client/launcher/html/service_provider.hpp
new file mode 100644
index 00000000..84f76629
--- /dev/null
+++ b/src/client/launcher/html/service_provider.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+class service_provider : public IServiceProvider
+{
+public:
+ virtual ~service_provider() = default;
+
+ HRESULT STDMETHODCALLTYPE QueryService(
+ REFGUID guidService,
+ REFIID riid,
+ void** ppvObject) override
+ {
+ if (IsEqualGUID(riid, IID_IInternetSecurityManager))
+ {
+ return QueryInterface(riid, ppvObject);
+ }
+
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+};
+
diff --git a/src/client/launcher/launcher.cpp b/src/client/launcher/launcher.cpp
new file mode 100644
index 00000000..73cb8d5f
--- /dev/null
+++ b/src/client/launcher/launcher.cpp
@@ -0,0 +1,43 @@
+#include
+#include
+
+#include "launcher.hpp"
+#include "html/html_window.hpp"
+
+#include "resource.hpp"
+
+namespace launcher
+{
+ bool run()
+ {
+ bool run_game = false;
+ html_window window("BOIII", 750, 430);
+
+ window.get_html_frame()->register_callback(
+ "openUrl", [](const std::vector& params) -> CComVariant
+ {
+ if (params.empty()) return {};
+
+ const auto& param = params[0];
+ if (!param.is_string()) return {};
+
+ const auto url = param.get_string();
+ ShellExecuteA(nullptr, "open", url.data(), nullptr, nullptr, SW_SHOWNORMAL);
+
+ return {};
+ });
+
+ window.get_html_frame()->register_callback(
+ "runGame", [&](const std::vector& /*params*/) -> CComVariant
+ {
+ run_game = true;
+ window.get_window()->close();
+ return {};
+ });
+
+ window.get_html_frame()->load_html(utils::nt::load_resource(MENU_MAIN));
+
+ window::run();
+ return run_game;
+ }
+}
diff --git a/src/client/launcher/launcher.hpp b/src/client/launcher/launcher.hpp
new file mode 100644
index 00000000..ee128bcd
--- /dev/null
+++ b/src/client/launcher/launcher.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+namespace launcher
+{
+ bool run();
+}
diff --git a/src/client/launcher/window.cpp b/src/client/launcher/window.cpp
new file mode 100644
index 00000000..d9340123
--- /dev/null
+++ b/src/client/launcher/window.cpp
@@ -0,0 +1,127 @@
+#include
+#include "window.hpp"
+
+namespace
+{
+ thread_local uint32_t window_count = 0;
+}
+
+window::window(const std::string& title, const int width, const int height,
+ std::function(window*, UINT, WPARAM, LPARAM)> callback,
+ const long flags)
+ : callback_(std::move(callback))
+{
+ ZeroMemory(&this->wc_, sizeof(this->wc_));
+
+ this->classname_ = "window-base-" + std::to_string(time(nullptr));
+
+ this->wc_.cbSize = sizeof(this->wc_);
+ this->wc_.style = CS_HREDRAW | CS_VREDRAW;
+ this->wc_.lpfnWndProc = static_processor;
+ this->wc_.hInstance = GetModuleHandle(nullptr);
+ this->wc_.hCursor = LoadCursor(nullptr, IDC_ARROW);
+ this->wc_.hIcon = LoadIcon(this->wc_.hInstance, MAKEINTRESOURCE(102));
+ this->wc_.hIconSm = this->wc_.hIcon;
+ this->wc_.hbrBackground = HBRUSH(COLOR_WINDOW);
+ this->wc_.lpszClassName = this->classname_.data();
+ RegisterClassEx(&this->wc_);
+
+ const auto x = (GetSystemMetrics(SM_CXSCREEN) - width) / 2;
+ const auto y = (GetSystemMetrics(SM_CYSCREEN) - height) / 2;
+
+ ++window_count;
+
+ this->handle_ = CreateWindowExA(NULL, this->wc_.lpszClassName, title.data(), flags, x, y, width, height, nullptr,
+ nullptr, this->wc_.hInstance, this);
+
+ SendMessageA(this->handle_, WM_DPICHANGED, 0, 0);
+ ShowWindow(this->handle_, SW_SHOW);
+}
+
+window::~window()
+{
+ this->close();
+ UnregisterClass(this->wc_.lpszClassName, this->wc_.hInstance);
+}
+
+void window::close()
+{
+ if (!this->handle_) return;
+
+ DestroyWindow(this->handle_);
+ this->handle_ = nullptr;
+}
+
+void window::run()
+{
+ MSG msg{};
+ while (GetMessage(&msg, nullptr, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+}
+
+LRESULT window::processor(const UINT message, const WPARAM w_param, const LPARAM l_param)
+{
+ if (message == WM_DPICHANGED)
+ {
+ const auto dpi = GetDpiForWindow(*this);
+ if (dpi != this->last_dpi_)
+ {
+ RECT rect;
+ GetWindowRect(*this, &rect);
+
+ const auto scale = dpi * 1.0 / this->last_dpi_;
+ this->last_dpi_ = dpi;
+
+ const auto width = rect.right - rect.left;
+ const auto height = rect.bottom - rect.top;
+
+ MoveWindow(*this, rect.left, rect.top, int(width * scale), int(height * scale), TRUE);
+ }
+ }
+
+ if (message == WM_DESTROY)
+ {
+ if (--window_count == 0)
+ {
+ PostQuitMessage(0);
+ }
+
+ return TRUE;
+ }
+
+ if (this->callback_)
+ {
+ const auto res = this->callback_(this, message, w_param, l_param);
+ if (res)
+ {
+ return *res;
+ }
+ }
+
+ return DefWindowProc(*this, message, w_param, l_param);
+}
+
+LRESULT CALLBACK window::static_processor(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param)
+{
+ if (message == WM_CREATE)
+ {
+ auto data = reinterpret_cast(l_param);
+ SetWindowLongPtrA(hwnd, GWLP_USERDATA, LONG_PTR(data->lpCreateParams));
+
+ reinterpret_cast(data->lpCreateParams)->handle_ = hwnd;
+ }
+
+ const auto self = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA));
+ if (self) return self->processor(message, w_param, l_param);
+
+ return DefWindowProc(hwnd, message, w_param, l_param);
+}
+
+
+window::operator HWND() const
+{
+ return this->handle_;
+}
diff --git a/src/client/launcher/window.hpp b/src/client/launcher/window.hpp
new file mode 100644
index 00000000..9b840759
--- /dev/null
+++ b/src/client/launcher/window.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+class window
+{
+public:
+ window(const std::string& title, int width, int height,
+ std::function(window*, UINT, WPARAM, LPARAM)> callback,
+ long flags = (WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX)));
+
+ virtual ~window();
+
+ void close();
+
+ operator HWND() const;
+
+ static void run();
+
+ LRESULT processor(UINT message, WPARAM w_param, LPARAM l_param);
+
+private:
+ uint32_t last_dpi_ = 96;
+
+ WNDCLASSEX wc_{};
+ HWND handle_ = nullptr;
+ std::string classname_;
+ std::function(window*, UINT, WPARAM, LPARAM)> callback_;
+
+ static LRESULT CALLBACK static_processor(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param);
+};
diff --git a/src/client/main.cpp b/src/client/main.cpp
index 805bd8ff..5909e93c 100644
--- a/src/client/main.cpp
+++ b/src/client/main.cpp
@@ -12,6 +12,7 @@
#include
#include "game/game.hpp"
+#include "launcher/launcher.hpp"
namespace
{
@@ -172,11 +173,11 @@ namespace
void enable_dpi_awareness()
{
- const utils::nt::library user32{ "user32.dll" };
+ const utils::nt::library user32{"user32.dll"};
const auto set_dpi = user32
- ? user32.get_proc(
- "SetProcessDpiAwarenessContext")
- : nullptr;
+ ? user32.get_proc(
+ "SetProcessDpiAwarenessContext")
+ : nullptr;
if (set_dpi)
{
set_dpi(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
@@ -209,6 +210,11 @@ namespace
{
remove_crash_file();
+ if (!launcher::run())
+ {
+ return 0;
+ }
+
const auto client_binary = "BlackOps3.exe"s;
const auto server_binary = "BlackOps3_UnrankedDedicatedServer.exe"s;
const auto has_server = !utils::io::file_exists(client_binary) || utils::flags::has_flag("dedicated");
diff --git a/src/client/resource.hpp b/src/client/resource.hpp
index 716757fb..38914012 100644
--- a/src/client/resource.hpp
+++ b/src/client/resource.hpp
@@ -13,3 +13,4 @@
#define DW_QOSCONFIG 307
#define TLS_DLL 308
+#define MENU_MAIN 309
diff --git a/src/client/resource.rc b/src/client/resource.rc
index 1f74314b..ccfa994c 100644
--- a/src/client/resource.rc
+++ b/src/client/resource.rc
@@ -102,6 +102,8 @@ DW_FASTFILE RCDATA "resources/dw/core_ffotd_tu32_593.ff"
DW_KEYS RCDATA "resources/dw/keys.txt"
DW_QOSCONFIG RCDATA "resources/dw/qosconfig4.csv"
+MENU_MAIN RCDATA "resources/main.html"
+
#ifdef _DEBUG
TLS_DLL RCDATA "../../build/bin/x64/Debug/tlsdll.dll"
#else
diff --git a/src/client/resources/main.html b/src/client/resources/main.html
new file mode 100644
index 00000000..6dd18df7
--- /dev/null
+++ b/src/client/resources/main.html
@@ -0,0 +1,478 @@
+
+
+
+
+
+
+ Open-IW5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp
index f0152758..a9c9d402 100644
--- a/src/client/std_include.hpp
+++ b/src/client/std_include.hpp
@@ -47,6 +47,7 @@
#include
#include
#include
+#include
#include
#include
diff --git a/src/common/utils/nt.cpp b/src/common/utils/nt.cpp
index f6b07add..c57ab267 100644
--- a/src/common/utils/nt.cpp
+++ b/src/common/utils/nt.cpp
@@ -72,6 +72,11 @@ namespace utils::nt
return &this->get_nt_headers()->OptionalHeader;
}
+ void** library::get_iat_entry(const std::string& module_name, std::string proc_name) const
+ {
+ return this->get_iat_entry(module_name, proc_name.data());
+ }
+
std::vector library::get_section_headers() const
{
std::vector headers;
@@ -162,7 +167,7 @@ namespace utils::nt
return this->module_;
}
- void** library::get_iat_entry(const std::string& module_name, const std::string& proc_name) const
+ void** library::get_iat_entry(const std::string& module_name, const char* proc_name) const
{
if (!this->is_valid()) return nullptr;
diff --git a/src/common/utils/nt.hpp b/src/common/utils/nt.hpp
index 962ed6d5..02b06dc1 100644
--- a/src/common/utils/nt.hpp
+++ b/src/common/utils/nt.hpp
@@ -54,10 +54,16 @@ namespace utils::nt
[[nodiscard]] HMODULE get_handle() const;
template
- [[nodiscard]] T get_proc(const std::string& process) const
+ [[nodiscard]] T get_proc(const char* process) const
{
if (!this->is_valid()) T{};
- return reinterpret_cast(GetProcAddress(this->module_, process.data()));
+ return reinterpret_cast(GetProcAddress(this->module_, process));
+ }
+
+ template
+ [[nodiscard]] T get_proc(const std::string& process) const
+ {
+ return get_proc(process.data());
}
template
@@ -97,7 +103,8 @@ namespace utils::nt
[[nodiscard]] PIMAGE_DOS_HEADER get_dos_header() const;
[[nodiscard]] PIMAGE_OPTIONAL_HEADER get_optional_header() const;
- [[nodiscard]] void** get_iat_entry(const std::string& module_name, const std::string& proc_name) const;
+ [[nodiscard]] void** get_iat_entry(const std::string& module_name, std::string proc_name) const;
+ [[nodiscard]] void** get_iat_entry(const std::string& module_name, const char* proc_name) const;
private:
HMODULE module_;