@openwf/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib.js +116 -0
- package/openwf.js +137 -0
- package/package.json +21 -0
package/lib.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
|
|
3
|
+
const getVersions = async () => {
|
|
4
|
+
if (!fs.existsSync("manifests/versions.html")) {
|
|
5
|
+
await fs.promises.mkdir("manifests", { recursive: true });
|
|
6
|
+
await fs.promises.writeFile("manifests/versions.html", await (await fetch("https://about.openwf.io/versions")).text(), "utf-8");
|
|
7
|
+
}
|
|
8
|
+
const tableRegex = /<tr id="(?<id>[0-9\.]+)"(?<attributes>[^>]*)>[^<]+<td><code>[^<]+(?:<!--[^<]+)?<\/code><\/td>[^<]+<td>(?:.|<) (?<near>[^<]+)<\/td>/gu;
|
|
9
|
+
const attributesRegex = /data-(?<key>[^=]+)="(?<value>[^"]+)"/gu;
|
|
10
|
+
const str = fs.readFileSync("manifests/versions.html", "utf-8");
|
|
11
|
+
const versions = [];
|
|
12
|
+
for (let version; version = tableRegex.exec(str)?.groups; ) {
|
|
13
|
+
const attributesString = version.attributes;
|
|
14
|
+
version.attributes = {};
|
|
15
|
+
for (let attribute; attribute = attributesRegex.exec(attributesString)?.groups; ) {
|
|
16
|
+
version.attributes[attribute.key.replaceAll("-", "_")] = attribute.value;
|
|
17
|
+
}
|
|
18
|
+
versions.push(version);
|
|
19
|
+
}
|
|
20
|
+
return versions;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getVersionType = (version) => {
|
|
24
|
+
if (version.attributes.magnet) {
|
|
25
|
+
if (version.attributes.base_manifest) {
|
|
26
|
+
return "patch";
|
|
27
|
+
} else {
|
|
28
|
+
if (version.attributes.langs) {
|
|
29
|
+
return "dirty";
|
|
30
|
+
} else {
|
|
31
|
+
return "cafee";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
return "steam";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getShortId = (version) => {
|
|
40
|
+
if (!version.attributes.magnet) {
|
|
41
|
+
return version.near + "-steam";
|
|
42
|
+
}
|
|
43
|
+
return version.id;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const downloadFile = async (url, out) => {
|
|
47
|
+
const { Readable } = require("node:stream");
|
|
48
|
+
const { body } = await fetch(url);
|
|
49
|
+
await fs.promises.writeFile(out, Readable.fromWeb(body));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const downloadFileFromMega = async (url, out) => {
|
|
53
|
+
const { File } = await import("megajs");
|
|
54
|
+
const file = File.fromURL(url);
|
|
55
|
+
await file.loadAttributes();
|
|
56
|
+
//console.log(`Downloading ${file.name}...`);
|
|
57
|
+
const data = await file.downloadBuffer();
|
|
58
|
+
await fs.promises.writeFile(out, data);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// For single-file torrents, downloadDir is the folder that contains the file.
|
|
62
|
+
const verifyLocalTorrentDownload = (torrentFilePath, downloadDir) => {
|
|
63
|
+
return new Promise(resolve => {
|
|
64
|
+
const nt = require("nt");
|
|
65
|
+
nt.read(torrentFilePath, (_err, torrent) => {
|
|
66
|
+
const hasher = torrent.hashCheck(downloadDir);
|
|
67
|
+
let p;
|
|
68
|
+
hasher.on("match", (i, hash, percent) => {
|
|
69
|
+
p = percent;
|
|
70
|
+
});
|
|
71
|
+
hasher.on("end", () => {
|
|
72
|
+
resolve(p == 100);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const downloadUpdatePatch = async (version, btConsent) => {
|
|
79
|
+
const params = new URLSearchParams(new URL(version.attributes.magnet).search);
|
|
80
|
+
const torrentFilePath = `depot/${params.get("dn")}.torrent`;
|
|
81
|
+
if (!fs.existsSync(torrentFilePath)) {
|
|
82
|
+
const torrentFileUrl = `https://about.openwf.io/supplementals/torrents/patches/${params.get("dn")}.torrent`;
|
|
83
|
+
await fs.promises.mkdir("depot", { recursive: true });
|
|
84
|
+
await downloadFile(torrentFileUrl, torrentFilePath);
|
|
85
|
+
}
|
|
86
|
+
let needToDownload = !fs.existsSync(`depot/${params.get("dn")}`);
|
|
87
|
+
while (true) {
|
|
88
|
+
if (needToDownload) {
|
|
89
|
+
if (btConsent) {
|
|
90
|
+
const WebTorrent = (await import("webtorrent")).default;
|
|
91
|
+
await new Promise(resolve => {
|
|
92
|
+
const client = new WebTorrent();
|
|
93
|
+
client.add(torrentFilePath, { path: "depot" }, torrent => {
|
|
94
|
+
torrent.on("done", () => {
|
|
95
|
+
client.destroy();
|
|
96
|
+
resolve(`depot/${params.get("dn")}`);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
//await downloadFile(params.get("ws"), `depot/${params.get("dn")}`);
|
|
102
|
+
await downloadFileFromMega(version.attributes.mega, `depot/${params.get("dn")}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (await verifyLocalTorrentDownload(torrentFilePath, "depot")) {
|
|
106
|
+
return `depot/${params.get("dn")}`;
|
|
107
|
+
}
|
|
108
|
+
if (needToDownload) {
|
|
109
|
+
// This was a fresh download, no point in retrying.
|
|
110
|
+
throw new Error(`Failed to download ${magnetUri}`);
|
|
111
|
+
}
|
|
112
|
+
needToDownload = true;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
module.exports = { getVersions, getVersionType, getShortId, downloadFile, downloadFileFromMega, verifyLocalTorrentDownload, downloadUpdatePatch };
|
package/openwf.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const tool = process.argv[2];
|
|
4
|
+
switch (tool) {
|
|
5
|
+
default: {
|
|
6
|
+
console.error("Syntax: openwf <download|install> <version>");
|
|
7
|
+
process.exit(1);
|
|
8
|
+
} break;
|
|
9
|
+
|
|
10
|
+
case "download": case "install": {
|
|
11
|
+
(async () => {
|
|
12
|
+
let target = process.argv[3];
|
|
13
|
+
if (!target) {
|
|
14
|
+
console.log(`Syntax: openwf ${tool} <version>`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const fs = require("node:fs");
|
|
19
|
+
const { getVersions, getVersionType, getShortId, downloadFile, downloadUpdatePatch } = require("./lib.js");
|
|
20
|
+
|
|
21
|
+
let isFresh = !fs.existsSync("manifests/versions.html");
|
|
22
|
+
if (isFresh) {
|
|
23
|
+
console.log("Downloading versions list...");
|
|
24
|
+
}
|
|
25
|
+
let versions = await getVersions();
|
|
26
|
+
let version;
|
|
27
|
+
while (true) {
|
|
28
|
+
version = versions.find(version => getShortId(version) == target);
|
|
29
|
+
if (!version) {
|
|
30
|
+
version = versions.find(version => version.near == target);
|
|
31
|
+
if (version) {
|
|
32
|
+
target = getShortId(version);
|
|
33
|
+
console.log(`Assuming you meant ${target}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (version || isFresh) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
console.log("Refreshing versions list...");
|
|
40
|
+
await fs.promises.unlink("manifests/versions.html");
|
|
41
|
+
versions = await getVersions();
|
|
42
|
+
isFresh = true;
|
|
43
|
+
}
|
|
44
|
+
if (!version) {
|
|
45
|
+
console.log(`Unknown version: ${target}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
const type = getVersionType(version);
|
|
49
|
+
|
|
50
|
+
let btConsent = false;
|
|
51
|
+
if (type != "steam") {
|
|
52
|
+
const yesno = require("yesno");
|
|
53
|
+
btConsent = await yesno({
|
|
54
|
+
question: "Would it be okay to use peer-to-peer networking (BitTorrent) for faster downloads? [Y/n]",
|
|
55
|
+
defaultValue: true
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (type == "steam" || type == "patch") {
|
|
60
|
+
const ContentManifest = require("lean-and-mean-steam-user/components/content_manifest");
|
|
61
|
+
const { fetchManifest, fetchDepotKey, downloadAndInstall, DEFAULT_HOSTS } = require("steam-manifest-tools");
|
|
62
|
+
|
|
63
|
+
console.log(`Fetching manifest...`);
|
|
64
|
+
const manifestId = type == "patch" ? version.attributes.base_manifest : version.id;
|
|
65
|
+
const manifest = ContentManifest.parse(await fetchManifest("230411", manifestId));
|
|
66
|
+
|
|
67
|
+
console.log(`Fetching depot key...`);
|
|
68
|
+
let depotKey = await fetchDepotKey(manifest.depot_id);
|
|
69
|
+
depotKey = Buffer.from(depotKey, "hex");
|
|
70
|
+
|
|
71
|
+
let remaining_chunks;
|
|
72
|
+
await fs.promises.mkdir("install", { recursive: true });
|
|
73
|
+
await downloadAndInstall(
|
|
74
|
+
manifest,
|
|
75
|
+
depotKey,
|
|
76
|
+
(num_chunks) => {
|
|
77
|
+
remaining_chunks = num_chunks;
|
|
78
|
+
if (remaining_chunks == 0) {
|
|
79
|
+
console.log("Unpacking...");
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
(path, host) => { console.log(`${path}: Downloading from ${host}`); },
|
|
83
|
+
(path, status, host) => {
|
|
84
|
+
console.log(`${path}: Got ${status}`);
|
|
85
|
+
if (status == 200 && --remaining_chunks == 0) {
|
|
86
|
+
console.log("Unpacking...");
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
(path, err) => { console.log(`${path}: `, err); },
|
|
90
|
+
DEFAULT_HOSTS,
|
|
91
|
+
`install/${target}`
|
|
92
|
+
);
|
|
93
|
+
} else {
|
|
94
|
+
console.log(`Version type (${type}) not yet supported.`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
if (type == "patch") {
|
|
98
|
+
console.log("Fetching update patch...");
|
|
99
|
+
const patchArchivePath = await downloadUpdatePatch(version, btConsent);
|
|
100
|
+
|
|
101
|
+
console.log("Applying update patch...");
|
|
102
|
+
const sz = require("7zip-min");
|
|
103
|
+
await sz.unpackSome(patchArchivePath, [target], "install");
|
|
104
|
+
}
|
|
105
|
+
if (tool == "install") {
|
|
106
|
+
console.log("Downloading Bootstrapper manifest...");
|
|
107
|
+
const meta = await (await fetch("https://openwf.io/supplementals/client%20drop-in/meta")).json();
|
|
108
|
+
|
|
109
|
+
console.log(meta.hotfix ? `Downloading Bootstrapper v${meta.version} ${meta.hotfix}...` : `Downloading Bootstrapper v${meta.version}...`);
|
|
110
|
+
const gameMajor = parseInt(version.near.split(".")[0]);
|
|
111
|
+
const dllName = gameMajor >= 29 ? "dwmapi.dll" : "wtsapi32.dll";
|
|
112
|
+
await downloadFile(`https://openwf.io/supplementals/client%20drop-in/${meta.version}/dwmapi.dll`, `install/${target}/${dllName}`);
|
|
113
|
+
if (meta.hotfix) {
|
|
114
|
+
await fs.promises.mkdir(`install/${target}/OpenWF`, { recursive: true });
|
|
115
|
+
await downloadFile(`https://openwf.io/supplementals/client%20drop-in/${meta.version}/${meta.hotfix}/Hotfix.owf`, `install/${target}/OpenWF/Hotfix.owf`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (type != "patch" && gameMajor >= 38 && process.platform == "win32") {
|
|
119
|
+
if (!fs.existsSync(`install/${target}/OpenWF/sideloadify-cli.cache`)) {
|
|
120
|
+
console.log("Downloading sideloadify-cli...");
|
|
121
|
+
await downloadFile("https://github.com/Sainan/Sideloadify/releases/download/1.1.0/sideloadify-cli.exe", `install/${target}/OpenWF/sideloadify-cli.cache`);
|
|
122
|
+
}
|
|
123
|
+
await fs.promises.mkdir(`install/${target}/OpenWF`, { recursive: true });
|
|
124
|
+
await fs.promises.rename(`install/${target}/OpenWF/sideloadify-cli.cache`, `install/${target}/OpenWF/sideloadify-cli.exe`);
|
|
125
|
+
await new Promise(resolve => {
|
|
126
|
+
const { spawn } = require("node:child_process");
|
|
127
|
+
const ps = spawn(`install/${target}/OpenWF/sideloadify-cli.exe`, [`install/${target}/Warframe.x64.exe`], { stdio: "inherit" });
|
|
128
|
+
ps.on("close", resolve);
|
|
129
|
+
});
|
|
130
|
+
await fs.promises.rename(`install/${target}/OpenWF/sideloadify-cli.exe`, `install/${target}/OpenWF/sideloadify-cli.cache`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
console.log("All done.");
|
|
134
|
+
process.exit(0);
|
|
135
|
+
})();
|
|
136
|
+
} break;
|
|
137
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openwf/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "lib.js",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Sainan/owf-npm.git"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"openwf": "openwf.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"7zip-min": "^3.0.1",
|
|
14
|
+
"lean-and-mean-steam-user": "^0.1.0",
|
|
15
|
+
"megajs": "^1.3.10",
|
|
16
|
+
"nt": "^0.7.1",
|
|
17
|
+
"steam-manifest-tools": "^0.1.11",
|
|
18
|
+
"webtorrent": "^2.3.0",
|
|
19
|
+
"yesno": "^0.4.0"
|
|
20
|
+
}
|
|
21
|
+
}
|