mirror of
https://github.com/Adamcake/Bolt.git
synced 2026-04-18 00:06:52 -04:00
app: add a function for direct6 downloads
This commit is contained in:
BIN
app/bun.lockb
BIN
app/bun.lockb
Binary file not shown.
File diff suppressed because one or more lines are too long
2
app/dist/index.html
vendored
2
app/dist/index.html
vendored
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Bolt Launcher</title>
|
||||
<script type="module" crossorigin src="/assets/index-DtKEzot0.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-B75594vm.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-B5crSLm9.css">
|
||||
</head>
|
||||
<body
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
{
|
||||
"name": "vite_app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode development",
|
||||
"watch": "vite build --watch --mode development",
|
||||
"build": "vite build",
|
||||
"minify": "vite build --minify",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"test": "vitest",
|
||||
"eslint": "eslint .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@tsconfig/svelte": "^5.0.2",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"prettier-plugin-tailwindcss": "^0.5.13",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.7",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.4.3",
|
||||
"typescript-eslint": "^7.4.0",
|
||||
"vite": "^5.2.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.2.0"
|
||||
}
|
||||
"name": "vite_app",
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@tsconfig/svelte": "^5.0.2",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"prettier-plugin-tailwindcss": "^0.5.13",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.7",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.4.3",
|
||||
"typescript-eslint": "^7.4.0",
|
||||
"vite": "^5.2.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.2.0"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode development",
|
||||
"watch": "vite build --watch --mode development",
|
||||
"build": "vite build",
|
||||
"minify": "vite build --minify",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"test": "vitest",
|
||||
"eslint": "eslint .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type Auth,
|
||||
type Character,
|
||||
type GameClient,
|
||||
type Direct6Token,
|
||||
unwrap
|
||||
} from '$lib/Util/interfaces';
|
||||
import {
|
||||
@@ -805,3 +806,130 @@ export function savePluginConfig(): void {
|
||||
};
|
||||
xml.send(JSON.stringify(get(pluginList)));
|
||||
}
|
||||
|
||||
// download and attempt to launch an official windows or mac client
|
||||
// (not the official linux client - that comes from a different url, see launchRS3Linux)
|
||||
export function launchOfficialClient(
|
||||
windows: boolean,
|
||||
osrs: boolean,
|
||||
jx_session_id: string,
|
||||
jx_character_id: string,
|
||||
jx_display_name: string
|
||||
) {
|
||||
saveConfig();
|
||||
const launchPath: string = `/launch-${osrs ? 'osrs' : 'rs3'}-${windows ? 'exe' : 'macapp'}?`;
|
||||
const metaPath: string = `${osrs ? 'osrs' : atob(boltSub.provider)}-${windows ? 'win' : 'mac'}`;
|
||||
const primaryUrl: string = `${atob(boltSub.direct6_url)}${metaPath}/${metaPath}.json`
|
||||
|
||||
const launch = (hash?: string, exe?: Blob) => {
|
||||
const xml = new XMLHttpRequest();
|
||||
const params: Record<string, string> = {};
|
||||
if (hash) params.hash = hash;
|
||||
if (jx_session_id) params.jx_session_id = jx_session_id;
|
||||
if (jx_character_id) params.jx_character_id = jx_character_id;
|
||||
if (jx_display_name) params.jx_display_name = jx_display_name;
|
||||
xml.open('POST', launchPath.concat(new URLSearchParams(params).toString()), true);
|
||||
xml.setRequestHeader('Content-Type', 'application/octet-stream');
|
||||
xml.onreadystatechange = () => {
|
||||
if (xml.readyState == 4) {
|
||||
msg(`Game launch status: '${xml.responseText.trim()}'`);
|
||||
if (xml.status == 200 && hash) {
|
||||
// TODO: change installed version to `hash`
|
||||
}
|
||||
}
|
||||
};
|
||||
if (exe !== null) {
|
||||
exe?.arrayBuffer().then((x) => xml.send(x));
|
||||
} else {
|
||||
xml.send();
|
||||
}
|
||||
};
|
||||
|
||||
// yes, there is way too much nesting happening here
|
||||
const xml = new XMLHttpRequest();
|
||||
xml.open('GET', primaryUrl, true);
|
||||
xml.onreadystatechange = () => {
|
||||
if (xml.readyState == 4) {
|
||||
if (xml.status == 200) {
|
||||
const metaToken: Direct6Token = JSON.parse(atob(xml.responseText.split('.')[1])).environments.production;
|
||||
// TODO: check here if metaToken.id matches the already-installed version, if it does, just launch straight away
|
||||
msg(`Downloading client version ${metaToken.version}...`);
|
||||
const catalogUrl: string = `${atob(boltSub.direct6_url)}${metaPath}/catalog/${metaToken.id}/catalog.json`;
|
||||
const xml2 = new XMLHttpRequest();
|
||||
xml2.open('GET', catalogUrl, true);
|
||||
xml2.onreadystatechange = () => {
|
||||
if (xml2.readyState == 4) {
|
||||
if (xml2.status == 200) {
|
||||
const catalog = JSON.parse(atob(xml2.responseText.split('.')[1]));
|
||||
const metafileUrl = catalog.metafile.replace("http:", "https:");
|
||||
const xml3 = new XMLHttpRequest();
|
||||
xml3.open('GET', metafileUrl, true);
|
||||
xml3.onreadystatechange = () => {
|
||||
if (xml3.readyState == 4) {
|
||||
if (xml3.status == 200) {
|
||||
const metafile = JSON.parse(atob(xml3.responseText.split('.')[1]));
|
||||
const chunk_promises: Promise<Blob>[] = metafile.pieces.digests.map((x: string) => {
|
||||
const hex_chunk: string = atob(x).split("").map(c => c.charCodeAt(0).toString(16).padStart(2, "0")).join("");
|
||||
const chunk_url: string = catalog.config.remote.baseUrl.replace("http:", "https:").concat(catalog.config.remote.pieceFormat.replace("{SubString:0,2,{TargetDigest}}", hex_chunk.substring(0, 2)).replace("{TargetDigest}", hex_chunk));
|
||||
return new Promise((resolve, reject) => {
|
||||
const xml = new XMLHttpRequest();
|
||||
xml.open('GET', chunk_url, true);
|
||||
xml.responseType = 'blob';
|
||||
xml.onreadystatechange = () => {
|
||||
if (xml.readyState == 4) {
|
||||
if (xml.status == 200) {
|
||||
const ds = new DecompressionStream("gzip");
|
||||
new Response(xml.response.slice(6).stream().pipeThrough(ds)).blob().then(resolve);
|
||||
} else {
|
||||
reject(`Error from ${chunk_url}: ${xml.status}: ${xml.responseText}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
xml.send();
|
||||
});
|
||||
});
|
||||
|
||||
// while all that stuff is downloading, let's read the file list and find the exe's location in the data blob
|
||||
var exeOffset: number = 0;
|
||||
var exeSize: number | null = null;
|
||||
for (let i = 0; i < metafile.files.length; i += 1) {
|
||||
const isTargetExe: boolean = windows ? metafile.files[i].name.endsWith(".exe") : metafile.files[i].name.includes(".app/Contents/MacOS/");
|
||||
if (isTargetExe) {
|
||||
if (exeSize !== null) {
|
||||
err(`Error parsing ${metafileUrl}: file list has multiple possibilities for main exe`, false);
|
||||
return;
|
||||
} else {
|
||||
exeSize = metafile.files[i].size;
|
||||
}
|
||||
} else if (exeSize === null) {
|
||||
exeOffset += metafile.files[i].size;
|
||||
}
|
||||
}
|
||||
if (exeSize === null) {
|
||||
err(`Error parsing ${metafileUrl}: file list has no possibilities for main exe`, false);
|
||||
return;
|
||||
}
|
||||
|
||||
Promise.all(chunk_promises).then(x => {
|
||||
const exeFile = new Blob(x).slice(exeOffset, exeOffset + <number>exeSize);
|
||||
launch(metafile.id, exeFile);
|
||||
});
|
||||
} else {
|
||||
err(`Error from ${metafileUrl}: ${xml3.status}: ${xml3.responseText}`, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
xml3.send();
|
||||
} else {
|
||||
err(`Error from ${catalogUrl}: ${xml2.status}: ${xml2.responseText}`, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
xml2.send();
|
||||
} else {
|
||||
err(`Error from ${primaryUrl}: ${xml.status}: ${xml.responseText}`, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
xml.send();
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ export interface Bolt {
|
||||
profile_api: string;
|
||||
shield_url: string;
|
||||
content_url: string;
|
||||
direct6_url: string;
|
||||
default_config_uri: string;
|
||||
games: Array<string>;
|
||||
}
|
||||
@@ -142,6 +143,14 @@ export interface SelectedPlay {
|
||||
client?: Client;
|
||||
}
|
||||
|
||||
// response token from the official "Direct6" URL
|
||||
export interface Direct6Token {
|
||||
id?: string;
|
||||
version?: string;
|
||||
promoteTime?: number;
|
||||
scanTime?: number;
|
||||
}
|
||||
|
||||
// connected game client for plugin management purposes
|
||||
export interface GameClient {
|
||||
uid: string;
|
||||
|
||||
@@ -125,6 +125,7 @@ bool Browser::App::Execute(const CefString&, CefRefPtr<CefV8Value>, const CefV8V
|
||||
retval->SetValue("shield_url", CefV8Value::CreateString("aHR0cHM6Ly9hdXRoLmphZ2V4LmNvbS9zaGllbGQvb2F1dGgvdG9rZW4"), V8_PROPERTY_ATTRIBUTE_READONLY);
|
||||
retval->SetValue("content_url", CefV8Value::CreateString("aHR0cHM6Ly9jb250ZW50LnJ1bmVzY2FwZS5jb20vZG93bmxvYWRzL3VidW50dS8"), V8_PROPERTY_ATTRIBUTE_READONLY);
|
||||
retval->SetValue("default_config_uri", CefV8Value::CreateString("aHR0cHM6Ly93d3cucnVuZXNjYXBlLmNvbS9rPTUvbD0wL2phdl9jb25maWcud3M"), V8_PROPERTY_ATTRIBUTE_READONLY);
|
||||
retval->SetValue("direct6_url", CefV8Value::CreateString("aHR0cHM6Ly9qYWdleC5ha2FtYWl6ZWQubmV0L2RpcmVjdDYv"), V8_PROPERTY_ATTRIBUTE_READONLY);
|
||||
|
||||
CefRefPtr<CefV8Value> games = CefV8Value::CreateArray(2);
|
||||
games->SetValue(0, CefV8Value::CreateString("UnVuZVNjYXBl"));
|
||||
|
||||
Reference in New Issue
Block a user