Skip to content

Commit

Permalink
feat: skeleton download and install game
Browse files Browse the repository at this point in the history
  • Loading branch information
3Shain committed Mar 8, 2023
1 parent ddc6ce6 commit 7b4fbdf
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 117 deletions.
2 changes: 1 addition & 1 deletion src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export async function createApp() {
const aria2_session = await resolve("./aria2.session");
await appendFile(aria2_session, "");
const pid = (await exec("echo", ["$PPID"])).stdOut.split("\n")[0];
const apid = await spawn("./sidecar/aria2/aria2c", [
const { pid: apid } = await spawn("./sidecar/aria2/aria2c", [
// "-q",
"-d",
"/",
Expand Down
2 changes: 1 addition & 1 deletion src/aria2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export async function createAria2({
if (e && e["code"] == 1) {
await rpc.addUri(options.uri, {
gid,
"max-connection-per-server": 10,
"max-connection-per-server": 16,
out: options.absDst,
continue: false,
"allow-overwrite": true, // in case control file broken
Expand Down
154 changes: 125 additions & 29 deletions src/launcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,35 @@ import {
log,
openDir,
waitImageReady,
alert,
readBinary,
readAllLines,
stats,
fatal,
setKey,
removeFile,
wait,
humanFileSize,
writeFile,
} from "./utils";
import {
Box,
Button,
ButtonGroup,
Center,
Flex,
HStack,
IconButton,
Progress,
ProgressIndicator,
Spacer,
VStack,
} from "@hope-ui/solid";
import { createIcon } from "@hope-ui/solid";
import { batch, createSignal, onMount, Show } from "solid-js";
import { join } from "path-browserify";
import { batch, createSignal, Show } from "solid-js";
import { basename, join } from "path-browserify";
import { gt, lt } from "semver";
import {
patchProgram,
patchRevertProgram,
patternSearch,
putLocal,
} from "./patch";
import { md5 } from "./utils/unix";
import { doStreamUnzip, md5, mkdirp } from "./utils/unix";
import { CommonUpdateProgram } from "./common-update-ui";
import { Locale } from "./locale";

Expand Down Expand Up @@ -158,8 +154,6 @@ export async function createLauncher({
})();
taskQueue.next(); // ignored anyway

onMount(() => {});

async function onButtonClick() {
if (programBusy()) return; // ignore
if (_gameInstalled()) {
Expand All @@ -180,7 +174,24 @@ export async function createLauncher({
try {
await stats(join(selection, "pkg_version"));
} catch {
await locale.alert("NOT_SUPPORTED_YET", "DOWNLOAD_FUNCTION_TBD");
taskQueue.next(async function* () {
yield* downloadAndInstallGameProgram({
aria2,
gameDir: selection,
gameFileZip: c.data.game.latest.path,
gameAudioZip: c.data.game.latest.voice_packs.find(
(x) => x.language == "zh-cn"
)!.path,
gameVersion: GAME_LATEST_VERSION,
});
// setGameInstalled
batch(() => {
setGameInstalled(true);
setGameInstallDir(selection);
setGameCurrentVersion(GAME_LATEST_VERSION);
});
await setKey("game_install_dir", selection);
});
return;
}
const gameVersion = await getGameVersion(
Expand All @@ -197,23 +208,20 @@ export async function createLauncher({
await locale.alert("NOT_SUPPORTED_YET", "UPGRADE_FUNCTION_TBD");
return;
}
try {
await stats(join(selection, "pkg_version"));
taskQueue.next(async function* () {
yield* checkIntegrityProgram({
aria2,
gameDir: selection,
remoteDir: c.data.game.latest.decompressed_path,
});
// setGameInstalled
batch(() => {
setGameInstalled(true);
setGameInstallDir(selection);
setGameCurrentVersion(gameVersion);
});
await setKey("game_install_dir", selection);
taskQueue.next(async function* () {
yield* checkIntegrityProgram({
aria2,
gameDir: selection,
remoteDir: c.data.game.latest.decompressed_path,
});
// setGameInstalled
batch(() => {
setGameInstalled(true);
setGameInstallDir(selection);
setGameCurrentVersion(gameVersion);
});
} catch {}
await setKey("game_install_dir", selection);
});
}
}

Expand Down Expand Up @@ -424,7 +432,95 @@ async function* checkIntegrityProgram({
}
}

async function* downloadAndInstallGameProgram() {}
async function* downloadAndInstallGameProgram({
aria2,
gameFileZip,
gameAudioZip,
gameDir,
gameVersion,
}: {
gameFileZip: string;
gameDir: string;
gameAudioZip: string;
gameVersion: string;
aria2: Aria2;
}): CommonUpdateProgram {
// FIXME: remove hardcoded data
const gameChannel = 1;
const gameSubchannel = 1;
const gameCps = "pcadbdpz";

const downloadTmp = join(gameDir, ".ariatmp");
const gameFileTmp = join(downloadTmp, "game.zip");
const audioFileTmp = join(downloadTmp, "audio.zip");
await mkdirp(downloadTmp);
yield ["setUndeterminedProgress"];
yield ["setStateText", "ALLOCATING_FILE"];
let gameFileStart = false;
for await (const progress of aria2.doStreamingDownload({
uri: gameFileZip,
absDst: gameFileTmp,
})) {
if (!gameFileStart && progress.downloadSpeed == BigInt(0)) {
continue;
}
gameFileStart = true;
yield [
"setStateText",
"DOWNLOADING_FILE_PROGRESS",
basename(gameFileZip),
humanFileSize(Number(progress.downloadSpeed)),
humanFileSize(Number(progress.completedLength)),
humanFileSize(Number(progress.totalLength)),
];
yield [
"setProgress",
Number((progress.completedLength * BigInt(100)) / progress.totalLength),
];
}
yield ["setStateText", "DECOMPRESS_FILE_PROGRESS"];
for await (const [dec, total] of doStreamUnzip(gameFileTmp, gameDir)) {
yield ["setProgress", (dec / total) * 100];
}
await removeFile(gameFileTmp);
yield ["setUndeterminedProgress"];
yield ["setStateText", "ALLOCATING_FILE"];
gameFileStart = false;
for await (const progress of aria2.doStreamingDownload({
uri: gameAudioZip,
absDst: audioFileTmp,
})) {
if (!gameFileStart && progress.downloadSpeed == BigInt(0)) {
continue;
}
gameFileStart = true;
yield [
"setStateText",
"DOWNLOADING_FILE_PROGRESS",
basename(gameAudioZip),
humanFileSize(Number(progress.downloadSpeed)),
humanFileSize(Number(progress.completedLength)),
humanFileSize(Number(progress.totalLength)),
];
yield [
"setProgress",
Number((progress.completedLength * BigInt(100)) / progress.totalLength),
];
}
yield ["setStateText", "DECOMPRESS_FILE_PROGRESS"];
for await (const [dec, total] of doStreamUnzip(audioFileTmp, gameDir)) {
yield ["setProgress", (dec / total) * 100];
}
await removeFile(audioFileTmp);
await writeFile(
join(gameDir, "config.ini"),
`[General]
game_version=${gameVersion}
channel=${gameChannel}
sub_channel=${gameSubchannel}
cps=${gameCps}`
);
}

// async function*
async function checkGameFolder() {
Expand Down
5 changes: 5 additions & 0 deletions src/locale/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,10 @@ const en: typeof zh_CN = {

DOWNLOAD_FUNCTION_TBD: "",
UPGRADE_FUNCTION_TBD: "",

DECOMPRESS_FILE_PROGRESS:"Decompressing files",
ALLOCATING_FILE: "Allocation files on disk",
DOWNLOADING_FILE_PROGRESS:"Downloading file: {0} ({2}/{3}) {1}/s"

};
export default en;
6 changes: 5 additions & 1 deletion src/locale/zh_CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,9 @@ export default {

// FIXME
DOWNLOAD_FUNCTION_TBD: "启动器尚未实装下载游戏功能。",
UPGRADE_FUNCTION_TBD: "启动器尚未实装升级功能。"
UPGRADE_FUNCTION_TBD: "启动器尚未实装升级功能。",

DECOMPRESS_FILE_PROGRESS:"正在解压文件",
ALLOCATING_FILE: "正在分配磁盘空间",
DOWNLOADING_FILE_PROGRESS:"正在下载游戏文件:{0} ({2}/{3}) 速度:每秒{1}"
}
79 changes: 79 additions & 0 deletions src/utils/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
export async function waitImageReady(url: string) {
return new Promise((res, rej) => {
const image = new Image();
image.src = url;
image.onload = res;
image.onerror = rej;
});
}

export function timeout(ms: number): Promise<never> {
return new Promise((_, rej) => {
setTimeout(() => {
rej("TIMEOUT");
}, ms);
});
}

export function wait(ms: number): Promise<number> {
return new Promise((res, rej) => {
setTimeout(() => {
res(ms);
}, ms);
});
}

export async function sha256_16(str: string) {
const buf = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(str)
);
return Array.prototype.map
.call(new Uint8Array(buf), (x) => ("00" + x.toString(16)).slice(-2))
.slice(0, 8)
.join("");
}

export function formatString(str: string, intrp: string[]) {
return str.replace(/{(\d+)}/g, function (match, number) {
return typeof intrp[number] != "undefined" ? intrp[number] : match;
});
}

// https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
/* Format bytes as human-readable text.
*
* @param bytes Number of bytes.
* @param si True to use metric (SI) units, aka powers of 1000. False to use
* binary (IEC), aka powers of 1024.
* @param dp Number of decimal places to display.
*
* @return Formatted string.
*/
export function humanFileSize(
bytes: number,
si: boolean = false,
dp: number = 1
) {
const thresh = si ? 1000 : 1024;

if (Math.abs(bytes) < thresh) {
return bytes + " B";
}

const units = si
? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
: ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
let u = -1;
const r = 10 ** dp;

do {
bytes /= thresh;
++u;
} while (
Math.round(Math.abs(bytes) * r) / r >= thresh &&
u < units.length - 1
);

return bytes.toFixed(dp) + " " + units[u];
}
Loading

0 comments on commit 7b4fbdf

Please sign in to comment.