From 331f79a69d146a50a8c5e380ad536a2851335eca Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 5 Jul 2024 22:41:34 +0100 Subject: [PATCH 1/7] resource_handler: create DefaultURLHandler --- src/browser/resource_handler.cxx | 117 +++++++++++++++++++++++++++++++ src/browser/resource_handler.hxx | 34 +++++++++ 2 files changed, 151 insertions(+) diff --git a/src/browser/resource_handler.cxx b/src/browser/resource_handler.cxx index 94d0d95..00b6856 100644 --- a/src/browser/resource_handler.cxx +++ b/src/browser/resource_handler.cxx @@ -1,4 +1,21 @@ #include "resource_handler.hxx" +#include "include/cef_urlrequest.h" +#include "include/internal/cef_types.h" + +// returns content-length or -1 if it's malformed or nonexistent +// CEF interprets -1 as "unknown length" +static int64 GetContentLength(CefRefPtr response) { + std::string content_length = response->GetHeaderByName("Content-Length").ToString(); + if (content_length.size() == 0) { + return -1; + } + int64 length = 0; + for (auto it = content_length.begin(); it != content_length.end(); it++) { + if (*it < '0' || *it > '9') return -1; + length = (length * 10) + ((int64)(*it) - '0'); + } + return length; +} bool Browser::ResourceHandler::Open(CefRefPtr, bool& handle_request, CefRefPtr) { handle_request = true; @@ -65,3 +82,103 @@ void Browser::ResourceHandler::finish() { this->file_manager = nullptr; } } + +Browser::DefaultURLHandler::DefaultURLHandler(CefRefPtr request): urlrequest_complete(false), headers_checked(false), urlrequest_callback(nullptr), cursor(0) { + this->url_request = CefURLRequest::Create(request, this, nullptr); +} + +CefResourceRequestHandler::ReturnValue Browser::DefaultURLHandler::OnBeforeResourceLoad(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr callback) { + // use CefResourceRequestHandler::OnBeforeResourceLoad to stall asynchronously on the IO thread + // until the request completes, if it hasn't already + if (urlrequest_complete) { + return RV_CONTINUE; + } else { + this->urlrequest_callback = callback; + return RV_CONTINUE_ASYNC; + } +} + +CefRefPtr Browser::DefaultURLHandler::GetResourceHandler(CefRefPtr browser, CefRefPtr frame, CefRefPtr request) { + return this; +} + +bool Browser::DefaultURLHandler::Open(CefRefPtr request, bool& handle_request, CefRefPtr callback) { + handle_request = true; + return true; +} + +void Browser::DefaultURLHandler::GetResponseHeaders(CefRefPtr response, int64& response_length, CefString& redirectUrl) { + CefRefPtr url_response = this->url_request->GetResponse(); + CefResponse::HeaderMap headers; + url_response->GetHeaderMap(headers); + response->SetHeaderMap(headers); + response->SetStatus(url_response->GetStatus()); + response->SetStatusText(url_response->GetStatusText()); + response->SetMimeType(url_response->GetMimeType()); + response->SetCharset(url_response->GetCharset()); + response->SetHeaderByName("Access-Control-Allow-Origin", "*", true); + response_length = GetContentLength(url_response); +} + +bool Browser::DefaultURLHandler::Read(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr callback) { + if (this->cursor == this->data.size()) { + // "To indicate response completion set |bytes_read| to 0 and return false." + bytes_read = 0; + return false; + } + if (this->cursor + bytes_to_read <= this->data.size()) { + // requested read is entirely in bounds + bytes_read = bytes_to_read; + memcpy(data_out, &this->data[this->cursor], bytes_read); + this->cursor += bytes_to_read; + } else { + // read only to end of string + bytes_read = this->data.size() - this->cursor; + memcpy(data_out, &this->data[this->cursor], bytes_read); + this->cursor = this->data.size(); + } + return true; +} + +bool Browser::DefaultURLHandler::Skip(int64 bytes_to_skip, int64& bytes_skipped, CefRefPtr callback) { + if (this->cursor + bytes_to_skip <= this->data.size()) { + // skip in bounds + bytes_skipped = bytes_to_skip; + this->cursor += bytes_to_skip; + } else { + // skip to end of string + bytes_skipped = this->data.size() - this->cursor; + this->cursor = this->data.size(); + } + return true; +} + +void Browser::DefaultURLHandler::Cancel() { + this->cursor = this->data.size(); +} + +void Browser::DefaultURLHandler::OnRequestComplete(CefRefPtr request) { + this->urlrequest_complete = true; + if (this->urlrequest_callback) { + this->urlrequest_callback->Continue(); + } +} + +void Browser::DefaultURLHandler::OnUploadProgress(CefRefPtr request, int64 current, int64 total) {} +void Browser::DefaultURLHandler::OnDownloadProgress(CefRefPtr request, int64 current, int64 total) {} + +void Browser::DefaultURLHandler::OnDownloadData(CefRefPtr request, const void* data, size_t data_length) { + CefRefPtr response = request->GetResponse(); + if (!this->headers_checked && response) { + const int64 content_length = GetContentLength(response); + if (content_length != -1) this->data.reserve(content_length); + this->headers_checked = true; + } + size_t write_offset = this->data.size(); + this->data.resize(write_offset + data_length); + memcpy(this->data.data() + write_offset, data, data_length); +} + +bool Browser::DefaultURLHandler::GetAuthCredentials(bool isProxy, const CefString& host, int port, const CefString& realm, const CefString& scheme, CefRefPtr callback) { + return false; +} diff --git a/src/browser/resource_handler.hxx b/src/browser/resource_handler.hxx index 5a39c4b..6ddafbd 100644 --- a/src/browser/resource_handler.hxx +++ b/src/browser/resource_handler.hxx @@ -5,6 +5,7 @@ #include "include/cef_base.h" #include "include/cef_resource_request_handler.h" +#include "include/cef_urlrequest.h" #include namespace Browser { @@ -55,5 +56,38 @@ namespace Browser { DISALLOW_COPY_AND_ASSIGN(ResourceHandler); }; + /// Struct for bridging a CefURLRequestClient to a CefResourceHandler + /// https://github.com/chromiumembedded/cef/blob/5735/include/cef_resource_request_handler.h + /// https://github.com/chromiumembedded/cef/blob/5735/include/cef_resource_handler.h + /// https://github.com/chromiumembedded/cef/blob/5735/include/cef_urlrequest.h#L137 + struct DefaultURLHandler: public CefResourceRequestHandler, CefResourceHandler, CefURLRequestClient { + DefaultURLHandler(CefRefPtr); + + CefResourceRequestHandler::ReturnValue OnBeforeResourceLoad(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr callback) override; + CefRefPtr GetResourceHandler(CefRefPtr browser, CefRefPtr frame, CefRefPtr request) override; + + bool Open(CefRefPtr request, bool& handle_request, CefRefPtr callback) override; + void GetResponseHeaders(CefRefPtr response, int64& response_length, CefString& redirectUrl) override; + bool Read(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr callback) override; + bool Skip(int64 bytes_to_skip, int64& bytes_skipped, CefRefPtr callback) override; + void Cancel() override; + + void OnRequestComplete(CefRefPtr request) override; + void OnUploadProgress(CefRefPtr request, int64 current, int64 total) override; + void OnDownloadProgress(CefRefPtr request, int64 current, int64 total) override; + void OnDownloadData(CefRefPtr request, const void* data, size_t data_length) override; + bool GetAuthCredentials(bool isProxy, const CefString& host, int port, const CefString& realm, const CefString& scheme, CefRefPtr callback) override; + + private: + CefRefPtr url_request; + CefRefPtr urlrequest_callback; + bool urlrequest_complete; + bool headers_checked; + std::vector data; + size_t cursor; + IMPLEMENT_REFCOUNTING(DefaultURLHandler); + DISALLOW_COPY_AND_ASSIGN(DefaultURLHandler); + }; + #endif } From 887f361eeac3def2f679023090bd960d91ff6259 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 5 Jul 2024 22:49:11 +0100 Subject: [PATCH 2/7] window_launcher: use a default request handler Browser::Launcher now intercepts all requests (except ones which fail to parse). The default behaviour is very similar to how CEF would behave if the request wasn't intercepted, except it reconstructs the CefRequest without associating it with any browser or frame, meaning it's not subject to cross-origin checks, and can present its own cross-origin policy in its response to the webpage. This will finally put a stop to the plague of issues with Jgx's login API having incorrect access control settings. --- src/browser/window_launcher.cxx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/browser/window_launcher.cxx b/src/browser/window_launcher.cxx index 05a2203..cae6e9a 100644 --- a/src/browser/window_launcher.cxx +++ b/src/browser/window_launcher.cxx @@ -454,7 +454,18 @@ CefRefPtr Browser::Launcher::GetResourceRequestHandle } } - return nullptr; + if (!browser->IsSame(this->browser) || !frame->IsMain()) { + return nullptr; + } + + // default way to handle web requests from the main window is to make a CefURLRequest that's not subject to CORS safety + disable_default_handling = true; + CefRefPtr new_request = CefRequest::Create(); + CefRequest::HeaderMap headers; + request->GetHeaderMap(headers); // doesn't include Referer header + headers.erase("Origin"); + new_request->Set(request->GetURL(), request->GetMethod(), request->GetPostData(), headers); + return new DefaultURLHandler(new_request); } void Browser::Launcher::OnBrowserDestroyed(CefRefPtr view, CefRefPtr browser) { From bc569d579ad886495c143e7d337f0f9f23f6a35c Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 5 Jul 2024 22:55:09 +0100 Subject: [PATCH 3/7] main: don't --disable-web-security See previous commit message for why this was used in the first place. The option doesn't appear to do anything on Windows, so an alternative solution was necessary. --- src/main.cxx | 44 ++------------------------------------------ 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/src/main.cxx b/src/main.cxx index baa18b2..95c5971 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -135,31 +135,8 @@ void BoltFailToObtainLockfile(std::filesystem::path tempdir) { #if defined(__linux__) int main(int argc, char* argv[]) { - // Provide CEF with command-line arguments - // Add a flag to disable web security, because a certain company's API endpoints don't have correct - // access control settings; remove this setting if they ever get their stuff together - // Also add a flag to disable GPUCache being saved on disk because it just breaks sometimes? - const char* arg1 = "--disable-web-security"; - const char* arg2 = "--disable-gpu-shader-disk-cache"; - const size_t arg1_len = strlen(arg1); - const size_t arg2_len = strlen(arg2); - char* arg1_ = new char[arg1_len + 1]; - char* arg2_ = new char[arg2_len + 1]; - memcpy(arg1_, arg1, arg1_len); - memcpy(arg2_, arg2, arg2_len); - arg1_[arg1_len] = '\0'; - arg2_[arg2_len] = '\0'; - char** argv_ = new char*[argc + 2]; - memcpy(argv_, argv, argc * sizeof(char*)); - argv_[argc] = arg1_; - argv_[argc + 1] = arg2_; - - CefMainArgs main_args(argc + 2, argv_); + CefMainArgs main_args(argc, argv); int ret = BoltRunAnyProcess(main_args); - - delete[] argv_; - delete[] arg2_; - delete[] arg1_; return ret; } @@ -260,15 +237,9 @@ int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t* lpCmdLine, i fmt::print("Exiting with error: CommandLineToArgvW failed\n"); return 1; } - const wchar_t* const arg = L"--disable-web-security"; const wchar_t* subp_arg = L"--type="; - bool has_arg = false; bool is_subprocess = false; for(size_t i = 0; i < argc; i += 1) { - // check if arg is --disable-web-security - if (wcsstr(argv[i], arg)) { - has_arg = true; - } // check if arg begins with --type= if (wcsncmp(argv[i], subp_arg, wcslen(subp_arg)) == 0) { is_subprocess = true; @@ -281,24 +252,13 @@ int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t* lpCmdLine, i int ret = BoltRunAnyProcess(CefMainArgs(hInstance)); CefShutdown(); return ret; - } else if (has_arg) { + } else { // CefApp struct - this implements handlers used by multiple processes Browser::App cef_app_; CefRefPtr cef_app = &cef_app_; int ret = BoltRunBrowserProcess(CefMainArgs(hInstance), cef_app); CefShutdown(); return ret; - } else { - wchar_t module_name[1024]; - GetModuleFileNameW(NULL, module_name, 1024); - std::wstring command_line(lpCmdLine); - command_line += std::wstring(L" "); - command_line += std::wstring(arg); - PROCESS_INFORMATION process_info; - STARTUPINFOW startup_info; - GetStartupInfoW(&startup_info); - CreateProcessW(module_name, command_line.data(), NULL, NULL, 0, 0, NULL, NULL, &startup_info, &process_info); - return 0; } } From 17fac203f8dca92bfaa1b59292b03823b22fc8d2 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 6 Jul 2024 02:50:49 +0100 Subject: [PATCH 4/7] app: post ArrayBuffer instead of blob POSTing a Blob is allowed according to MDN docs, and in the network tab it appears to work perfectly, but the recieving end gets a body with 0 bytes in it. I have literally no idea why, because the data uploaded including headers appears to be 1:1 identical, so I don't even know who I should be complaining to about this... --- app/src/lib/Util/functions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/lib/Util/functions.ts b/app/src/lib/Util/functions.ts index f0ebb35..c60ed45 100644 --- a/app/src/lib/Util/functions.ts +++ b/app/src/lib/Util/functions.ts @@ -834,7 +834,7 @@ export async function launchOfficialClient( ? osrsAppInstalledHash : rs3AppInstalledHash; - const launch = async (hash?: string, exe?: Blob) => { + const launch = async (hash?: string, exe?: Promise) => { const params: Record = {}; if (hash) params.hash = hash; if (jx_session_id) params.jx_session_id = jx_session_id; @@ -845,7 +845,7 @@ export async function launchOfficialClient( { method: 'POST', headers: { 'Content-Type': 'application/octet-stream' }, - body: exe + body: await exe } ); response.text().then((text) => msg(`Game launch status: '${text.trim()}'`)); @@ -942,6 +942,6 @@ export async function launchOfficialClient( // stitch all the data together, slice the exe out of it, and launch the game Promise.all(chunk_promises).then((x) => { const exeFile = new Blob(x).slice(exeOffset, exeOffset + exeSize); - launch(metafile.id, exeFile); + launch(metafile.id, exeFile.arrayBuffer()); }); } From e33b524353127e6a6e5df575406633d78ee986e8 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 6 Jul 2024 03:01:32 +0100 Subject: [PATCH 5/7] app: format and minify --- app/dist/assets/{index-dPPSjzHg.js => index-CfEoSnmR.js} | 2 +- app/dist/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename app/dist/assets/{index-dPPSjzHg.js => index-CfEoSnmR.js} (83%) diff --git a/app/dist/assets/index-dPPSjzHg.js b/app/dist/assets/index-CfEoSnmR.js similarity index 83% rename from app/dist/assets/index-dPPSjzHg.js rename to app/dist/assets/index-CfEoSnmR.js index 8fe3719..aecad86 100644 --- a/app/dist/assets/index-dPPSjzHg.js +++ b/app/dist/assets/index-CfEoSnmR.js @@ -1,5 +1,5 @@ var Yt=Object.defineProperty;var Kt=(t,e,n)=>e in t?Yt(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var Ve=(t,e,n)=>(Kt(t,typeof e!="symbol"?e+"":e,n),n);(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))r(o);new MutationObserver(o=>{for(const l of o)if(l.type==="childList")for(const c of l.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&r(c)}).observe(document,{childList:!0,subtree:!0});function n(o){const l={};return o.integrity&&(l.integrity=o.integrity),o.referrerPolicy&&(l.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?l.credentials="include":o.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function r(o){if(o.ep)return;o.ep=!0;const l=n(o);fetch(o.href,l)}})();function I(){}function Qt(t){return!!t&&(typeof t=="object"||typeof t=="function")&&typeof t.then=="function"}function Ot(t){return t()}function ft(){return Object.create(null)}function re(t){t.forEach(Ot)}function jt(t){return typeof t=="function"}function oe(t,e){return t!=t?e==e:t!==e||t&&typeof t=="object"||typeof t=="function"}let qe;function en(t,e){return t===e?!0:(qe||(qe=document.createElement("a")),qe.href=e,t===qe.href)}function tn(t){return Object.keys(t).length===0}function Ht(t,...e){if(t==null){for(const r of e)r(void 0);return I}const n=t.subscribe(...e);return n.unsubscribe?()=>n.unsubscribe():n}function K(t){let e;return Ht(t,n=>e=n)(),e}function F(t,e,n){t.$$.on_destroy.push(Ht(e,n))}function j(t,e,n){return t.set(n),e}function h(t,e){t.appendChild(e)}function L(t,e,n){t.insertBefore(e,n||null)}function S(t){t.parentNode&&t.parentNode.removeChild(t)}function He(t,e){for(let n=0;nt.removeEventListener(e,n,r)}function _(t,e,n){n==null?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function nn(t){return Array.from(t.childNodes)}function ae(t,e){e=""+e,t.data!==e&&(t.data=e)}function be(t,e){t.value=e??""}function pt(t,e,n){for(let r=0;r{const o=t.$$.callbacks[e];if(o){const l=rn(e,n,{cancelable:r});return o.slice().forEach(c=>{c.call(t,l)}),!l.defaultPrevented}return!0}}const Pe=[],z=[];let Re=[];const Ke=[],cn=Promise.resolve();let Qe=!1;function an(){Qe||(Qe=!0,cn.then(ot))}function Je(t){Re.push(t)}function Ie(t){Ke.push(t)}const Ze=new Set;let Ce=0;function ot(){if(Ce!==0)return;const t=Ne;do{try{for(;Cet.indexOf(r)===-1?e.push(r):n.push(r)),n.forEach(r=>r()),Re=e}const Be=new Set;let we;function ve(){we={r:0,c:[],p:we}}function Se(){we.r||re(we.c),we=we.p}function U(t,e){t&&t.i&&(Be.delete(t),t.i(e))}function B(t,e,n,r){if(t&&t.o){if(Be.has(t))return;Be.add(t),we.c.push(()=>{Be.delete(t),r&&(n&&t.d(1),r())}),t.o(e)}else r&&r()}function Ee(t,e){const n=e.token={};function r(o,l,c,i){if(e.token!==n)return;e.resolved=i;let u=e.ctx;c!==void 0&&(u=u.slice(),u[c]=i);const a=o&&(e.current=o)(u);let d=!1;e.block&&(e.blocks?e.blocks.forEach((f,p)=>{p!==l&&f&&(ve(),B(f,1,1,()=>{e.blocks[p]===f&&(e.blocks[p]=null)}),Se())}):e.block.d(1),a.c(),U(a,1),a.m(e.mount(),e.anchor),d=!0),e.block=a,e.blocks&&(e.blocks[l]=a),d&&ot()}if(Qt(t)){const o=Ae();if(t.then(l=>{he(o),r(e.then,1,e.value,l),he(null)},l=>{if(he(o),r(e.catch,2,e.error,l),he(null),!e.hasCatch)throw l}),e.current!==e.pending)return r(e.pending,0),!0}else{if(e.current!==e.then)return r(e.then,1,e.value,t),!0;e.resolved=t}}function lt(t,e,n){const r=e.slice(),{resolved:o}=t;t.current===t.then&&(r[t.value]=o),t.current===t.catch&&(r[t.error]=o),t.block.p(r,n)}function pe(t){return(t==null?void 0:t.length)!==void 0?t:Array.from(t)}function Oe(t,e,n){const r=t.$$.props[e];r!==void 0&&(t.$$.bound[r]=n,n(t.$$.ctx[r]))}function ne(t){t&&t.c()}function ee(t,e,n){const{fragment:r,after_update:o}=t.$$;r&&r.m(e,n),Je(()=>{const l=t.$$.on_mount.map(Ot).filter(jt);t.$$.on_destroy?t.$$.on_destroy.push(...l):re(l),t.$$.on_mount=[]}),o.forEach(Je)}function te(t,e){const n=t.$$;n.fragment!==null&&(dn(n.after_update),re(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function fn(t,e){t.$$.dirty[0]===-1&&(Pe.push(t),an(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{const g=k.length?k[0]:p;return a.ctx&&o(a.ctx[f],a.ctx[f]=g)&&(!a.skip_bound&&a.bound[f]&&a.bound[f](g),d&&fn(t,f)),p}):[],a.update(),d=!0,re(a.before_update),a.fragment=r?r(a.ctx):!1,e.target){if(e.hydrate){const f=nn(e.target);a.fragment&&a.fragment.l(f),f.forEach(S)}else a.fragment&&a.fragment.c();e.intro&&U(t.$$.fragment),ee(t,e.target,e.anchor),ot()}he(u)}class ce{constructor(){Ve(this,"$$");Ve(this,"$$set")}$destroy(){te(this,1),this.$destroy=I}$on(e,n){if(!jt(n))return I;const r=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return r.push(n),()=>{const o=r.indexOf(n);o!==-1&&r.splice(o,1)}}$set(e){this.$$set&&!tn(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}}const pn="4";typeof window<"u"&&(window.__svelte||(window.__svelte={v:new Set})).v.add(pn);const Te=[];function At(t,e){return{subscribe:W(t,e).subscribe}}function W(t,e=I){let n;const r=new Set;function o(i){if(oe(t,i)&&(t=i,n)){const u=!Te.length;for(const a of r)a[1](),Te.push(a,t);if(u){for(let a=0;a{r.delete(a),r.size===0&&n&&(n(),n=null)}}return{set:o,update:l,subscribe:c}}function Dt(t){if(t.ok)return t.value;throw t.error}var Q=(t=>(t[t.rs3=0]="rs3",t[t.osrs=1]="osrs",t))(Q||{}),ie=(t=>(t[t.osrs=0]="osrs",t[t.runeLite=1]="runeLite",t[t.hdos=2]="hdos",t[t.rs3=3]="rs3",t))(ie||{});const _n={use_dark_theme:!0,flatpak_rich_presence:!1,rs_config_uri:"",runelite_custom_jar:"",runelite_use_custom_jar:!1,selected_account:"",selected_characters:new Map,selected_game_accounts:new Map,selected_game_index:1,selected_client_index:1},Ut=At("https://bolt-internal"),hn=At("1fddee4e-b100-4f4e-b2b0-097f9088f9d2"),ct=W(),Fe=W(""),J=W({..._n}),ye=W(new Map),ge=W(!1),fe=W(),et=W(),Le=W([]),We=W({}),it=W([]),tt=W(""),qt=W(""),Bt=W(""),$t=W(""),Jt=W(""),nt=W(""),st=W(""),Y=W(!1),Ge=W(new Map),Z=W({game:Q.osrs,client:ie.runeLite}),Xe=W(!1);function bn(){se.use_dark_theme==!1&&document.documentElement.classList.remove("dark")}function gn(t,e){e&&t.win.close(),it.update(n=>(n.splice(ut.indexOf(t),1),n))}function Gt(){if(de&&de.win&&!de.win.closed)de.win.focus();else if(de&&de.win&&de.win.closed||de){const t=Ft(),e=wn();kn({origin:atob(V.origin),redirect:atob(V.redirect),authMethod:"",loginType:"",clientid:atob(V.clientid),flow:"launcher",pkceState:t,pkceCodeVerifier:e}).then(n=>{const r=window.open(n,"","width=480,height=720");We.set({state:t,verifier:e,win:r})})}}function mn(){const t=new URLSearchParams(window.location.search);Fe.set(t.get("platform")),tt.set(t.get("rs3_deb_installed_hash")),qt.set(t.get("rs3_exe_installed_hash")),Bt.set(t.get("rs3_app_installed_hash")),$t.set(t.get("osrs_exe_installed_hash")),Jt.set(t.get("osrs_app_installed_hash")),nt.set(t.get("runelite_installed_id")),st.set(t.get("hdos_installed_version"));const e=t.get("plugins");e!==null?(ge.set(!0),fe.set(JSON.parse(e))):ge.set(!1);const n=t.get("credentials");if(n)try{JSON.parse(n).forEach(l=>{ye.update(c=>(c.set(l.sub,l),c))})}catch(o){q(`Couldn't parse credentials file: ${o}`,!1)}const r=t.get("config");if(r)try{const o=JSON.parse(r);J.set(o),J.update(l=>(l.selected_game_accounts?(l.selected_characters=new Map(Object.entries(l.selected_game_accounts)),delete l.selected_game_accounts):l.selected_characters&&(l.selected_characters=new Map(Object.entries(l.selected_characters))),l))}catch(o){q(`Couldn't parse config file: ${o}`,!1)}}async function Xt(t,e,n){return new Promise(r=>{if(t.expiry-Date.now()<3e4){const o=new URLSearchParams({grant_type:"refresh_token",client_id:n,refresh_token:t.refresh_token}),l=new XMLHttpRequest;l.onreadystatechange=()=>{if(l.readyState==4)if(l.status==200){const c=zt(l.response),i=Dt(c);i?(t.access_token=i.access_token,t.expiry=i.expiry,t.id_token=i.id_token,t.login_provider=i.login_provider,t.refresh_token=i.refresh_token,i.session_id&&(t.session_id=i.session_id),t.sub=i.sub,r(null)):r(0)}else r(l.status)},l.onerror=()=>{r(0)},l.open("POST",e,!0),l.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),l.setRequestHeader("Accept","application/json"),l.send(o)}else r(null)})}function zt(t){const e=JSON.parse(t),n=e.id_token.split(".");if(n.length!==3){const l=`Malformed id_token: ${n.length} sections, expected 3`;return q(l,!1),{ok:!1,error:new Error(l)}}const r=JSON.parse(atob(n[0]));if(r.typ!=="JWT"){const l=`Bad id_token header: typ ${r.typ}, expected JWT`;return q(l,!1),{ok:!1,error:new Error(l)}}const o=JSON.parse(atob(n[1]));return{ok:!0,value:{access_token:e.access_token,id_token:e.id_token,refresh_token:e.refresh_token,sub:o.sub,login_provider:o.login_provider||null,expiry:Date.now()+e.expires_in*1e3,session_id:e.session_id}}}async function kn(t){const e=new TextEncoder().encode(t.pkceCodeVerifier),n=await crypto.subtle.digest("SHA-256",e);let r="";const o=new Uint8Array(n);for(let c=0;cMath.round(o*(n-0)/255+0)).map(o=>e[o]).join("")}async function Wt(t,e,n){return new Promise(r=>{const o=new XMLHttpRequest;o.onreadystatechange=()=>{o.readyState==4&&(o.status==200?n.then(l=>{if(typeof l!="number"){const c={id:l.id,userId:l.userId,displayName:l.displayName,suffix:l.suffix,characters:new Map};$(`Successfully added login for ${c.displayName}`),JSON.parse(o.response).forEach(i=>{c.characters.set(i.accountId,{accountId:i.accountId,displayName:i.displayName,userHash:i.userHash})}),Sn(c),r(!0)}else q(`Error getting account info: ${l}`,!1),r(!1)}):(q(`Error: from ${e}: ${o.status}: ${o.response}`,!1),r(!1)))},o.open("GET",e,!0),o.setRequestHeader("Accept","application/json"),o.setRequestHeader("Authorization","Bearer ".concat(t.session_id)),o.send()})}async function _t(t,e){return await yn(t,e)}async function yn(t,e){const n=Ft(),r=crypto.randomUUID(),o=atob(V.origin).concat("/oauth2/auth?").concat(new URLSearchParams({id_token_hint:e.id_token,nonce:btoa(r),prompt:"consent",redirect_uri:"http://localhost",response_type:"id_token code",state:n,client_id:K(hn),scope:"openid offline"}).toString()),l=vn(e);return t?(t.location.href=o,it.update(c=>(c.push({state:n,nonce:r,creds:e,win:t,account_info_promise:l}),c)),!1):e.session_id?await Wt(e,atob(V.auth_api).concat("/accounts"),l):(q("Rejecting stored credentials with missing session_id",!1),!1)}function vn(t){return new Promise(e=>{const n=`${atob(V.api)}/users/${t.sub}/displayName`,r=new XMLHttpRequest;r.onreadystatechange=()=>{r.readyState==4&&(r.status==200?e(JSON.parse(r.response)):e(r.status))},r.open("GET",n,!0),r.setRequestHeader("Authorization","Bearer ".concat(t.access_token)),r.send()})}function Sn(t){const e=()=>{Z.update(n=>{n.account=t;const[r]=t.characters.keys();return n.character=t.characters.get(r),ke.size>0&&(n.credentials=ke.get(t.userId)),n})};Ge.update(n=>(n.set(t.userId,t),n)),ze.account&&se.selected_account?t.userId==se.selected_account&&e():ze.account||e(),We.set({})}function Ln(t,e,n){return new Promise(r=>{const o=new XMLHttpRequest;o.open("POST",e,!0),o.onreadystatechange=()=>{o.readyState==4&&r(o.status)},o.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),o.send(new URLSearchParams({token:t,client_id:n}))})}async function Me(){const t=new XMLHttpRequest;t.open("POST","/save-credentials",!0),t.setRequestHeader("Content-Type","application/json"),t.onreadystatechange=()=>{t.readyState==4&&$(`Save-credentials status: ${t.responseText.trim()}`)},Z.update(n=>{var r;return n.credentials=ke.get((r=ze.account)==null?void 0:r.userId),n});const e=[];ke.forEach(n=>{e.push(n)}),t.send(JSON.stringify(e))}function xn(t,e,n){Ue();const r=(i,u)=>{const a=new XMLHttpRequest,d={};i&&(d.hash=i),t&&(d.jx_session_id=t),e&&(d.jx_character_id=e),n&&(d.jx_display_name=n),se.rs_plugin_loader&&(d.plugin_loader="1"),se.rs_config_uri?d.config_uri=se.rs_config_uri:d.config_uri=atob(V.default_config_uri),a.open("POST","/launch-rs3-deb?".concat(new URLSearchParams(d).toString()),!0),a.onreadystatechange=()=>{a.readyState==4&&($(`Game launch status: '${a.responseText.trim()}'`),a.status==200&&i&&tt.set(i))},a.send(u)},o=new XMLHttpRequest,l=atob(V.content_url),c=l.concat("dists/trusty/non-free/binary-amd64/Packages");o.open("GET",c,!0),o.onreadystatechange=()=>{if(o.readyState==4&&o.status==200){const i=Object.fromEntries(o.response.split(` -`).map(u=>u.split(": ")));if(!i.Filename||!i.Size){q(`Could not parse package data from URL: ${c}`,!1),r();return}if(i.SHA256!==K(tt)){$("Downloading RS3 client...");const u=new XMLHttpRequest;u.open("GET",l.concat(i.Filename),!0),u.responseType="arraybuffer",u.onprogress=a=>{a.loaded&&Le.update(d=>(d[0].text=`Downloading RS3 client... ${(Math.round(1e3*a.loaded/a.total)/10).toFixed(1)}%`,d))},u.onreadystatechange=()=>{u.readyState==4&&u.status==200&&r(i.SHA256,u.response)},u.onerror=()=>{q(`Error downloading game client: from ${c}: non-http error`,!1),r()},u.send()}else $("Latest client is already installed"),r()}},o.onerror=()=>{q(`Error: from ${c}: non-http error`,!1),r()},o.send()}function Vt(t,e,n,r){Ue();const o=r?"/launch-runelite-jar-configure?":"/launch-runelite-jar?",l=(u,a,d)=>{const f=new XMLHttpRequest,p={};u&&(p.id=u),d&&(p.jar_path=d),t&&(p.jx_session_id=t),e&&(p.jx_character_id=e),n&&(p.jx_display_name=n),se.flatpak_rich_presence&&(p.flatpak_rich_presence=""),f.open(a?"POST":"GET",o.concat(new URLSearchParams(p).toString()),!0),f.onreadystatechange=()=>{f.readyState==4&&($(`Game launch status: '${f.responseText.trim()}'`),f.status==200&&u&&nt.set(u))},f.send(a)};if(se.runelite_use_custom_jar){l(null,null,se.runelite_custom_jar);return}const c=new XMLHttpRequest,i="https://api.github.com/repos/runelite/launcher/releases";c.open("GET",i,!0),c.onreadystatechange=()=>{if(c.readyState==4)if(c.status==200){const u=JSON.parse(c.responseText).map(a=>a.assets).flat().find(a=>a.name.toLowerCase()=="runelite.jar");if(u.id!=K(nt)){$("Downloading RuneLite...");const a=new XMLHttpRequest;a.open("GET",u.browser_download_url,!0),a.responseType="arraybuffer",a.onreadystatechange=()=>{a.readyState==4&&(a.status==200?l(u.id,a.response):q(`Error downloading from ${u.url}: ${a.status}: ${a.responseText}`,!1))},a.onprogress=d=>{d.loaded&&d.lengthComputable&&Le.update(f=>(f[0].text=`Downloading RuneLite... ${(Math.round(1e3*d.loaded/d.total)/10).toFixed(1)}%`,f))},a.send()}else $("Latest JAR is already installed"),l()}else q(`Error from ${i}: ${c.status}: ${c.responseText}`,!1)},c.send()}function Cn(t,e,n){return Vt(t,e,n,!1)}function Tn(t,e,n){return Vt(t,e,n,!0)}function Pn(t,e,n){Ue();const r=(c,i)=>{const u=new XMLHttpRequest,a={};c&&(a.version=c),t&&(a.jx_session_id=t),e&&(a.jx_character_id=e),n&&(a.jx_display_name=n),u.open("POST","/launch-hdos-jar?".concat(new URLSearchParams(a).toString()),!0),u.onreadystatechange=()=>{u.readyState==4&&($(`Game launch status: '${u.responseText.trim()}'`),u.status==200&&c&&st.set(c))},u.send(i)},o=new XMLHttpRequest,l="https://cdn.hdos.dev/client/getdown.txt";o.open("GET",l,!0),o.onreadystatechange=()=>{if(o.readyState==4)if(o.status==200){const c=o.responseText.match(/^launcher\.version *= *(.*?)$/m);if(c&&c.length>=2){const i=c[1];if(i!==K(st)){const u=`https://cdn.hdos.dev/launcher/v${i}/hdos-launcher.jar`;$("Downloading HDOS...");const a=new XMLHttpRequest;a.open("GET",u,!0),a.responseType="arraybuffer",a.onreadystatechange=()=>{if(a.readyState==4)if(a.status==200)r(i,a.response);else{const d=JSON.parse(o.responseText).map(f=>f.assets).flat().find(f=>f.name.toLowerCase()=="runelite.jar");q(`Error downloading from ${d.url}: ${a.status}: ${a.responseText}`,!1)}},a.onprogress=d=>{d.loaded&&d.lengthComputable&&Le.update(f=>(f[0].text=`Downloading HDOS... ${(Math.round(1e3*d.loaded/d.total)/10).toFixed(1)}%`,f))},a.send()}else $("Latest JAR is already installed"),r()}else $("Couldn't parse latest launcher version"),r()}else q(`Error from ${l}: ${o.status}: ${o.responseText}`,!1)},o.send()}let Ye=!1;function Ue(){var t;if(K(Y)&&!Ye){Ye=!0;const e=new XMLHttpRequest;e.open("POST","/save-config",!0),e.onreadystatechange=()=>{e.readyState==4&&($(`Save config status: '${e.responseText.trim()}'`),e.status==200&&Y.set(!1),Ye=!1)},e.setRequestHeader("Content-Type","application/json");const n={};(t=se.selected_characters)==null||t.forEach((l,c)=>{n[c]=l});const r={};Object.assign(r,se),r.selected_characters=n;const o=JSON.stringify(r,null,4);e.send(o)}}function Zt(){return new Promise((t,e)=>{const n=new XMLHttpRequest,r=K(Ut).concat("/list-game-clients");n.open("GET",r,!0),n.onreadystatechange=()=>{if(n.readyState==4)if(n.status==200&&n.getResponseHeader("content-type")==="application/json"){const o=JSON.parse(n.responseText);t(Object.keys(o).map(l=>({uid:l,identity:o[l].identity||null})))}else e(`error (${n.responseText})`)},n.send()})}function Rn(){const t=new XMLHttpRequest;t.open("POST","/save-plugin-config",!0),t.setRequestHeader("Content-Type","application/json"),t.onreadystatechange=()=>{t.readyState==4&&$(`Save-plugin-config status: ${t.responseText.trim()}`)},t.send(JSON.stringify(K(fe)))}async function ht(t,e,n,r,o){Ue();const l=`${e?"osrs":atob(V.provider)}-${t?"win":"mac"}`,c=t?e?$t:qt:e?Jt:Bt,i=async(O,C)=>{const b={};O&&(b.hash=O),n&&(b.jx_session_id=n),r&&(b.jx_character_id=r),o&&(b.jx_display_name=o);const T=await fetch(`/launch-${e?"osrs":"rs3"}-${t?"exe":"app"}?${new URLSearchParams(b).toString()}`,{method:"POST",headers:{"Content-Type":"application/octet-stream"},body:C});T.text().then(D=>$(`Game launch status: '${D.trim()}'`)),T.status==200&&O&&c.set(O)},u=`${atob(V.direct6_url)}${l}/${l}.json`,a=await fetch(u,{method:"GET"}),d=await a.text();if(a.status!==200){q(`Error from ${u}: ${a.status}: ${d}`,!1);return}const f=JSON.parse(atob(d.split(".")[1])).environments.production;if(K(c)===f.id){$("Latest client is already installed"),i();return}$(`Downloading client version ${f.version}`);const p=`${atob(V.direct6_url)}${l}/catalog/${f.id}/catalog.json`,k=await fetch(p,{method:"GET"}),g=await k.text();if(k.status!==200){q(`Error from ${p}: ${k.status}: ${g}`,!1);return}const E=JSON.parse(atob(g.split(".")[1])),N=E.metafile.replace(/^http:/i,"https:"),y=await fetch(N,{method:"GET"}),v=await y.text();if(y.status!==200){q(`Error from ${N}: ${y.status}: ${v}`,!1);return}const w=JSON.parse(atob(v.split(".")[1])),x=w.pieces.digests.map(O=>{const C=atob(O).split("").map(T=>T.charCodeAt(0).toString(16).padStart(2,"0")).join(""),b=E.config.remote.baseUrl.replace(/^http:/i,"https:").concat(E.config.remote.pieceFormat.replace("{SubString:0,2,{TargetDigest}}",C.substring(0,2)).replace("{TargetDigest}",C));return fetch(b,{method:"GET"}).then(T=>T.blob().then(D=>{const A=new DecompressionStream("gzip");return new Response(D.slice(6).stream().pipeThrough(A)).blob()}))});let R=0,P=null;for(let O=0;O{const C=new Blob(O).slice(R,R+P);i(w.id,C)})}function bt(t,e,n){const r=t.slice();return r[22]=e[n],r}function gt(t){let e,n=t[22][1].displayName+"",r,o,l;return{c(){e=m("option"),r=G(n),_(e,"data-id",o=t[22][1].userId),_(e,"class","dark:bg-slate-900"),e.__value=l=t[22][1].displayName,be(e,e.__value)},m(c,i){L(c,e,i),h(e,r)},p(c,i){i&4&&n!==(n=c[22][1].displayName+"")&&ae(r,n),i&4&&o!==(o=c[22][1].userId)&&_(e,"data-id",o),i&4&&l!==(l=c[22][1].displayName)&&(e.__value=l,be(e,e.__value))},d(c){c&&S(e)}}}function En(t){let e,n,r,o,l,c,i,u,a,d=pe(t[2]),f=[];for(let p=0;pn(13,r=b)),F(t,ye,b=>n(14,o=b)),F(t,Ge,b=>n(2,l=b)),F(t,J,b=>n(15,c=b));let{showAccountDropdown:i}=e,{hoverAccountButton:u}=e;const a=atob(V.origin),d=atob(V.clientid),f=a.concat("/oauth2/token"),p=a.concat("/oauth2/revoke");let k=!1,g;function E(b){u||b.button===0&&i&&!k&&n(5,i=!1)}function N(){var D;if(g.options.length==0){$("Logout unsuccessful: no account selected");return}let b=r.credentials;r.account&&(l.delete((D=r.account)==null?void 0:D.userId),Ge.set(l));const T=g.selectedIndex;T>0?n(1,g.selectedIndex=T-1,g):T==0&&l.size>0?n(1,g.selectedIndex=T+1,g):(delete r.account,delete r.character,delete r.credentials),v(),b&&Xt(b,f,d).then(A=>{A===null?Ln(b.access_token,p,d).then(X=>{X===200?($("Successful logout"),y(b)):q(`Logout unsuccessful: status ${X}`,!1)}):A===400||A===401?($("Logout unsuccessful: credentials are invalid, so discarding them anyway"),b&&y(b)):q("Logout unsuccessful: unable to verify credentials due to a network error",!1)})}function y(b){o.delete(b.sub),Me()}function v(){var T;Y.set(!0);const b=g[g.selectedIndex].getAttribute("data-id");if(j(Z,r.account=l.get(b),r),j(J,c.selected_account=b,c),j(Z,r.credentials=o.get((T=r.account)==null?void 0:T.userId),r),r.account&&r.account.characters){const D=document.getElementById("character_select");D.selectedIndex=0;const[A]=r.account.characters.keys();j(Z,r.character=r.account.characters.get(A),r)}}addEventListener("mousedown",E),De(()=>{var T;let b=0;l.forEach(D=>{var A;D.displayName==((A=r.account)==null?void 0:A.displayName)&&n(1,g.selectedIndex=b,g),b++}),j(Z,r.credentials=o.get((T=r.account)==null?void 0:T.userId),r)}),rt(()=>{removeEventListener("mousedown",E)});function w(b){z[b?"unshift":"push"](()=>{g=b,n(1,g)})}const x=()=>{v()},R=()=>{Gt()},P=()=>{N()},O=()=>{n(0,k=!0)},C=()=>{n(0,k=!1)};return t.$$set=b=>{"showAccountDropdown"in b&&n(5,i=b.showAccountDropdown),"hoverAccountButton"in b&&n(6,u=b.hoverAccountButton)},[k,g,l,N,v,i,u,w,x,R,P,O,C]}class Nn extends ce{constructor(e){super(),le(this,e,Mn,En,oe,{showAccountDropdown:5,hoverAccountButton:6})}}function In(t){let e;return{c(){e=G("Log In")},m(n,r){L(n,e,r)},p:I,d(n){n&&S(e)}}}function On(t){var r;let e=((r=t[6].account)==null?void 0:r.displayName)+"",n;return{c(){n=G(e)},m(o,l){L(o,n,l)},p(o,l){var c;l&64&&e!==(e=((c=o[6].account)==null?void 0:c.displayName)+"")&&ae(n,e)},d(o){o&&S(n)}}}function mt(t){let e,n,r,o;function l(i){t[20](i)}let c={hoverAccountButton:t[2]};return t[1]!==void 0&&(c.showAccountDropdown=t[1]),n=new Nn({props:c}),z.push(()=>Oe(n,"showAccountDropdown",l)),{c(){e=m("div"),ne(n.$$.fragment),_(e,"class","absolute right-2 top-[72px]")},m(i,u){L(i,e,u),ee(n,e,null),o=!0},p(i,u){const a={};u&4&&(a.hoverAccountButton=i[2]),!r&&u&2&&(r=!0,a.showAccountDropdown=i[1],Ie(()=>r=!1)),n.$set(a)},i(i){o||(U(n.$$.fragment,i),o=!0)},o(i){B(n.$$.fragment,i),o=!1},d(i){i&&S(e),te(n)}}}function jn(t){let e,n,r,o,l,c,i,u,a,d,f,p,k,g,E,N;function y(R,P){return R[6].account?On:In}let v=y(t),w=v(t),x=t[1]&&mt(t);return{c(){e=m("div"),n=m("div"),r=m("button"),r.textContent="RS3",o=M(),l=m("button"),l.textContent="OSRS",c=M(),i=m("div"),u=m("button"),u.innerHTML='Change Theme',a=M(),d=m("button"),d.innerHTML='Settings',f=M(),p=m("button"),w.c(),k=M(),x&&x.c(),_(r,"class","mx-1 w-20 rounded-lg border-2 border-blue-500 p-2 duration-200 hover:opacity-75"),_(l,"class","mx-1 w-20 rounded-lg border-2 border-blue-500 bg-blue-500 p-2 text-black duration-200 hover:opacity-75"),_(n,"class","m-3 ml-9 font-bold"),_(u,"class","my-3 h-10 w-10 rounded-full bg-blue-500 p-2 duration-200 hover:rotate-45 hover:opacity-75"),_(d,"class","m-3 h-10 w-10 rounded-full bg-blue-500 p-2 duration-200 hover:rotate-45 hover:opacity-75"),_(p,"class","m-2 w-48 rounded-lg border-2 border-slate-300 bg-inherit p-2 text-center font-bold text-black duration-200 hover:opacity-75 dark:border-slate-800 dark:text-slate-50"),_(i,"class","ml-auto flex"),_(e,"class","fixed top-0 flex h-16 w-screen border-b-2 border-slate-300 bg-slate-100 duration-200 dark:border-slate-800 dark:bg-slate-900")},m(R,P){L(R,e,P),h(e,n),h(n,r),t[10](r),h(n,o),h(n,l),t[12](l),h(e,c),h(e,i),h(i,u),h(i,a),h(i,d),h(i,f),h(i,p),w.m(p,null),t[16](p),h(e,k),x&&x.m(e,null),g=!0,E||(N=[H(r,"click",t[11]),H(l,"click",t[13]),H(u,"click",t[14]),H(d,"click",t[15]),H(p,"mouseenter",t[17]),H(p,"mouseleave",t[18]),H(p,"click",t[19])],E=!0)},p(R,[P]){v===(v=y(R))&&w?w.p(R,P):(w.d(1),w=v(R),w&&(w.c(),w.m(p,null))),R[1]?x?(x.p(R,P),P&2&&U(x,1)):(x=mt(R),x.c(),U(x,1),x.m(e,null)):x&&(ve(),B(x,1,1,()=>{x=null}),Se())},i(R){g||(U(x),g=!0)},o(R){B(x),g=!1},d(R){R&&S(e),t[10](null),t[12](null),w.d(),t[16](null),x&&x.d(),E=!1,re(N)}}}function Hn(t,e,n){let r,o,l;F(t,J,T=>n(21,r=T)),F(t,Y,T=>n(22,o=T)),F(t,Z,T=>n(6,l=T));let{showSettings:c}=e,i=!1,u=!1,a,d,f;function p(){let T=document.documentElement;T.classList.contains("dark")?T.classList.remove("dark"):T.classList.add("dark"),j(J,r.use_dark_theme=!r.use_dark_theme,r),j(Y,o=!0,o)}function k(T){switch(T){case Q.osrs:j(Z,l.game=Q.osrs,l),j(Z,l.client=r.selected_client_index,l),j(J,r.selected_game_index=Q.osrs,r),j(Y,o=!0,o),d.classList.add("bg-blue-500","text-black"),a.classList.remove("bg-blue-500","text-black");break;case Q.rs3:j(Z,l.game=Q.rs3,l),j(J,r.selected_game_index=Q.rs3,r),j(Y,o=!0,o),d.classList.remove("bg-blue-500","text-black"),a.classList.add("bg-blue-500","text-black");break}}function g(){if(f.innerHTML=="Log In"){Gt();return}n(1,i=!i)}De(()=>{k(r.selected_game_index)});function E(T){z[T?"unshift":"push"](()=>{a=T,n(3,a)})}const N=()=>{k(Q.rs3)};function y(T){z[T?"unshift":"push"](()=>{d=T,n(4,d)})}const v=()=>{k(Q.osrs)},w=()=>p(),x=()=>{n(0,c=!0)};function R(T){z[T?"unshift":"push"](()=>{f=T,n(5,f)})}const P=()=>{n(2,u=!0)},O=()=>{n(2,u=!1)},C=()=>g();function b(T){i=T,n(1,i)}return t.$$set=T=>{"showSettings"in T&&n(0,c=T.showSettings)},[c,i,u,a,d,f,l,p,k,g,E,N,y,v,w,x,R,P,O,C,b]}class An extends ce{constructor(e){super(),le(this,e,Hn,jn,oe,{showSettings:0})}}function Dn(t){let e,n,r,o,l,c;return{c(){e=m("div"),n=m("div"),r=M(),o=m("div"),_(n,"class","absolute left-0 top-0 z-10 h-screen w-screen backdrop-blur-sm backdrop-filter"),_(o,"class","absolute left-0 top-0 z-10 h-screen w-screen bg-black opacity-75"),_(o,"role","none")},m(i,u){L(i,e,u),h(e,n),h(e,r),h(e,o),l||(c=H(o,"click",t[1]),l=!0)},p:I,i:I,o:I,d(i){i&&S(e),l=!1,c()}}}function Un(t){const e=ln();return[e,r=>{e("click",r)}]}class at extends ce{constructor(e){super(),le(this,e,Un,Dn,oe,{})}}function qn(t){let e,n,r,o,l,c,i,u,a,d,f,p;return n=new at({}),{c(){e=m("div"),ne(n.$$.fragment),r=M(),o=m("div"),l=m("p"),l.textContent=`${t[1]}`,c=M(),i=m("p"),i.textContent=`${t[2]}`,u=M(),a=m("button"),a.textContent="I Understand",_(l,"class","p-2"),_(i,"class","p-2"),_(a,"class","m-5 rounded-lg border-2 border-blue-500 p-2 duration-200 hover:opacity-75"),_(o,"class","absolute left-1/4 top-1/4 z-20 w-1/2 rounded-lg bg-slate-100 p-5 text-center shadow-lg dark:bg-slate-900"),_(e,"id","disclaimer")},m(k,g){L(k,e,g),ee(n,e,null),h(e,r),h(e,o),h(o,l),h(o,c),h(o,i),h(o,u),h(o,a),d=!0,f||(p=H(a,"click",t[3]),f=!0)},p:I,i(k){d||(U(n.$$.fragment,k),d=!0)},o(k){B(n.$$.fragment,k),d=!1},d(k){k&&S(e),te(n),f=!1,p()}}}function Bn(t,e,n){let r;F(t,Xe,i=>n(0,r=i));const o=atob("Qm9sdCBpcyBhbiB1bm9mZmljaWFsIHRoaXJkLXBhcnR5IGxhdW5jaGVyLiBJdCdzIGZyZWUgYW5kIG9wZW4tc291cmNlIHNvZnR3YXJlIGxpY2Vuc2VkIHVuZGVyIEFHUEwgMy4wLg=="),l=atob("SmFnZXggaXMgbm90IHJlc3BvbnNpYmxlIGZvciBhbnkgcHJvYmxlbXMgb3IgZGFtYWdlIGNhdXNlZCBieSB1c2luZyB0aGlzIHByb2R1Y3Qu");return[r,o,l,()=>{j(Xe,r=!1,r)}]}class $n extends ce{constructor(e){super(),le(this,e,Bn,qn,oe,{})}}function Jn(t){let e,n,r,o;return{c(){e=m("div"),n=m("button"),n.innerHTML=`
Browse app data +`).map(u=>u.split(": ")));if(!i.Filename||!i.Size){q(`Could not parse package data from URL: ${c}`,!1),r();return}if(i.SHA256!==K(tt)){$("Downloading RS3 client...");const u=new XMLHttpRequest;u.open("GET",l.concat(i.Filename),!0),u.responseType="arraybuffer",u.onprogress=a=>{a.loaded&&Le.update(d=>(d[0].text=`Downloading RS3 client... ${(Math.round(1e3*a.loaded/a.total)/10).toFixed(1)}%`,d))},u.onreadystatechange=()=>{u.readyState==4&&u.status==200&&r(i.SHA256,u.response)},u.onerror=()=>{q(`Error downloading game client: from ${c}: non-http error`,!1),r()},u.send()}else $("Latest client is already installed"),r()}},o.onerror=()=>{q(`Error: from ${c}: non-http error`,!1),r()},o.send()}function Vt(t,e,n,r){Ue();const o=r?"/launch-runelite-jar-configure?":"/launch-runelite-jar?",l=(u,a,d)=>{const f=new XMLHttpRequest,p={};u&&(p.id=u),d&&(p.jar_path=d),t&&(p.jx_session_id=t),e&&(p.jx_character_id=e),n&&(p.jx_display_name=n),se.flatpak_rich_presence&&(p.flatpak_rich_presence=""),f.open(a?"POST":"GET",o.concat(new URLSearchParams(p).toString()),!0),f.onreadystatechange=()=>{f.readyState==4&&($(`Game launch status: '${f.responseText.trim()}'`),f.status==200&&u&&nt.set(u))},f.send(a)};if(se.runelite_use_custom_jar){l(null,null,se.runelite_custom_jar);return}const c=new XMLHttpRequest,i="https://api.github.com/repos/runelite/launcher/releases";c.open("GET",i,!0),c.onreadystatechange=()=>{if(c.readyState==4)if(c.status==200){const u=JSON.parse(c.responseText).map(a=>a.assets).flat().find(a=>a.name.toLowerCase()=="runelite.jar");if(u.id!=K(nt)){$("Downloading RuneLite...");const a=new XMLHttpRequest;a.open("GET",u.browser_download_url,!0),a.responseType="arraybuffer",a.onreadystatechange=()=>{a.readyState==4&&(a.status==200?l(u.id,a.response):q(`Error downloading from ${u.url}: ${a.status}: ${a.responseText}`,!1))},a.onprogress=d=>{d.loaded&&d.lengthComputable&&Le.update(f=>(f[0].text=`Downloading RuneLite... ${(Math.round(1e3*d.loaded/d.total)/10).toFixed(1)}%`,f))},a.send()}else $("Latest JAR is already installed"),l()}else q(`Error from ${i}: ${c.status}: ${c.responseText}`,!1)},c.send()}function Cn(t,e,n){return Vt(t,e,n,!1)}function Tn(t,e,n){return Vt(t,e,n,!0)}function Pn(t,e,n){Ue();const r=(c,i)=>{const u=new XMLHttpRequest,a={};c&&(a.version=c),t&&(a.jx_session_id=t),e&&(a.jx_character_id=e),n&&(a.jx_display_name=n),u.open("POST","/launch-hdos-jar?".concat(new URLSearchParams(a).toString()),!0),u.onreadystatechange=()=>{u.readyState==4&&($(`Game launch status: '${u.responseText.trim()}'`),u.status==200&&c&&st.set(c))},u.send(i)},o=new XMLHttpRequest,l="https://cdn.hdos.dev/client/getdown.txt";o.open("GET",l,!0),o.onreadystatechange=()=>{if(o.readyState==4)if(o.status==200){const c=o.responseText.match(/^launcher\.version *= *(.*?)$/m);if(c&&c.length>=2){const i=c[1];if(i!==K(st)){const u=`https://cdn.hdos.dev/launcher/v${i}/hdos-launcher.jar`;$("Downloading HDOS...");const a=new XMLHttpRequest;a.open("GET",u,!0),a.responseType="arraybuffer",a.onreadystatechange=()=>{if(a.readyState==4)if(a.status==200)r(i,a.response);else{const d=JSON.parse(o.responseText).map(f=>f.assets).flat().find(f=>f.name.toLowerCase()=="runelite.jar");q(`Error downloading from ${d.url}: ${a.status}: ${a.responseText}`,!1)}},a.onprogress=d=>{d.loaded&&d.lengthComputable&&Le.update(f=>(f[0].text=`Downloading HDOS... ${(Math.round(1e3*d.loaded/d.total)/10).toFixed(1)}%`,f))},a.send()}else $("Latest JAR is already installed"),r()}else $("Couldn't parse latest launcher version"),r()}else q(`Error from ${l}: ${o.status}: ${o.responseText}`,!1)},o.send()}let Ye=!1;function Ue(){var t;if(K(Y)&&!Ye){Ye=!0;const e=new XMLHttpRequest;e.open("POST","/save-config",!0),e.onreadystatechange=()=>{e.readyState==4&&($(`Save config status: '${e.responseText.trim()}'`),e.status==200&&Y.set(!1),Ye=!1)},e.setRequestHeader("Content-Type","application/json");const n={};(t=se.selected_characters)==null||t.forEach((l,c)=>{n[c]=l});const r={};Object.assign(r,se),r.selected_characters=n;const o=JSON.stringify(r,null,4);e.send(o)}}function Zt(){return new Promise((t,e)=>{const n=new XMLHttpRequest,r=K(Ut).concat("/list-game-clients");n.open("GET",r,!0),n.onreadystatechange=()=>{if(n.readyState==4)if(n.status==200&&n.getResponseHeader("content-type")==="application/json"){const o=JSON.parse(n.responseText);t(Object.keys(o).map(l=>({uid:l,identity:o[l].identity||null})))}else e(`error (${n.responseText})`)},n.send()})}function Rn(){const t=new XMLHttpRequest;t.open("POST","/save-plugin-config",!0),t.setRequestHeader("Content-Type","application/json"),t.onreadystatechange=()=>{t.readyState==4&&$(`Save-plugin-config status: ${t.responseText.trim()}`)},t.send(JSON.stringify(K(fe)))}async function ht(t,e,n,r,o){Ue();const l=`${e?"osrs":atob(V.provider)}-${t?"win":"mac"}`,c=t?e?$t:qt:e?Jt:Bt,i=async(O,C)=>{const b={};O&&(b.hash=O),n&&(b.jx_session_id=n),r&&(b.jx_character_id=r),o&&(b.jx_display_name=o);const T=await fetch(`/launch-${e?"osrs":"rs3"}-${t?"exe":"app"}?${new URLSearchParams(b).toString()}`,{method:"POST",headers:{"Content-Type":"application/octet-stream"},body:await C});T.text().then(D=>$(`Game launch status: '${D.trim()}'`)),T.status==200&&O&&c.set(O)},u=`${atob(V.direct6_url)}${l}/${l}.json`,a=await fetch(u,{method:"GET"}),d=await a.text();if(a.status!==200){q(`Error from ${u}: ${a.status}: ${d}`,!1);return}const f=JSON.parse(atob(d.split(".")[1])).environments.production;if(K(c)===f.id){$("Latest client is already installed"),i();return}$(`Downloading client version ${f.version}`);const p=`${atob(V.direct6_url)}${l}/catalog/${f.id}/catalog.json`,k=await fetch(p,{method:"GET"}),g=await k.text();if(k.status!==200){q(`Error from ${p}: ${k.status}: ${g}`,!1);return}const E=JSON.parse(atob(g.split(".")[1])),N=E.metafile.replace(/^http:/i,"https:"),y=await fetch(N,{method:"GET"}),v=await y.text();if(y.status!==200){q(`Error from ${N}: ${y.status}: ${v}`,!1);return}const w=JSON.parse(atob(v.split(".")[1])),x=w.pieces.digests.map(O=>{const C=atob(O).split("").map(T=>T.charCodeAt(0).toString(16).padStart(2,"0")).join(""),b=E.config.remote.baseUrl.replace(/^http:/i,"https:").concat(E.config.remote.pieceFormat.replace("{SubString:0,2,{TargetDigest}}",C.substring(0,2)).replace("{TargetDigest}",C));return fetch(b,{method:"GET"}).then(T=>T.blob().then(D=>{const A=new DecompressionStream("gzip");return new Response(D.slice(6).stream().pipeThrough(A)).blob()}))});let R=0,P=null;for(let O=0;O{const C=new Blob(O).slice(R,R+P);i(w.id,C.arrayBuffer())})}function bt(t,e,n){const r=t.slice();return r[22]=e[n],r}function gt(t){let e,n=t[22][1].displayName+"",r,o,l;return{c(){e=m("option"),r=G(n),_(e,"data-id",o=t[22][1].userId),_(e,"class","dark:bg-slate-900"),e.__value=l=t[22][1].displayName,be(e,e.__value)},m(c,i){L(c,e,i),h(e,r)},p(c,i){i&4&&n!==(n=c[22][1].displayName+"")&&ae(r,n),i&4&&o!==(o=c[22][1].userId)&&_(e,"data-id",o),i&4&&l!==(l=c[22][1].displayName)&&(e.__value=l,be(e,e.__value))},d(c){c&&S(e)}}}function En(t){let e,n,r,o,l,c,i,u,a,d=pe(t[2]),f=[];for(let p=0;pn(13,r=b)),F(t,ye,b=>n(14,o=b)),F(t,Ge,b=>n(2,l=b)),F(t,J,b=>n(15,c=b));let{showAccountDropdown:i}=e,{hoverAccountButton:u}=e;const a=atob(V.origin),d=atob(V.clientid),f=a.concat("/oauth2/token"),p=a.concat("/oauth2/revoke");let k=!1,g;function E(b){u||b.button===0&&i&&!k&&n(5,i=!1)}function N(){var D;if(g.options.length==0){$("Logout unsuccessful: no account selected");return}let b=r.credentials;r.account&&(l.delete((D=r.account)==null?void 0:D.userId),Ge.set(l));const T=g.selectedIndex;T>0?n(1,g.selectedIndex=T-1,g):T==0&&l.size>0?n(1,g.selectedIndex=T+1,g):(delete r.account,delete r.character,delete r.credentials),v(),b&&Xt(b,f,d).then(A=>{A===null?Ln(b.access_token,p,d).then(X=>{X===200?($("Successful logout"),y(b)):q(`Logout unsuccessful: status ${X}`,!1)}):A===400||A===401?($("Logout unsuccessful: credentials are invalid, so discarding them anyway"),b&&y(b)):q("Logout unsuccessful: unable to verify credentials due to a network error",!1)})}function y(b){o.delete(b.sub),Me()}function v(){var T;Y.set(!0);const b=g[g.selectedIndex].getAttribute("data-id");if(j(Z,r.account=l.get(b),r),j(J,c.selected_account=b,c),j(Z,r.credentials=o.get((T=r.account)==null?void 0:T.userId),r),r.account&&r.account.characters){const D=document.getElementById("character_select");D.selectedIndex=0;const[A]=r.account.characters.keys();j(Z,r.character=r.account.characters.get(A),r)}}addEventListener("mousedown",E),De(()=>{var T;let b=0;l.forEach(D=>{var A;D.displayName==((A=r.account)==null?void 0:A.displayName)&&n(1,g.selectedIndex=b,g),b++}),j(Z,r.credentials=o.get((T=r.account)==null?void 0:T.userId),r)}),rt(()=>{removeEventListener("mousedown",E)});function w(b){z[b?"unshift":"push"](()=>{g=b,n(1,g)})}const x=()=>{v()},R=()=>{Gt()},P=()=>{N()},O=()=>{n(0,k=!0)},C=()=>{n(0,k=!1)};return t.$$set=b=>{"showAccountDropdown"in b&&n(5,i=b.showAccountDropdown),"hoverAccountButton"in b&&n(6,u=b.hoverAccountButton)},[k,g,l,N,v,i,u,w,x,R,P,O,C]}class Nn extends ce{constructor(e){super(),le(this,e,Mn,En,oe,{showAccountDropdown:5,hoverAccountButton:6})}}function In(t){let e;return{c(){e=G("Log In")},m(n,r){L(n,e,r)},p:I,d(n){n&&S(e)}}}function On(t){var r;let e=((r=t[6].account)==null?void 0:r.displayName)+"",n;return{c(){n=G(e)},m(o,l){L(o,n,l)},p(o,l){var c;l&64&&e!==(e=((c=o[6].account)==null?void 0:c.displayName)+"")&&ae(n,e)},d(o){o&&S(n)}}}function mt(t){let e,n,r,o;function l(i){t[20](i)}let c={hoverAccountButton:t[2]};return t[1]!==void 0&&(c.showAccountDropdown=t[1]),n=new Nn({props:c}),z.push(()=>Oe(n,"showAccountDropdown",l)),{c(){e=m("div"),ne(n.$$.fragment),_(e,"class","absolute right-2 top-[72px]")},m(i,u){L(i,e,u),ee(n,e,null),o=!0},p(i,u){const a={};u&4&&(a.hoverAccountButton=i[2]),!r&&u&2&&(r=!0,a.showAccountDropdown=i[1],Ie(()=>r=!1)),n.$set(a)},i(i){o||(U(n.$$.fragment,i),o=!0)},o(i){B(n.$$.fragment,i),o=!1},d(i){i&&S(e),te(n)}}}function jn(t){let e,n,r,o,l,c,i,u,a,d,f,p,k,g,E,N;function y(R,P){return R[6].account?On:In}let v=y(t),w=v(t),x=t[1]&&mt(t);return{c(){e=m("div"),n=m("div"),r=m("button"),r.textContent="RS3",o=M(),l=m("button"),l.textContent="OSRS",c=M(),i=m("div"),u=m("button"),u.innerHTML='Change Theme',a=M(),d=m("button"),d.innerHTML='Settings',f=M(),p=m("button"),w.c(),k=M(),x&&x.c(),_(r,"class","mx-1 w-20 rounded-lg border-2 border-blue-500 p-2 duration-200 hover:opacity-75"),_(l,"class","mx-1 w-20 rounded-lg border-2 border-blue-500 bg-blue-500 p-2 text-black duration-200 hover:opacity-75"),_(n,"class","m-3 ml-9 font-bold"),_(u,"class","my-3 h-10 w-10 rounded-full bg-blue-500 p-2 duration-200 hover:rotate-45 hover:opacity-75"),_(d,"class","m-3 h-10 w-10 rounded-full bg-blue-500 p-2 duration-200 hover:rotate-45 hover:opacity-75"),_(p,"class","m-2 w-48 rounded-lg border-2 border-slate-300 bg-inherit p-2 text-center font-bold text-black duration-200 hover:opacity-75 dark:border-slate-800 dark:text-slate-50"),_(i,"class","ml-auto flex"),_(e,"class","fixed top-0 flex h-16 w-screen border-b-2 border-slate-300 bg-slate-100 duration-200 dark:border-slate-800 dark:bg-slate-900")},m(R,P){L(R,e,P),h(e,n),h(n,r),t[10](r),h(n,o),h(n,l),t[12](l),h(e,c),h(e,i),h(i,u),h(i,a),h(i,d),h(i,f),h(i,p),w.m(p,null),t[16](p),h(e,k),x&&x.m(e,null),g=!0,E||(N=[H(r,"click",t[11]),H(l,"click",t[13]),H(u,"click",t[14]),H(d,"click",t[15]),H(p,"mouseenter",t[17]),H(p,"mouseleave",t[18]),H(p,"click",t[19])],E=!0)},p(R,[P]){v===(v=y(R))&&w?w.p(R,P):(w.d(1),w=v(R),w&&(w.c(),w.m(p,null))),R[1]?x?(x.p(R,P),P&2&&U(x,1)):(x=mt(R),x.c(),U(x,1),x.m(e,null)):x&&(ve(),B(x,1,1,()=>{x=null}),Se())},i(R){g||(U(x),g=!0)},o(R){B(x),g=!1},d(R){R&&S(e),t[10](null),t[12](null),w.d(),t[16](null),x&&x.d(),E=!1,re(N)}}}function Hn(t,e,n){let r,o,l;F(t,J,T=>n(21,r=T)),F(t,Y,T=>n(22,o=T)),F(t,Z,T=>n(6,l=T));let{showSettings:c}=e,i=!1,u=!1,a,d,f;function p(){let T=document.documentElement;T.classList.contains("dark")?T.classList.remove("dark"):T.classList.add("dark"),j(J,r.use_dark_theme=!r.use_dark_theme,r),j(Y,o=!0,o)}function k(T){switch(T){case Q.osrs:j(Z,l.game=Q.osrs,l),j(Z,l.client=r.selected_client_index,l),j(J,r.selected_game_index=Q.osrs,r),j(Y,o=!0,o),d.classList.add("bg-blue-500","text-black"),a.classList.remove("bg-blue-500","text-black");break;case Q.rs3:j(Z,l.game=Q.rs3,l),j(J,r.selected_game_index=Q.rs3,r),j(Y,o=!0,o),d.classList.remove("bg-blue-500","text-black"),a.classList.add("bg-blue-500","text-black");break}}function g(){if(f.innerHTML=="Log In"){Gt();return}n(1,i=!i)}De(()=>{k(r.selected_game_index)});function E(T){z[T?"unshift":"push"](()=>{a=T,n(3,a)})}const N=()=>{k(Q.rs3)};function y(T){z[T?"unshift":"push"](()=>{d=T,n(4,d)})}const v=()=>{k(Q.osrs)},w=()=>p(),x=()=>{n(0,c=!0)};function R(T){z[T?"unshift":"push"](()=>{f=T,n(5,f)})}const P=()=>{n(2,u=!0)},O=()=>{n(2,u=!1)},C=()=>g();function b(T){i=T,n(1,i)}return t.$$set=T=>{"showSettings"in T&&n(0,c=T.showSettings)},[c,i,u,a,d,f,l,p,k,g,E,N,y,v,w,x,R,P,O,C,b]}class An extends ce{constructor(e){super(),le(this,e,Hn,jn,oe,{showSettings:0})}}function Dn(t){let e,n,r,o,l,c;return{c(){e=m("div"),n=m("div"),r=M(),o=m("div"),_(n,"class","absolute left-0 top-0 z-10 h-screen w-screen backdrop-blur-sm backdrop-filter"),_(o,"class","absolute left-0 top-0 z-10 h-screen w-screen bg-black opacity-75"),_(o,"role","none")},m(i,u){L(i,e,u),h(e,n),h(e,r),h(e,o),l||(c=H(o,"click",t[1]),l=!0)},p:I,i:I,o:I,d(i){i&&S(e),l=!1,c()}}}function Un(t){const e=ln();return[e,r=>{e("click",r)}]}class at extends ce{constructor(e){super(),le(this,e,Un,Dn,oe,{})}}function qn(t){let e,n,r,o,l,c,i,u,a,d,f,p;return n=new at({}),{c(){e=m("div"),ne(n.$$.fragment),r=M(),o=m("div"),l=m("p"),l.textContent=`${t[1]}`,c=M(),i=m("p"),i.textContent=`${t[2]}`,u=M(),a=m("button"),a.textContent="I Understand",_(l,"class","p-2"),_(i,"class","p-2"),_(a,"class","m-5 rounded-lg border-2 border-blue-500 p-2 duration-200 hover:opacity-75"),_(o,"class","absolute left-1/4 top-1/4 z-20 w-1/2 rounded-lg bg-slate-100 p-5 text-center shadow-lg dark:bg-slate-900"),_(e,"id","disclaimer")},m(k,g){L(k,e,g),ee(n,e,null),h(e,r),h(e,o),h(o,l),h(o,c),h(o,i),h(o,u),h(o,a),d=!0,f||(p=H(a,"click",t[3]),f=!0)},p:I,i(k){d||(U(n.$$.fragment,k),d=!0)},o(k){B(n.$$.fragment,k),d=!1},d(k){k&&S(e),te(n),f=!1,p()}}}function Bn(t,e,n){let r;F(t,Xe,i=>n(0,r=i));const o=atob("Qm9sdCBpcyBhbiB1bm9mZmljaWFsIHRoaXJkLXBhcnR5IGxhdW5jaGVyLiBJdCdzIGZyZWUgYW5kIG9wZW4tc291cmNlIHNvZnR3YXJlIGxpY2Vuc2VkIHVuZGVyIEFHUEwgMy4wLg=="),l=atob("SmFnZXggaXMgbm90IHJlc3BvbnNpYmxlIGZvciBhbnkgcHJvYmxlbXMgb3IgZGFtYWdlIGNhdXNlZCBieSB1c2luZyB0aGlzIHByb2R1Y3Qu");return[r,o,l,()=>{j(Xe,r=!1,r)}]}class $n extends ce{constructor(e){super(),le(this,e,Bn,qn,oe,{})}}function Jn(t){let e,n,r,o;return{c(){e=m("div"),n=m("button"),n.innerHTML=`
Browse app data Browse App Data
`,_(n,"id","data_dir_button"),_(n,"class","p-2 hover:opacity-75"),_(e,"id","general_options"),_(e,"class","col-span-3 p-5 pt-10")},m(l,c){L(l,e,c),h(e,n),r||(o=H(n,"click",t[1]),r=!0)},p:I,i:I,o:I,d(l){l&&S(e),r=!1,o()}}}function Gn(t){function e(){var r=new XMLHttpRequest;r.open("GET","/browse-data"),r.onreadystatechange=()=>{r.readyState==4&&$(`Browse status: '${r.responseText.trim()}'`)},r.send()}return[e,()=>{e()}]}class Xn extends ce{constructor(e){super(),le(this,e,Gn,Jn,oe,{})}}function zn(t){let e,n,r,o,l,c,i,u,a,d,f,p,k,g,E,N;return{c(){e=m("div"),n=m("button"),n.innerHTML=`
Configure RuneLite Configure RuneLite
`,r=M(),o=m("div"),l=m("label"),l.textContent="Use custom RuneLite JAR:",c=M(),i=m("input"),u=M(),a=m("div"),d=m("textarea"),f=M(),p=m("br"),k=M(),g=m("button"),g.textContent="Select File",_(n,"id","rl_configure"),_(n,"class","p-2 pb-5 hover:opacity-75"),_(l,"for","use_custom_jar"),_(i,"type","checkbox"),_(i,"name","use_custom_jar"),_(i,"id","use_custom_jar"),_(i,"class","ml-2"),_(o,"class","mx-auto border-t-2 border-slate-300 p-2 pt-5 dark:border-slate-800"),d.disabled=!0,_(d,"name","custom_jar_file"),_(d,"id","custom_jar_file"),_(d,"class","h-10 rounded border-2 border-slate-300 bg-slate-100 text-slate-950 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-50"),g.disabled=!0,_(g,"id","custom_jar_file_button"),_(g,"class","mt-1 rounded-lg border-2 border-blue-500 p-1 duration-200 hover:opacity-75"),_(a,"id","custom_jar_div"),_(a,"class","mx-auto p-2 opacity-25"),_(e,"id","osrs_options"),_(e,"class","col-span-3 p-5 pt-10")},m(y,v){L(y,e,v),h(e,n),h(e,r),h(e,o),h(o,l),h(o,c),h(o,i),t[9](i),h(e,u),h(e,a),h(a,d),t[11](d),h(a,f),h(a,p),h(a,k),h(a,g),t[13](g),t[15](a),E||(N=[H(n,"click",t[8]),H(i,"change",t[10]),H(d,"change",t[12]),H(g,"click",t[14])],E=!0)},p:I,i:I,o:I,d(y){y&&S(e),t[9](null),t[11](null),t[13](null),t[15](null),E=!1,re(N)}}}function Fn(t,e,n){let r,o,l;F(t,J,P=>n(16,r=P)),F(t,Z,P=>n(17,o=P)),F(t,Y,P=>n(18,l=P));let c,i,u,a;function d(){c.classList.toggle("opacity-25"),n(1,i.disabled=!i.disabled,i),n(2,u.disabled=!u.disabled,u),j(J,r.runelite_use_custom_jar=a.checked,r),r.runelite_use_custom_jar?i.value&&j(J,r.runelite_custom_jar=i.value,r):(n(1,i.value="",i),j(J,r.runelite_custom_jar="",r),j(J,r.runelite_use_custom_jar=!1,r)),j(Y,l=!0,l)}function f(){j(J,r.runelite_custom_jar=i.value,r),j(Y,l=!0,l)}function p(){n(3,a.disabled=!0,a),n(2,u.disabled=!0,u);var P=new XMLHttpRequest;P.onreadystatechange=()=>{P.readyState==4&&(P.status==200&&(n(1,i.value=P.responseText,i),j(J,r.runelite_custom_jar=P.responseText,r),j(Y,l=!0,l)),n(2,u.disabled=!1,u),n(3,a.disabled=!1,a))},P.open("GET","/jar-file-picker",!0),P.send()}function k(){var P,O,C;if(!o.account||!o.character){$("Please log in to configure RuneLite");return}Tn((P=o.credentials)==null?void 0:P.session_id,(O=o.character)==null?void 0:O.accountId,(C=o.character)==null?void 0:C.displayName)}De(()=>{n(3,a.checked=r.runelite_use_custom_jar,a),a.checked&&r.runelite_custom_jar?(c.classList.remove("opacity-25"),n(1,i.disabled=!1,i),n(2,u.disabled=!1,u),n(1,i.value=r.runelite_custom_jar,i)):(n(3,a.checked=!1,a),j(J,r.runelite_use_custom_jar=!1,r))});const g=()=>k();function E(P){z[P?"unshift":"push"](()=>{a=P,n(3,a)})}const N=()=>{d()};function y(P){z[P?"unshift":"push"](()=>{i=P,n(1,i)})}const v=()=>{f()};function w(P){z[P?"unshift":"push"](()=>{u=P,n(2,u)})}const x=()=>{p()};function R(P){z[P?"unshift":"push"](()=>{c=P,n(0,c)})}return[c,i,u,a,d,f,p,k,g,E,N,y,v,w,x,R]}class Wn extends ce{constructor(e){super(),le(this,e,Fn,zn,oe,{})}}function Vn(t){let e,n,r,o,l,c;return{c(){e=m("div"),n=m("label"),n.textContent="Enable Bolt plugin loader:",r=M(),o=m("input"),_(n,"for","enable_plugins"),_(o,"type","checkbox"),_(o,"name","enable_plugins"),_(o,"id","enable_plugins"),_(o,"class","ml-2"),_(e,"class","mx-auto p-2")},m(i,u){L(i,e,u),h(e,n),h(e,r),h(e,o),o.checked=t[3].rs_plugin_loader,l||(c=[H(o,"change",t[6]),H(o,"change",t[7])],l=!0)},p(i,u){u&8&&(o.checked=i[3].rs_plugin_loader)},d(i){i&&S(e),l=!1,re(c)}}}function Zn(t){let e,n,r,o,l,c,i,u,a,d,f,p=ge&&Vn(t);return{c(){e=m("div"),p&&p.c(),n=M(),r=m("div"),o=m("label"),o.textContent="Use custom config URI:",l=M(),c=m("input"),i=M(),u=m("div"),a=m("textarea"),_(o,"for","use_custom_uri"),_(c,"type","checkbox"),_(c,"name","use_custom_uri"),_(c,"id","use_custom_uri"),_(c,"class","ml-2"),_(r,"class","mx-auto p-2"),a.disabled=!0,_(a,"name","config_uri_address"),_(a,"id","config_uri_address"),_(a,"rows","4"),_(a,"class","rounded border-2 border-slate-300 bg-slate-100 text-slate-950 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-50"),a.value=" ",_(u,"id","config_uri_div"),_(u,"class","mx-auto p-2 opacity-25"),_(e,"id","rs3_options"),_(e,"class","col-span-3 p-5 pt-10")},m(k,g){L(k,e,g),p&&p.m(e,null),h(e,n),h(e,r),h(r,o),h(r,l),h(r,c),t[8](c),h(e,i),h(e,u),h(u,a),t[11](a),t[12](u),d||(f=[H(c,"change",t[9]),H(a,"change",t[10])],d=!0)},p(k,[g]){ge&&p.p(k,g)},i:I,o:I,d(k){k&&S(e),p&&p.d(),t[8](null),t[11](null),t[12](null),d=!1,re(f)}}}function Yn(t,e,n){let r,o,l;F(t,ct,v=>n(13,r=v)),F(t,J,v=>n(3,o=v)),F(t,Y,v=>n(14,l=v));let c,i,u;function a(){c.classList.toggle("opacity-25"),n(1,i.disabled=!i.disabled,i),j(Y,l=!0,l),u.checked||(n(1,i.value=atob(r.default_config_uri),i),j(J,o.rs_config_uri="",o))}function d(){j(J,o.rs_config_uri=i.value,o),j(Y,l=!0,l)}De(()=>{o.rs_config_uri?(n(1,i.value=o.rs_config_uri,i),n(2,u.checked=!0,u),a()):n(1,i.value=atob(r.default_config_uri),i)});function f(){o.rs_plugin_loader=this.checked,J.set(o)}const p=()=>Y.set(!0);function k(v){z[v?"unshift":"push"](()=>{u=v,n(2,u)})}const g=()=>{a()},E=()=>{d()};function N(v){z[v?"unshift":"push"](()=>{i=v,n(1,i)})}function y(v){z[v?"unshift":"push"](()=>{c=v,n(0,c)})}return[c,i,u,o,a,d,f,p,k,g,E,N,y]}class Kn extends ce{constructor(e){super(),le(this,e,Yn,Zn,oe,{})}}function Qn(t){let e,n;return e=new Kn({}),{c(){ne(e.$$.fragment)},m(r,o){ee(e,r,o),n=!0},i(r){n||(U(e.$$.fragment,r),n=!0)},o(r){B(e.$$.fragment,r),n=!1},d(r){te(e,r)}}}function es(t){let e,n;return e=new Wn({}),{c(){ne(e.$$.fragment)},m(r,o){ee(e,r,o),n=!0},i(r){n||(U(e.$$.fragment,r),n=!0)},o(r){B(e.$$.fragment,r),n=!1},d(r){te(e,r)}}}function ts(t){let e,n;return e=new Xn({}),{c(){ne(e.$$.fragment)},m(r,o){ee(e,r,o),n=!0},i(r){n||(U(e.$$.fragment,r),n=!0)},o(r){B(e.$$.fragment,r),n=!1},d(r){te(e,r)}}}function ns(t){let e,n,r,o,l,c,i,u,a,d,f,p,k,g,E,N,y,v,w,x,R,P,O,C;n=new at({}),n.$on("click",t[7]);const b=[ts,es,Qn],T=[];function D(A,X){return A[1]==A[5].general?0:A[1]==A[5].osrs?1:A[1]==A[5].rs3?2:-1}return~(x=D(t))&&(R=T[x]=b[x](t)),{c(){e=m("div"),ne(n.$$.fragment),r=M(),o=m("div"),l=m("button"),l.innerHTML='Close',c=M(),i=m("div"),u=m("div"),a=m("button"),d=G("General"),f=m("br"),p=M(),k=m("button"),g=G("OSRS"),E=m("br"),N=M(),y=m("button"),v=G("RS3"),w=M(),R&&R.c(),_(l,"class","absolute right-3 top-3 rounded-full bg-rose-500 p-[2px] shadow-lg duration-200 hover:rotate-90 hover:opacity-75"),_(a,"id","general_button"),_(a,"class",me),_(k,"id","osrs_button"),_(k,"class",$e),_(y,"id","rs3_button"),_(y,"class",me),_(u,"class","relative h-full border-r-2 border-slate-300 pt-10 dark:border-slate-800"),_(i,"class","grid h-full grid-cols-4"),_(o,"class","absolute left-[13%] top-[13%] z-20 h-3/4 w-3/4 rounded-lg bg-slate-100 text-center shadow-lg dark:bg-slate-900"),_(e,"id","settings")},m(A,X){L(A,e,X),ee(n,e,null),h(e,r),h(e,o),h(o,l),h(o,c),h(o,i),h(i,u),h(u,a),h(a,d),t[9](a),h(u,f),h(u,p),h(u,k),h(k,g),t[11](k),h(u,E),h(u,N),h(u,y),h(y,v),t[13](y),h(i,w),~x&&T[x].m(i,null),P=!0,O||(C=[H(l,"click",t[8]),H(a,"click",t[10]),H(k,"click",t[12]),H(y,"click",t[14])],O=!0)},p(A,[X]){let xe=x;x=D(A),x!==xe&&(R&&(ve(),B(T[xe],1,1,()=>{T[xe]=null}),Se()),~x?(R=T[x],R||(R=T[x]=b[x](A),R.c()),U(R,1),R.m(i,null)):R=null)},i(A){P||(U(n.$$.fragment,A),U(R),P=!0)},o(A){B(n.$$.fragment,A),B(R),P=!1},d(A){A&&S(e),te(n),t[9](null),t[11](null),t[13](null),~x&&T[x].d(),O=!1,re(C)}}}let $e="border-2 border-blue-500 bg-blue-500 hover:opacity-75 font-bold text-black duration-200 rounded-lg p-1 mx-auto my-1 w-3/4",me="border-2 border-blue-500 hover:opacity-75 duration-200 rounded-lg p-1 mx-auto my-1 w-3/4";function ss(t,e,n){let{showSettings:r}=e;var o=(w=>(w[w.general=0]="general",w[w.osrs=1]="osrs",w[w.rs3=2]="rs3",w))(o||{});let l=1,c,i,u;function a(w){switch(w){case 0:n(1,l=0),n(2,c.classList.value=$e,c),n(3,i.classList.value=me,i),n(4,u.classList.value=me,u);break;case 1:n(1,l=1),n(2,c.classList.value=me,c),n(3,i.classList.value=$e,i),n(4,u.classList.value=me,u);break;case 2:n(1,l=2),n(2,c.classList.value=me,c),n(3,i.classList.value=me,i),n(4,u.classList.value=$e,u);break}}function d(w){w.key==="Escape"&&n(0,r=!1)}addEventListener("keydown",d),rt(()=>{removeEventListener("keydown",d)});const f=()=>{n(0,r=!1)},p=()=>{n(0,r=!1)};function k(w){z[w?"unshift":"push"](()=>{c=w,n(2,c)})}const g=()=>{a(o.general)};function E(w){z[w?"unshift":"push"](()=>{i=w,n(3,i)})}const N=()=>{a(o.osrs)};function y(w){z[w?"unshift":"push"](()=>{u=w,n(4,u)})}const v=()=>{a(o.rs3)};return t.$$set=w=>{"showSettings"in w&&n(0,r=w.showSettings)},[r,l,c,i,u,o,a,f,p,k,g,E,N,y,v]}class rs extends ce{constructor(e){super(),le(this,e,ss,ns,oe,{showSettings:0})}}function kt(t,e,n){const r=t.slice();return r[1]=e[n],r}function os(t){var u;let e,n=((u=t[1].time)==null?void 0:u.toLocaleTimeString())+"",r,o,l=t[1].text+"",c,i;return{c(){e=m("li"),r=G(n),o=G(` - `),c=G(l),i=M()},m(a,d){L(a,e,d),h(e,r),h(e,o),h(e,c),h(e,i)},p(a,d){var f;d&1&&n!==(n=((f=a[1].time)==null?void 0:f.toLocaleTimeString())+"")&&ae(r,n),d&1&&l!==(l=a[1].text+"")&&ae(c,l)},d(a){a&&S(e)}}}function ls(t){var u;let e,n=((u=t[1].time)==null?void 0:u.toLocaleTimeString())+"",r,o,l=t[1].text+"",c,i;return{c(){e=m("li"),r=G(n),o=G(` diff --git a/app/dist/index.html b/app/dist/index.html index 42bd0c6..f273030 100644 --- a/app/dist/index.html +++ b/app/dist/index.html @@ -4,7 +4,7 @@ Bolt Launcher - + Date: Sat, 6 Jul 2024 04:10:28 +0100 Subject: [PATCH 6/7] window_launcher: launch exe on windows --- src/browser/window_launcher.cxx | 20 ++++ src/browser/window_launcher.hxx | 25 +++++ src/browser/window_launcher_posix.cxx | 37 +------ src/browser/window_launcher_win.cxx | 142 +++++++++++++++++++++++++- 4 files changed, 188 insertions(+), 36 deletions(-) diff --git a/src/browser/window_launcher.cxx b/src/browser/window_launcher.cxx index cae6e9a..6d30706 100644 --- a/src/browser/window_launcher.cxx +++ b/src/browser/window_launcher.cxx @@ -555,6 +555,26 @@ void Browser::Launcher::Refresh() const { this->browser->GetMainFrame()->LoadURL(this->BuildURL()); } +void Browser::Launcher::ParseQuery(std::string_view query, std::function callback) { + size_t pos = 0; + while (true) { + const size_t next_and = query.find('&', pos); + const size_t next_eq = query.find('=', pos); + if (next_eq == std::string_view::npos) break; + else if (next_and != std::string_view::npos && next_eq > next_and) { + pos = next_and + 1; + continue; + } + const bool is_last = next_and == std::string_view::npos; + const auto end = is_last ? query.end() : query.begin() + next_and; + const std::string_view key(query.begin() + pos, query.begin() + next_eq); + const std::string_view val(query.begin() + next_eq + 1, end); + callback(key, val); + if (is_last) break; + pos = next_and + 1; + } +} + CefRefPtr SaveFileFromPost(CefRefPtr request, const std::filesystem::path::value_type* path) { CefRefPtr post_data = request->GetPostData(); if (post_data->GetElementCount() != 1) { diff --git a/src/browser/window_launcher.hxx b/src/browser/window_launcher.hxx index f16a98a..65f3717 100644 --- a/src/browser/window_launcher.hxx +++ b/src/browser/window_launcher.hxx @@ -4,7 +4,10 @@ #include "../browser.hxx" #include "../file_manager.hxx" +#include "include/cef_parser.h" + #include +#include namespace Browser { struct Launcher: public Window { @@ -37,6 +40,9 @@ namespace Browser { /// inserting their contents into the query params CefString BuildURL() const; + /// Goes through all the key-value pairs in the given query string and calls the callback for each one. + void ParseQuery(std::string_view query, std::function callback); + /* Functions called by GetResourceRequestHandler. The result will be returned immediately and must not be null. The request and URL query string are provided for parsing. @@ -76,4 +82,23 @@ namespace Browser { }; } +#if defined(_WIN32) +#define PQTOSTRING ToWString +#else +#define PQTOSTRING ToString +#endif + +#define PQCHECK(KEY) \ +if (key == #KEY) { \ + has_##KEY = true; \ + KEY = CefURIDecode(std::string(val), true, (cef_uri_unescape_rule_t)(UU_SPACES | UU_PATH_SEPARATORS | UU_URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | UU_REPLACE_PLUS_WITH_SPACE)).PQTOSTRING(); \ + return; \ +} + +#define PQBOOL(KEY) \ +if (key == #KEY) { \ + KEY = (val.size() > 0 && val != "0"); \ + return; \ +} + #endif diff --git a/src/browser/window_launcher_posix.cxx b/src/browser/window_launcher_posix.cxx index 26e1348..4d425bb 100644 --- a/src/browser/window_launcher_posix.cxx +++ b/src/browser/window_launcher_posix.cxx @@ -1,14 +1,11 @@ #include "window_launcher.hxx" #include "resource_handler.hxx" -#include "include/cef_parser.h" - #include #include #include #include #include -#include #include // see #34 for why this function exists and why it can't be run between fork-exec or just run `env`. @@ -41,28 +38,6 @@ bool FindJava(const char* java_home, std::string& out) { return false; } -void ParseQuery(std::string_view query, std::function callback) { - size_t pos = 0; - while (true) { - const size_t next_and = query.find('&', pos); - const size_t next_eq = query.find('=', pos); - if (next_eq == std::string_view::npos) break; - else if (next_and != std::string_view::npos && next_eq > next_and) { - pos = next_and + 1; - continue; - } - const bool is_last = next_and == std::string_view::npos; - const auto end = is_last ? query.end() : query.begin() + next_and; - const std::string_view key(query.begin() + pos, query.begin() + next_eq); - const std::string_view val(query.begin() + next_eq + 1, end); - callback(key, val); - if (is_last) break; - pos = next_and + 1; - } -} -const cef_uri_unescape_rule_t pqrule = (cef_uri_unescape_rule_t)(UU_SPACES | UU_PATH_SEPARATORS | UU_URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | UU_REPLACE_PLUS_WITH_SPACE); -#define PQCHECK(KEY) if (key == #KEY) { has_##KEY = true; KEY = CefURIDecode(std::string(val), true, pqrule).ToString(); return; } - CefRefPtr Browser::Launcher::LaunchRs3Deb(CefRefPtr request, std::string_view query) { /* strings that I don't want to be searchable, which also need to be mutable for passing to env functions */ // PULSE_PROP_OVERRIDE= @@ -102,18 +77,14 @@ CefRefPtr Browser::Launcher::LaunchRs3Deb(CefRefPtrParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { PQCHECK(hash) PQCHECK(config_uri) PQCHECK(jx_session_id) PQCHECK(jx_character_id) PQCHECK(jx_display_name) - #if defined(BOLT_PLUGINS) - // no reason to URIDecode this - if (key == "plugin_loader" && val == "1") { - plugin_loader = true; - } + PQBOOL(plugin_loader) #endif }); @@ -344,7 +315,7 @@ CefRefPtr Browser::Launcher::LaunchRuneliteJar(CefRef bool has_jx_character_id = false; std::string jx_display_name; bool has_jx_display_name = false; - ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { + this->ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { PQCHECK(rl_path) PQCHECK(id) PQCHECK(jx_session_id) @@ -501,7 +472,7 @@ CefRefPtr Browser::Launcher::LaunchHdosJar(CefRefPtr< bool has_jx_character_id = false; std::string jx_display_name; bool has_jx_display_name = false; - ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { + this->ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { PQCHECK(version) PQCHECK(jx_session_id) PQCHECK(jx_character_id) diff --git a/src/browser/window_launcher_win.cxx b/src/browser/window_launcher_win.cxx index 66bd903..47bdffe 100644 --- a/src/browser/window_launcher_win.cxx +++ b/src/browser/window_launcher_win.cxx @@ -1,6 +1,7 @@ #include "window_launcher.hxx" #include "resource_handler.hxx" +#include #include CefRefPtr Browser::Launcher::LaunchRs3Deb(CefRefPtr request, std::string_view query) { @@ -9,9 +10,144 @@ CefRefPtr Browser::Launcher::LaunchRs3Deb(CefRefPtr Browser::Launcher::LaunchRs3Exe(CefRefPtr request, std::string_view query) { - // TODO - const char* data = ".exe is not supported on this platform\n"; - return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); + const CefRefPtr post_data = request->GetPostData(); + + // parse query + std::wstring hash; + bool has_hash = false; + std::wstring config_uri; + bool has_config_uri = false; + std::wstring jx_session_id; + bool has_jx_session_id = false; + std::wstring jx_character_id; + bool has_jx_character_id = false; + std::wstring jx_display_name; + bool has_jx_display_name = false; +#if defined(BOLT_PLUGINS) + bool plugin_loader = false; +#endif + this->ParseQuery(query, [&](const std::string_view& key, const std::string_view& val) { + PQCHECK(hash) + PQCHECK(config_uri) + PQCHECK(jx_session_id) + PQCHECK(jx_character_id) + PQCHECK(jx_display_name) +#if defined(BOLT_PLUGINS) + PQBOOL(plugin_loader) +#endif + }); + + // if there was a "hash" in the query string, we need to save the new game exe and the new hash + if (has_hash) { + if (post_data == nullptr || post_data->GetElementCount() != 1) { + // hash param must be accompanied by POST data containing the file it's a hash of, + // so hash but no POST is a bad request + const char* data = "Bad Request"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 400, "text/plain"); + } + std::ofstream file(this->rs3_exe_path, std::ios::out | std::ios::binary); + if (file.fail()) { + const char* data = "Failed to save executable; if the game is already running, close it and try again\n"; + return new Browser::ResourceHandler(reinterpret_cast(data), strlen(data), 500, "text/plain"); + } + CefPostData::ElementVector vec; + post_data->GetElements(vec); + size_t exe_size = vec[0]->GetBytesCount(); + unsigned char* exe = new unsigned char[exe_size]; + vec[0]->GetBytes(exe_size, exe); + file.write((const char*)exe, exe_size); + file.close(); + delete[] exe; + } + + // create the command line for the new process + std::wstring command_line = std::wstring(L"\"") + this->rs3_exe_path.wstring() + L"\""; + if (has_config_uri) { + command_line += L" --configURI \""; + command_line += config_uri; + command_line += L"\""; + } + + // create the environment block for the new process + std::wstring session_key = L"JX_SESSION_ID="; + std::wstring character_key = L"JX_CHARACTER_ID="; + std::wstring name_key = L"JX_DISPLAY_NAME="; + LPWCH env = GetEnvironmentStringsW(); + size_t env_size = 0; + while (env[env_size] != '\0') { + while (env[env_size] != '\0') env_size += 1; + env_size += 1; + } + const size_t current_process_env_size = env_size; + if (has_jx_session_id) env_size += session_key.size() + jx_session_id.size() + 1; + if (has_jx_character_id) env_size += character_key.size() + jx_character_id.size() + 1; + if (has_jx_display_name) env_size += name_key.size() + jx_display_name.size() + 1; + wchar_t* new_env = new wchar_t[env_size + 1]; + memcpy(&new_env[0], env, current_process_env_size * sizeof(*env)); + FreeEnvironmentStringsW(env); + wchar_t* cursor = &new_env[current_process_env_size]; + if (has_jx_session_id) { + memcpy(cursor, &session_key[0], session_key.size() * sizeof(session_key[0])); + cursor += session_key.size(); + memcpy(cursor, &jx_session_id[0], jx_session_id.size() * sizeof(jx_session_id[0])); + cursor += jx_session_id.size(); + *cursor = '\0'; + cursor += 1; + } + if (has_jx_character_id) { + memcpy(cursor, &character_key[0], character_key.size() * sizeof(character_key[0])); + cursor += character_key.size(); + memcpy(cursor, &jx_character_id[0], jx_character_id.size() * sizeof(jx_character_id[0])); + cursor += jx_character_id.size(); + *cursor = '\0'; + cursor += 1; + } + if (has_jx_display_name) { + memcpy(cursor, &name_key[0], name_key.size() * sizeof(name_key[0])); + cursor += name_key.size(); + memcpy(cursor, &jx_display_name[0], jx_display_name.size() * sizeof(jx_display_name[0])); + cursor += jx_display_name.size(); + *cursor = '\0'; + cursor += 1; + } + *cursor = '\0'; + + PROCESS_INFORMATION pi; + STARTUPINFOW si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + if (!CreateProcessW( + NULL, + &command_line[0], + NULL, + NULL, + false, + CREATE_UNICODE_ENVIRONMENT, // CREATE_SUSPENDED goes here + new_env, + NULL, + &si, + &pi + )) { + delete[] new_env; + const DWORD error = GetLastError(); + const std::string error_message = "CreateProcess failed with error " + std::to_string(error) + "\n"; + return new ResourceHandler(error_message, 500, "text/plain"); + } + delete[] new_env; + + if (has_hash) { + std::wofstream file(this->rs3_exe_hash_path, std::ios::out | std::ios::binary); + if (file.fail()) { + const char* data = "OK, but unable to save hash file\n"; + return new Browser::ResourceHandler(reinterpret_cast(data), strlen(data), 200, "text/plain"); + } + file << hash; + file.close(); + } + + const char* data = "OK\n"; + return new ResourceHandler(reinterpret_cast(data), strlen(data), 200, "text/plain"); } CefRefPtr Browser::Launcher::LaunchRs3App(CefRefPtr request, std::string_view query) { From b0c49a8f370154fd2166ac40bf44e86c053b4a9e Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 7 Jul 2024 23:04:43 +0100 Subject: [PATCH 7/7] resource_handler: int64 -> int64_t --- src/browser/resource_handler.cxx | 16 ++++++++-------- src/browser/resource_handler.hxx | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/browser/resource_handler.cxx b/src/browser/resource_handler.cxx index 00b6856..cfecc09 100644 --- a/src/browser/resource_handler.cxx +++ b/src/browser/resource_handler.cxx @@ -4,15 +4,15 @@ // returns content-length or -1 if it's malformed or nonexistent // CEF interprets -1 as "unknown length" -static int64 GetContentLength(CefRefPtr response) { +static int64_t GetContentLength(CefRefPtr response) { std::string content_length = response->GetHeaderByName("Content-Length").ToString(); if (content_length.size() == 0) { return -1; } - int64 length = 0; + int64_t length = 0; for (auto it = content_length.begin(); it != content_length.end(); it++) { if (*it < '0' || *it > '9') return -1; - length = (length * 10) + ((int64)(*it) - '0'); + length = (length * 10) + ((int64_t)(*it) - '0'); } return length; } @@ -107,7 +107,7 @@ bool Browser::DefaultURLHandler::Open(CefRefPtr request, bool& handl return true; } -void Browser::DefaultURLHandler::GetResponseHeaders(CefRefPtr response, int64& response_length, CefString& redirectUrl) { +void Browser::DefaultURLHandler::GetResponseHeaders(CefRefPtr response, int64_t& response_length, CefString& redirectUrl) { CefRefPtr url_response = this->url_request->GetResponse(); CefResponse::HeaderMap headers; url_response->GetHeaderMap(headers); @@ -140,7 +140,7 @@ bool Browser::DefaultURLHandler::Read(void* data_out, int bytes_to_read, int& by return true; } -bool Browser::DefaultURLHandler::Skip(int64 bytes_to_skip, int64& bytes_skipped, CefRefPtr callback) { +bool Browser::DefaultURLHandler::Skip(int64_t bytes_to_skip, int64_t& bytes_skipped, CefRefPtr callback) { if (this->cursor + bytes_to_skip <= this->data.size()) { // skip in bounds bytes_skipped = bytes_to_skip; @@ -164,13 +164,13 @@ void Browser::DefaultURLHandler::OnRequestComplete(CefRefPtr requ } } -void Browser::DefaultURLHandler::OnUploadProgress(CefRefPtr request, int64 current, int64 total) {} -void Browser::DefaultURLHandler::OnDownloadProgress(CefRefPtr request, int64 current, int64 total) {} +void Browser::DefaultURLHandler::OnUploadProgress(CefRefPtr request, int64_t current, int64_t total) {} +void Browser::DefaultURLHandler::OnDownloadProgress(CefRefPtr request, int64_t current, int64_t total) {} void Browser::DefaultURLHandler::OnDownloadData(CefRefPtr request, const void* data, size_t data_length) { CefRefPtr response = request->GetResponse(); if (!this->headers_checked && response) { - const int64 content_length = GetContentLength(response); + const int64_t content_length = GetContentLength(response); if (content_length != -1) this->data.reserve(content_length); this->headers_checked = true; } diff --git a/src/browser/resource_handler.hxx b/src/browser/resource_handler.hxx index 6ddafbd..89c6508 100644 --- a/src/browser/resource_handler.hxx +++ b/src/browser/resource_handler.hxx @@ -67,14 +67,14 @@ namespace Browser { CefRefPtr GetResourceHandler(CefRefPtr browser, CefRefPtr frame, CefRefPtr request) override; bool Open(CefRefPtr request, bool& handle_request, CefRefPtr callback) override; - void GetResponseHeaders(CefRefPtr response, int64& response_length, CefString& redirectUrl) override; + void GetResponseHeaders(CefRefPtr response, int64_t& response_length, CefString& redirectUrl) override; bool Read(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr callback) override; - bool Skip(int64 bytes_to_skip, int64& bytes_skipped, CefRefPtr callback) override; + bool Skip(int64_t bytes_to_skip, int64_t& bytes_skipped, CefRefPtr callback) override; void Cancel() override; void OnRequestComplete(CefRefPtr request) override; - void OnUploadProgress(CefRefPtr request, int64 current, int64 total) override; - void OnDownloadProgress(CefRefPtr request, int64 current, int64 total) override; + void OnUploadProgress(CefRefPtr request, int64_t current, int64_t total) override; + void OnDownloadProgress(CefRefPtr request, int64_t current, int64_t total) override; void OnDownloadData(CefRefPtr request, const void* data, size_t data_length) override; bool GetAuthCredentials(bool isProxy, const CefString& host, int port, const CefString& realm, const CefString& scheme, CefRefPtr callback) override;