spplice-api 0.1.0 → 0.2.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/dist/docs/example.d.ts +2 -0
- package/dist/docs/example.d.ts.map +1 -0
- package/{docs/example.ts → dist/docs/example.js} +10 -13
- package/dist/docs/example.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/src/ConsoleTools.d.ts +40 -0
- package/dist/src/ConsoleTools.d.ts.map +1 -0
- package/dist/src/ConsoleTools.js +116 -0
- package/dist/src/ConsoleTools.js.map +1 -0
- package/dist/src/PackageTools.d.ts +52 -0
- package/dist/src/PackageTools.d.ts.map +1 -0
- package/dist/src/PackageTools.js +160 -0
- package/dist/src/PackageTools.js.map +1 -0
- package/dist/src/SteamTools.d.ts +47 -0
- package/dist/src/SteamTools.d.ts.map +1 -0
- package/dist/src/SteamTools.js +166 -0
- package/dist/src/SteamTools.js.map +1 -0
- package/dist/test/extract.test.d.ts +2 -0
- package/dist/test/extract.test.d.ts.map +1 -0
- package/dist/test/extract.test.js +11 -0
- package/dist/test/extract.test.js.map +1 -0
- package/package.json +18 -5
- package/index.ts +0 -10
- package/src/ConsoleTools.ts +0 -120
- package/src/PackageTools.ts +0 -170
- package/src/SteamTools.ts +0 -195
- package/test/extract.test.ts +0 -11
- package/tsconfig.json +0 -29
package/src/SteamTools.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import SteamworksSDK from "steamworks-ffi-node";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import { execSync, execFile } from "node:child_process";
|
|
6
|
-
import psList from "ps-list";
|
|
7
|
-
|
|
8
|
-
type UserDetails = {
|
|
9
|
-
steamid: BigInt,
|
|
10
|
-
name: string
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
class SteamClient {
|
|
14
|
-
|
|
15
|
-
steamworksClient: SteamworksSDK;
|
|
16
|
-
appID: number;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Initialize Steamworks API.
|
|
20
|
-
* @param appID Game's Steam AppID (default 620 = Portal 2)
|
|
21
|
-
* Note: This makes Steam think that the specified game (Portal 2) is running.
|
|
22
|
-
*/
|
|
23
|
-
constructor (appID: number = 620) {
|
|
24
|
-
this.appID = appID;
|
|
25
|
-
// HACK: Temporarily use AppID of Spacewar - Steam otherwise won't let us launch Portal 2
|
|
26
|
-
// Fixed by implementing manual startup of Portal 2
|
|
27
|
-
appID = 480;
|
|
28
|
-
// The Steamworks SDK requires steam_appid.txt in the working directory when
|
|
29
|
-
// the app is not launched through Steam, regardless of the envvar.
|
|
30
|
-
fs.writeFileSync(path.join(process.cwd(), "steam_appid.txt"), appID.toString());
|
|
31
|
-
this.steamworksClient = SteamworksSDK.getInstance();
|
|
32
|
-
const success = this.steamworksClient.init({ appId: appID });
|
|
33
|
-
if (!success) throw "Failed to initialize Steam API";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @returns {string} Absolute path to game directory.
|
|
38
|
-
*/
|
|
39
|
-
getGameDir (): string {
|
|
40
|
-
const pathString = this.steamworksClient.apps.getAppInstallDir(this.appID) as string;
|
|
41
|
-
return path.resolve(pathString);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @returns {UserDetails} Current user's SteamID64 and username.
|
|
46
|
-
*/
|
|
47
|
-
getUserDetails (): UserDetails {
|
|
48
|
-
const steamid = BigInt(this.steamworksClient.getStatus().steamId);
|
|
49
|
-
const name = this.steamworksClient.friends.getPersonaName();
|
|
50
|
-
return { steamid, name };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Checks whether the currently logged-in user has access to the game on Steam.
|
|
55
|
-
* @returns {boolean} true if current user has the game, false otherwise.
|
|
56
|
-
*/
|
|
57
|
-
doesUserOwnGame (): boolean {
|
|
58
|
-
return this.steamworksClient.apps.isSubscribed();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Searches for Steam binaries and returns all that were found. Output is
|
|
63
|
-
* sorted by how relevant the results are. The most useful result is first.
|
|
64
|
-
*
|
|
65
|
-
* @returns {string[]} Array of absolute paths to found Steam executables,
|
|
66
|
-
* sorted by descending relevance.
|
|
67
|
-
*/
|
|
68
|
-
static async findSteam (): Promise<string[]> {
|
|
69
|
-
|
|
70
|
-
const candidates: string[] = [];
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* First pass:
|
|
74
|
-
*
|
|
75
|
-
* Start by looking at the list of running system processes.
|
|
76
|
-
* The most relevant binary is likely one that is currently running.
|
|
77
|
-
*/
|
|
78
|
-
const processList = await psList();
|
|
79
|
-
// Sort the process list, putting the most recent processes first.
|
|
80
|
-
// The more relevant process is the one that was last opened.
|
|
81
|
-
processList.sort((a, b) => Number(b.startTime) - Number(a.startTime));
|
|
82
|
-
// Filter for processes with name "steam".
|
|
83
|
-
const steamProcesses = processList.filter(p =>
|
|
84
|
-
p.name === "steam" ||
|
|
85
|
-
p.name === "steam.exe"
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
// TODO: Read registry on Windows
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Second pass:
|
|
92
|
-
*
|
|
93
|
-
* Look for processes listening on port 27036. Steam uses this port to do
|
|
94
|
-
* game streaming, making it a fairly reliable way to fingerprint Steam.
|
|
95
|
-
*/
|
|
96
|
-
try {
|
|
97
|
-
let steamPortPIDs: number[] = [];
|
|
98
|
-
// There's no neat package for this, so we use OS-dependent shells.
|
|
99
|
-
if (process.platform === "win32") {
|
|
100
|
-
const stdout = execSync("netstat -ano | findstr :27036");
|
|
101
|
-
const matches = stdout.toString().match(/\s+(\d+)$/gm);
|
|
102
|
-
if (matches) steamPortPIDs = [...new Set(matches.map(m => Number(m.trim())))];
|
|
103
|
-
} else {
|
|
104
|
-
const stdout = execSync("lsof -i :27036 -t");
|
|
105
|
-
const result = stdout.toString();
|
|
106
|
-
steamPortPIDs = result.split("\n").map(Number);
|
|
107
|
-
}
|
|
108
|
-
// Filter for processes with the PIDs we found.
|
|
109
|
-
const steamPortProcesses = processList.filter(p => steamPortPIDs.includes(p.pid));
|
|
110
|
-
steamProcesses.push(...steamPortProcesses);
|
|
111
|
-
} catch (_) { }
|
|
112
|
-
|
|
113
|
-
// Push process paths to candidate list.
|
|
114
|
-
for (const process of steamProcesses) {
|
|
115
|
-
if (process.path) candidates.push(process.path);
|
|
116
|
-
else if (process.cmd) candidates.push(process.cmd.split(" ")[0]!);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Third pass:
|
|
121
|
-
*
|
|
122
|
-
* Blindly push common Steam installation paths as a last resort.
|
|
123
|
-
* These will get validated in the final filtering step.
|
|
124
|
-
*/
|
|
125
|
-
candidates.push(
|
|
126
|
-
`C:\\Program Files (x86)\\Steam\\steam.exe`,
|
|
127
|
-
`${os.homedir()}/.steam/steam/ubuntu12_32/steam`,
|
|
128
|
-
`${os.homedir()}/.local/share/Steam/ubuntu12_32/steam`,
|
|
129
|
-
`${os.homedir()}/.var/app/com.valvesoftware.Steam/.local/share/Steam/ubuntu12_32/steam`
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
// Filter and normalize candidate list.
|
|
133
|
-
for (let i = candidates.length - 1; i >= 0; i--) {
|
|
134
|
-
// Remove any empty entries
|
|
135
|
-
if (!candidates[i]?.trim()) {
|
|
136
|
-
candidates.splice(i, 1);
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
// Resolve candidate path
|
|
140
|
-
candidates[i] = path.resolve(candidates[i]!);
|
|
141
|
-
const candidate = candidates[i]!;
|
|
142
|
-
// Check that the path actually points to something
|
|
143
|
-
if (!fs.existsSync(candidate)) {
|
|
144
|
-
candidates.splice(i, 1);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
// Check that the destination is a file
|
|
148
|
-
const stat = fs.lstatSync(candidate);
|
|
149
|
-
if (!stat.isFile()) {
|
|
150
|
-
candidates.splice(i, 1);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Return list of candidates without duplicates
|
|
156
|
-
return [...new Set(candidates)];
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Runs the Steam executable with the given command-line arguments.
|
|
162
|
-
* Can be used to start Steam, or to start a Steam game by using "-applaunch".
|
|
163
|
-
*
|
|
164
|
-
* TODO: Replace with manual game launch, hooking virtual file system.
|
|
165
|
-
*
|
|
166
|
-
* @param args Command-line arguments for Steam.
|
|
167
|
-
*/
|
|
168
|
-
static async launchSteam (args: string[] = []) {
|
|
169
|
-
|
|
170
|
-
// Get list of canidate Steam executables
|
|
171
|
-
// TODO: Use Steamworks for this somehow?
|
|
172
|
-
const steamExes = await SteamClient.findSteam();
|
|
173
|
-
|
|
174
|
-
// Try each executable until one works, i.e. doesn't fail before `timeout`
|
|
175
|
-
const timeout = 5000;
|
|
176
|
-
for (const steam of steamExes) {
|
|
177
|
-
const error = await Promise.all([
|
|
178
|
-
new Promise(resolve => {
|
|
179
|
-
execFile(steam, args, (error) => {
|
|
180
|
-
resolve(error);
|
|
181
|
-
});
|
|
182
|
-
}),
|
|
183
|
-
new Promise(resolve => setTimeout(resolve, timeout, false))
|
|
184
|
-
]);
|
|
185
|
-
if (!error) break;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export {
|
|
193
|
-
type UserDetails,
|
|
194
|
-
SteamClient
|
|
195
|
-
};
|
package/test/extract.test.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "bun:test";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import * as Spplice from "../index";
|
|
4
|
-
|
|
5
|
-
test("repository package extraction", async () => {
|
|
6
|
-
const repo = await Spplice.Repository.fromURL("https://p2r3.github.io/spplice-repo/index.json");
|
|
7
|
-
const pkg = Array.from(repo.packages)[0]!;
|
|
8
|
-
await pkg.extract(".tmp");
|
|
9
|
-
expect(await Bun.file(".tmp/scripts/vscripts/mapspawn.nut").exists()).toBeTrue();
|
|
10
|
-
await fs.rm(".tmp", { recursive: true, force: true });
|
|
11
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
// Environment setup & latest features
|
|
4
|
-
"lib": ["ESNext"],
|
|
5
|
-
"target": "ESNext",
|
|
6
|
-
"module": "Preserve",
|
|
7
|
-
"moduleDetection": "force",
|
|
8
|
-
"jsx": "react-jsx",
|
|
9
|
-
"allowJs": true,
|
|
10
|
-
|
|
11
|
-
// Bundler mode
|
|
12
|
-
"moduleResolution": "bundler",
|
|
13
|
-
"allowImportingTsExtensions": true,
|
|
14
|
-
"verbatimModuleSyntax": true,
|
|
15
|
-
"noEmit": true,
|
|
16
|
-
|
|
17
|
-
// Best practices
|
|
18
|
-
"strict": true,
|
|
19
|
-
"skipLibCheck": true,
|
|
20
|
-
"noFallthroughCasesInSwitch": true,
|
|
21
|
-
"noUncheckedIndexedAccess": true,
|
|
22
|
-
"noImplicitOverride": true,
|
|
23
|
-
|
|
24
|
-
// Some stricter flags (disabled by default)
|
|
25
|
-
"noUnusedLocals": false,
|
|
26
|
-
"noUnusedParameters": false,
|
|
27
|
-
"noPropertyAccessFromIndexSignature": false
|
|
28
|
-
}
|
|
29
|
-
}
|