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_;