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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=example.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../../docs/example.ts"],"names":[],"mappings":""}
@@ -1,29 +1,25 @@
1
1
  import * as Spplice from "..";
2
2
  import path from "node:path";
3
-
4
3
  // Start Steam interface - Steam must be running, user must be logged in
5
4
  const steamClient = new Spplice.SteamClient();
6
5
  // Get Portal 2 game directory
7
6
  const gameDir = steamClient.getGameDir();
8
-
9
7
  // Load default package repository/repositories
10
8
  const repositories = await Promise.all([
11
- Spplice.Repository.fromURL("https://p2r3.github.io/spplice-repo/index.json")
9
+ Spplice.Repository.fromURL("https://p2r3.github.io/spplice-repo/index.json")
12
10
  ]);
13
11
  // Aggregate all packages into a single list
14
12
  const packages = repositories.map(r => Array.from(r.packages)).flat(1);
15
-
16
13
  // Pick a package to install - for this example, the first one on the list
17
- const packageToInstall = packages[0]!;
14
+ const packageToInstall = packages[0];
18
15
  // Extract the package files to portal2_tempcontent
19
16
  packageToInstall.extract(path.join(gameDir, "portal2_tempcontent"));
20
17
  // Tell Steam to start the game
21
18
  Spplice.SteamClient.launchSteam([
22
- "-applaunch", steamClient.appID.toString(),
23
- "-tempcontent", // Enable reading from portal2_tempcontent
24
- "-netconport 22333" // Enable console TCP socket
19
+ "-applaunch", steamClient.appID.toString(),
20
+ "-tempcontent", // Enable reading from portal2_tempcontent
21
+ "-netconport 22333" // Enable console TCP socket
25
22
  ]);
26
-
27
23
  // Connect to the game's developer console
28
24
  const gameConsole = new Spplice.Console(22333);
29
25
  await gameConsole.connect();
@@ -31,8 +27,9 @@ await gameConsole.connect();
31
27
  gameConsole.send("echo Hello from Spplice API!");
32
28
  // Monitor and print console output
33
29
  setInterval(() => {
34
- let line;
35
- while (line = gameConsole.readLine()) {
36
- console.log("[P2]", line);
37
- }
30
+ let line;
31
+ while (line = gameConsole.readLine()) {
32
+ console.log("[P2]", line);
33
+ }
38
34
  }, 200);
35
+ //# sourceMappingURL=example.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example.js","sourceRoot":"","sources":["../../docs/example.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,IAAI,CAAC;AAC9B,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,wEAAwE;AACxE,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;AAC9C,8BAA8B;AAC9B,MAAM,OAAO,GAAG,WAAW,CAAC,UAAU,EAAE,CAAC;AAEzC,+CAA+C;AAC/C,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;IACrC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,gDAAgD,CAAC;CAC7E,CAAC,CAAC;AACH,4CAA4C;AAC5C,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAEvE,0EAA0E;AAC1E,MAAM,gBAAgB,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;AACtC,mDAAmD;AACnD,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC;AACpE,+BAA+B;AAC/B,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC;IAC9B,YAAY,EAAE,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE;IAC1C,cAAc,EAAE,0CAA0C;IAC1D,mBAAmB,CAAC,4BAA4B;CACjD,CAAC,CAAC;AAEH,0CAA0C;AAC1C,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC/C,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;AAC5B,iCAAiC;AACjC,WAAW,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;AACjD,mCAAmC;AACnC,WAAW,CAAC,GAAG,EAAE;IACf,IAAI,IAAI,CAAC;IACT,OAAO,IAAI,GAAG,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC,EAAE,GAAG,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { SteamClient } from "./src/SteamTools.js";
2
+ export { SpplicePackage as Package, SppliceRepository as Repository } from "./src/PackageTools.js";
3
+ export { Console } from "./src/ConsoleTools.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,IAAI,OAAO,EACzB,iBAAiB,IAAI,UAAU,EAChC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,OAAO,EACR,MAAM,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { SteamClient } from "./src/SteamTools.js";
2
+ export { SpplicePackage as Package, SppliceRepository as Repository } from "./src/PackageTools.js";
3
+ export { Console } from "./src/ConsoleTools.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,IAAI,OAAO,EACzB,iBAAiB,IAAI,UAAU,EAChC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,OAAO,EACR,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import net from "net";
2
+ export declare class Console {
3
+ socket: net.Socket;
4
+ port: number;
5
+ error: Error | null;
6
+ connected: boolean;
7
+ private closing;
8
+ private writeBuffer;
9
+ private readBuffer;
10
+ /**
11
+ * Creates and configures TCP socket for Portal 2 netcon.
12
+ * @param port Network port number for connection.
13
+ */
14
+ constructor(port: number);
15
+ /**
16
+ * Connects to the Portal 2 console TCP socket.
17
+ */
18
+ connect(): Promise<void>;
19
+ /**
20
+ * Disconnects from the Portal 2 console socket.
21
+ */
22
+ disconnect(): void;
23
+ /**
24
+ * Sends a console command to the game.
25
+ * @param command Command to execute.
26
+ */
27
+ send(command: string): void;
28
+ /**
29
+ * Reads output from the game console.
30
+ * @param length Max amount of bytes to read. Default is -1 (read all).
31
+ */
32
+ read(length?: number): string;
33
+ /**
34
+ * Reads and returns one full line of console output. Does not read
35
+ * incomplete lines. Removes newline and carriage return characters.
36
+ * @returns One full line of console output, or `null` if no output.
37
+ */
38
+ readLine(): string | null;
39
+ }
40
+ //# sourceMappingURL=ConsoleTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConsoleTools.d.ts","sourceRoot":"","sources":["../../src/ConsoleTools.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,qBAAa,OAAO;IAGlB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC;IAEnB,IAAI,EAAE,MAAM,CAAC;IAEb,KAAK,EAAE,KAAK,GAAG,IAAI,CAAQ;IAE3B,SAAS,EAAE,OAAO,CAAS;IAI3B,OAAO,CAAC,OAAO,CAAkB;IAEjC,OAAO,CAAC,WAAW,CAAc;IAEjC,OAAO,CAAC,UAAU,CAAc;IAEhC;;;OAGG;gBACU,IAAI,EAAE,MAAM;IAsBzB;;OAEG;IACG,OAAO;IAsBb;;OAEG;IACH,UAAU;IAOV;;;OAGG;IACH,IAAI,CAAE,OAAO,EAAE,MAAM;IAMrB;;;OAGG;IACH,IAAI,CAAE,MAAM,GAAE,MAAW,GAAG,MAAM;IAYlC;;;;OAIG;IACH,QAAQ,IAAK,MAAM,GAAG,IAAI;CAM3B"}
@@ -0,0 +1,116 @@
1
+ import net from "net";
2
+ export class Console {
3
+ // TCP socket handle
4
+ socket;
5
+ // Network port number
6
+ port;
7
+ // Last connection error, or null if none
8
+ error = null;
9
+ // Connection state - true if connected, false otherwise
10
+ connected = false;
11
+ // Whether we expect the socket to close intentionally.
12
+ // Determines whether a repeat connection is attempted.
13
+ closing = false;
14
+ // Stores unflushed writes, re-sent if the socket reconnects.
15
+ writeBuffer = "";
16
+ // Stores output read from the socket, but not yet read by the client.
17
+ readBuffer = "";
18
+ /**
19
+ * Creates and configures TCP socket for Portal 2 netcon.
20
+ * @param port Network port number for connection.
21
+ */
22
+ constructor(port) {
23
+ this.socket = new net.Socket();
24
+ this.socket.setNoDelay(true);
25
+ this.socket.setKeepAlive(false);
26
+ this.port = port;
27
+ // Listen for incoming data and log to read buffer
28
+ this.socket.on("data", data => {
29
+ this.readBuffer += data;
30
+ });
31
+ // Clear pending write buffer when data gets flushed
32
+ this.socket.on("drain", () => {
33
+ this.writeBuffer = "";
34
+ });
35
+ // Reconnect if the socket ever closes unexpectedly
36
+ this.socket.on("close", () => {
37
+ this.connected = false;
38
+ if (!this.closing) {
39
+ setTimeout(() => this.connect(), 200);
40
+ }
41
+ });
42
+ }
43
+ /**
44
+ * Connects to the Portal 2 console TCP socket.
45
+ */
46
+ async connect() {
47
+ // Attempt connection to the socket
48
+ this.error = await new Promise(resolve => {
49
+ let onConnect, onError;
50
+ this.socket.once("error", onError = (err) => {
51
+ this.socket.off("connect", onConnect);
52
+ resolve(err);
53
+ });
54
+ this.socket.once("connect", onConnect = () => {
55
+ this.socket.off("error", onError);
56
+ resolve(null);
57
+ });
58
+ this.socket.connect(this.port, "127.0.0.1");
59
+ });
60
+ if (!this.error)
61
+ this.connected = true;
62
+ // Re-send anything that remains in the write buffer from the last session
63
+ if (this.writeBuffer) {
64
+ const success = this.socket.write(this.writeBuffer);
65
+ if (success)
66
+ this.writeBuffer = "";
67
+ }
68
+ }
69
+ /**
70
+ * Disconnects from the Portal 2 console socket.
71
+ */
72
+ disconnect() {
73
+ this.closing = true;
74
+ this.writeBuffer = "";
75
+ this.readBuffer = "";
76
+ this.socket.end();
77
+ }
78
+ /**
79
+ * Sends a console command to the game.
80
+ * @param command Command to execute.
81
+ */
82
+ send(command) {
83
+ const data = command + "\n";
84
+ const success = this.socket.write(data);
85
+ if (!success)
86
+ this.writeBuffer += data;
87
+ }
88
+ /**
89
+ * Reads output from the game console.
90
+ * @param length Max amount of bytes to read. Default is -1 (read all).
91
+ */
92
+ read(length = -1) {
93
+ let output;
94
+ if (length < 0) {
95
+ output = this.readBuffer;
96
+ this.readBuffer = "";
97
+ }
98
+ else {
99
+ output = this.readBuffer.slice(0, length);
100
+ this.readBuffer = this.readBuffer.slice(length);
101
+ }
102
+ return output;
103
+ }
104
+ /**
105
+ * Reads and returns one full line of console output. Does not read
106
+ * incomplete lines. Removes newline and carriage return characters.
107
+ * @returns One full line of console output, or `null` if no output.
108
+ */
109
+ readLine() {
110
+ const newlineIndex = this.readBuffer.indexOf("\n");
111
+ if (newlineIndex === -1)
112
+ return null;
113
+ return this.read(newlineIndex + 1).slice(0, -1).replaceAll("\r", "");
114
+ }
115
+ }
116
+ //# sourceMappingURL=ConsoleTools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConsoleTools.js","sourceRoot":"","sources":["../../src/ConsoleTools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { Readable } from "node:stream";
2
+ declare class SpplicePackage {
3
+ title: string;
4
+ author: string;
5
+ description: string;
6
+ version: string;
7
+ args: string[];
8
+ file: string;
9
+ icon: string;
10
+ /**
11
+ * @param foreignPackage Untrusted "foreign" object (e.g. from an online repo),
12
+ * assumed to be shaped roughly like a Spplice package.
13
+ */
14
+ constructor(foreignPackage: any);
15
+ /**
16
+ * Extracts a readable stream of a tar.xz archive to the given folder.
17
+ * @param inputStream Readable stream of input tar.xz file.
18
+ * @param destinationPath Path to directory in which to extract the archive.
19
+ */
20
+ static extractFromStream(inputStream: Readable, destinationPath: string): Promise<unknown>;
21
+ /**
22
+ * Extracts a tar.xz archive file to the given folder.
23
+ * @param filePath Input tar.xz archive path.
24
+ * @param destinationPath Path to directory in which to extract the archive.
25
+ */
26
+ static extractFromFile(filePath: string, destinationPath: string): Promise<unknown>;
27
+ /**
28
+ * Downloads a tar.xz archive from a URL and extracts it to the given folder.
29
+ * @param fileURL Input tar.xz archive URL.
30
+ * @param destinationPath Path to directory in which to extract the archive.
31
+ */
32
+ static extractFromURL(fileURL: string, destinationPath: string): Promise<unknown>;
33
+ /**
34
+ * Downloads and extracts the package's file archive to the given folder.
35
+ * @param destinationPath Path to directory in which to extract the archive.
36
+ */
37
+ extract(destinationPath: string): Promise<unknown>;
38
+ }
39
+ declare class SppliceRepository {
40
+ packages: Set<SpplicePackage>;
41
+ addPackage(newPackage: SpplicePackage): void;
42
+ removePackage(removedPackage: SpplicePackage): void;
43
+ /**
44
+ * Fetches a Spplice repository from its URL.
45
+ * @param repositoryURL Link to Spplice repository index (JSON).
46
+ * @param strict Whether to throw an error when package parsing fails.
47
+ * @returns A new Spplice repository.
48
+ */
49
+ static fromURL(repositoryURL: string, strict?: boolean): Promise<SppliceRepository>;
50
+ }
51
+ export { SpplicePackage, SppliceRepository };
52
+ //# sourceMappingURL=PackageTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PackageTools.d.ts","sourceRoot":"","sources":["../../src/PackageTools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAMvC,cAAM,cAAc;IAElB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAM;IACzB,OAAO,EAAE,MAAM,CAAW;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAM;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;gBACU,cAAc,EAAE,GAAG;IA6BhC;;;;OAIG;WACU,iBAAiB,CAAE,WAAW,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM;IAgC9E;;;;OAIG;WACU,eAAe,CAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM;IAKvE;;;;OAIG;WACU,cAAc,CAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM;IAQrE;;;OAGG;IACG,OAAO,CAAE,eAAe,EAAE,MAAM;CAIvC;AAED,cAAM,iBAAiB;IAErB,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC,CAAa;IAE1C,UAAU,CAAE,UAAU,EAAE,cAAc;IAGtC,aAAa,CAAE,cAAc,EAAE,cAAc;IAI7C;;;;;OAKG;WACU,OAAO,CAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC;CA4BlG;AAED,OAAO,EACL,cAAc,EACd,iBAAiB,EAClB,CAAC"}
@@ -0,0 +1,160 @@
1
+ import { Readable } from "node:stream";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import * as tar from "tar-stream";
5
+ import * as lzma from "lzma-native";
6
+ class SpplicePackage {
7
+ title;
8
+ author;
9
+ description = "";
10
+ version = "1.0.0";
11
+ args = [];
12
+ file;
13
+ icon;
14
+ /**
15
+ * @param foreignPackage Untrusted "foreign" object (e.g. from an online repo),
16
+ * assumed to be shaped roughly like a Spplice package.
17
+ */
18
+ constructor(foreignPackage) {
19
+ // Title is required
20
+ this.title = foreignPackage.title.toString().trim();
21
+ if (!this.title)
22
+ throw "Invalid package title";
23
+ // Author is required
24
+ this.author = foreignPackage.author.toString().trim();
25
+ if (!this.author)
26
+ throw "Invalid package author";
27
+ // Description is optional
28
+ if (typeof foreignPackage.description === "string") {
29
+ this.description = foreignPackage.description.trim();
30
+ }
31
+ // File URL is required
32
+ if (typeof foreignPackage.file !== "string")
33
+ throw "Invalid package file URL";
34
+ this.file = foreignPackage.file;
35
+ // Icon URL is required
36
+ if (typeof foreignPackage.icon !== "string")
37
+ throw "Invalid package icon URL";
38
+ this.icon = foreignPackage.icon;
39
+ // Version is optional
40
+ if (typeof foreignPackage.version === "string") {
41
+ this.version = foreignPackage.version;
42
+ }
43
+ // Command line arguments are optional
44
+ if (Array.isArray(foreignPackage.args)) {
45
+ this.args = foreignPackage.args;
46
+ }
47
+ else if (typeof foreignPackage.args === "string") {
48
+ this.args = [foreignPackage.args];
49
+ }
50
+ }
51
+ /**
52
+ * Extracts a readable stream of a tar.xz archive to the given folder.
53
+ * @param inputStream Readable stream of input tar.xz file.
54
+ * @param destinationPath Path to directory in which to extract the archive.
55
+ */
56
+ static async extractFromStream(inputStream, destinationPath) {
57
+ return await new Promise((resolve, reject) => {
58
+ try {
59
+ const lzmaDecompressor = lzma.createDecompressor();
60
+ const tarExtractor = tar.extract();
61
+ tarExtractor.on("entry", (header, stream, next) => {
62
+ // Get just entry name and type from header.
63
+ // It's probably best not to replicate permissions/users/groups.
64
+ const { name, type } = header;
65
+ const fullPath = path.join(destinationPath, name);
66
+ // Set up handler to continue when this entry has been parsed
67
+ stream.on("end", () => next());
68
+ // For files, pipe them to a write stream.
69
+ // For directories, quietly create them recursively.
70
+ if (type === "file") {
71
+ stream.pipe(fs.createWriteStream(fullPath));
72
+ }
73
+ else if (type === "directory") {
74
+ fs.mkdirSync(fullPath, { recursive: true });
75
+ }
76
+ // Drain the rest of the stream if anything remains
77
+ stream.resume();
78
+ });
79
+ // Resolve promise once archive has been extracted
80
+ tarExtractor.on("finish", resolve);
81
+ // Pipe the input file - first decompress, then extract
82
+ inputStream.pipe(lzmaDecompressor).pipe(tarExtractor);
83
+ }
84
+ catch (err) {
85
+ reject(err);
86
+ }
87
+ });
88
+ }
89
+ /**
90
+ * Extracts a tar.xz archive file to the given folder.
91
+ * @param filePath Input tar.xz archive path.
92
+ * @param destinationPath Path to directory in which to extract the archive.
93
+ */
94
+ static async extractFromFile(filePath, destinationPath) {
95
+ const inputStream = fs.createReadStream(filePath);
96
+ return await SpplicePackage.extractFromStream(inputStream, destinationPath);
97
+ }
98
+ /**
99
+ * Downloads a tar.xz archive from a URL and extracts it to the given folder.
100
+ * @param fileURL Input tar.xz archive URL.
101
+ * @param destinationPath Path to directory in which to extract the archive.
102
+ */
103
+ static async extractFromURL(fileURL, destinationPath) {
104
+ const response = await fetch(fileURL);
105
+ if (response.status !== 200)
106
+ throw `Server returned response ${response.status}`;
107
+ if (!response.body)
108
+ throw `Server did not return any content.`;
109
+ const inputStream = Readable.fromWeb(response.body);
110
+ return await SpplicePackage.extractFromStream(inputStream, destinationPath);
111
+ }
112
+ /**
113
+ * Downloads and extracts the package's file archive to the given folder.
114
+ * @param destinationPath Path to directory in which to extract the archive.
115
+ */
116
+ async extract(destinationPath) {
117
+ return await SpplicePackage.extractFromURL(this.file, destinationPath);
118
+ }
119
+ }
120
+ class SppliceRepository {
121
+ packages = new Set();
122
+ addPackage(newPackage) {
123
+ this.packages.add(newPackage);
124
+ }
125
+ removePackage(removedPackage) {
126
+ this.packages.delete(removedPackage);
127
+ }
128
+ /**
129
+ * Fetches a Spplice repository from its URL.
130
+ * @param repositoryURL Link to Spplice repository index (JSON).
131
+ * @param strict Whether to throw an error when package parsing fails.
132
+ * @returns A new Spplice repository.
133
+ */
134
+ static async fromURL(repositoryURL, strict = false) {
135
+ // Get JSON data from input URL
136
+ const response = await fetch(repositoryURL.trim());
137
+ if (response.status !== 200)
138
+ throw `Server returned response ${response.status}`;
139
+ const repositoryData = (await response.json());
140
+ // Validate repository structure
141
+ if (!Array.isArray(repositoryData.packages)) {
142
+ throw "Malformed repository index.";
143
+ }
144
+ const repository = new SppliceRepository();
145
+ // Try to convert each entry to a Spplice package, skip any that fail
146
+ for (const entry of repositoryData.packages) {
147
+ try {
148
+ const newPackage = new SpplicePackage(entry);
149
+ repository.addPackage(newPackage);
150
+ }
151
+ catch (err) {
152
+ if (strict)
153
+ throw err;
154
+ }
155
+ }
156
+ return repository;
157
+ }
158
+ }
159
+ export { SpplicePackage, SppliceRepository };
160
+ //# sourceMappingURL=PackageTools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PackageTools.js","sourceRoot":"","sources":["../../src/PackageTools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { SteamworksSDK } from "steamworks-ffi-node";
2
+ type UserDetails = {
3
+ steamid: BigInt;
4
+ name: string;
5
+ };
6
+ declare class SteamClient {
7
+ steamworksClient: SteamworksSDK;
8
+ appID: number;
9
+ /**
10
+ * Initialize Steamworks API.
11
+ * @param appID Game's Steam AppID (default 620 = Portal 2)
12
+ * Note: This makes Steam think that the specified game (Portal 2) is running.
13
+ */
14
+ constructor(appID?: number);
15
+ /**
16
+ * @returns {string} Absolute path to game directory.
17
+ */
18
+ getGameDir(): string;
19
+ /**
20
+ * @returns {UserDetails} Current user's SteamID64 and username.
21
+ */
22
+ getUserDetails(): UserDetails;
23
+ /**
24
+ * Checks whether the currently logged-in user has access to the game on Steam.
25
+ * @returns {boolean} true if current user has the game, false otherwise.
26
+ */
27
+ doesUserOwnGame(): boolean;
28
+ /**
29
+ * Searches for Steam binaries and returns all that were found. Output is
30
+ * sorted by how relevant the results are. The most useful result is first.
31
+ *
32
+ * @returns {string[]} Array of absolute paths to found Steam executables,
33
+ * sorted by descending relevance.
34
+ */
35
+ static findSteam(): Promise<string[]>;
36
+ /**
37
+ * Runs the Steam executable with the given command-line arguments.
38
+ * Can be used to start Steam, or to start a Steam game by using "-applaunch".
39
+ *
40
+ * TODO: Replace with manual game launch, hooking virtual file system.
41
+ *
42
+ * @param args Command-line arguments for Steam.
43
+ */
44
+ static launchSteam(args?: string[]): Promise<void>;
45
+ }
46
+ export { type UserDetails, SteamClient };
47
+ //# sourceMappingURL=SteamTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SteamTools.d.ts","sourceRoot":"","sources":["../../src/SteamTools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOpD,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAA;CACb,CAAC;AAEF,cAAM,WAAW;IAEf,gBAAgB,EAAE,aAAa,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IAEd;;;;OAIG;gBACU,KAAK,GAAE,MAAY;IAahC;;OAEG;IACH,UAAU,IAAK,MAAM;IAKrB;;OAEG;IACH,cAAc,IAAK,WAAW;IAM9B;;;OAGG;IACH,eAAe,IAAK,OAAO;IAI3B;;;;;;OAMG;WACU,SAAS,IAAK,OAAO,CAAC,MAAM,EAAE,CAAC;IA4F5C;;;;;;;OAOG;WACU,WAAW,CAAE,IAAI,GAAE,MAAM,EAAO;CAsB9C;AAED,OAAO,EACL,KAAK,WAAW,EAChB,WAAW,EACZ,CAAC"}