@loontail/minecraft-kit 0.6.0 → 0.6.1
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/README.md +55 -8
- package/dist/cli/index.cjs +2330 -1234
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +2329 -1233
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +1689 -851
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +678 -417
- package/dist/index.d.ts +678 -417
- package/dist/index.mjs +1682 -852
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
- package/LICENSE.md +0 -22
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import process2 from 'process';
|
|
2
3
|
import os from 'os';
|
|
3
4
|
import { LRUCache } from 'lru-cache';
|
|
4
5
|
import path from 'path';
|
|
@@ -12,6 +13,317 @@ import { AsyncResource } from 'async_hooks';
|
|
|
12
13
|
import { Buffer as Buffer$1 } from 'buffer';
|
|
13
14
|
import { spawn } from 'child_process';
|
|
14
15
|
|
|
16
|
+
// src/types/errors.ts
|
|
17
|
+
var MinecraftKitErrorCodes = {
|
|
18
|
+
NETWORK_TIMEOUT: "NETWORK_TIMEOUT",
|
|
19
|
+
NETWORK_HTTP_ERROR: "NETWORK_HTTP_ERROR",
|
|
20
|
+
NETWORK_ABORTED: "NETWORK_ABORTED",
|
|
21
|
+
INTEGRITY_HASH_MISMATCH: "INTEGRITY_HASH_MISMATCH",
|
|
22
|
+
INTEGRITY_SIZE_MISMATCH: "INTEGRITY_SIZE_MISMATCH",
|
|
23
|
+
MANIFEST_INVALID: "MANIFEST_INVALID",
|
|
24
|
+
MANIFEST_NOT_FOUND: "MANIFEST_NOT_FOUND",
|
|
25
|
+
METADATA_PARSE_ERROR: "METADATA_PARSE_ERROR",
|
|
26
|
+
FILESYSTEM_PATH_TRAVERSAL: "FILESYSTEM_PATH_TRAVERSAL",
|
|
27
|
+
FILESYSTEM_WRITE_ERROR: "FILESYSTEM_WRITE_ERROR",
|
|
28
|
+
FILESYSTEM_READ_ERROR: "FILESYSTEM_READ_ERROR",
|
|
29
|
+
ARCHIVE_INVALID: "ARCHIVE_INVALID",
|
|
30
|
+
ARCHIVE_TOO_LARGE: "ARCHIVE_TOO_LARGE",
|
|
31
|
+
ARCHIVE_ENTRY_REJECTED: "ARCHIVE_ENTRY_REJECTED",
|
|
32
|
+
RUNTIME_NOT_FOUND: "RUNTIME_NOT_FOUND",
|
|
33
|
+
RUNTIME_UNSUPPORTED_PLATFORM: "RUNTIME_UNSUPPORTED_PLATFORM",
|
|
34
|
+
FORGE_PROCESSOR_FAILED: "FORGE_PROCESSOR_FAILED",
|
|
35
|
+
FORGE_INSTALLER_INVALID: "FORGE_INSTALLER_INVALID",
|
|
36
|
+
LAUNCH_JAVA_NOT_FOUND: "LAUNCH_JAVA_NOT_FOUND",
|
|
37
|
+
LAUNCH_PROCESS_FAILED: "LAUNCH_PROCESS_FAILED",
|
|
38
|
+
LAUNCH_ABORTED: "LAUNCH_ABORTED",
|
|
39
|
+
VERIFICATION_FAILED: "VERIFICATION_FAILED",
|
|
40
|
+
INVALID_INPUT: "INVALID_INPUT",
|
|
41
|
+
NOT_IMPLEMENTED: "NOT_IMPLEMENTED",
|
|
42
|
+
UNSUPPORTED_VERSION: "UNSUPPORTED_VERSION",
|
|
43
|
+
LZMA_DECODE_ERROR: "LZMA_DECODE_ERROR",
|
|
44
|
+
AUTH_DEVICE_CODE_EXPIRED: "AUTH_DEVICE_CODE_EXPIRED",
|
|
45
|
+
AUTH_DEVICE_CODE_DECLINED: "AUTH_DEVICE_CODE_DECLINED",
|
|
46
|
+
AUTH_DEVICE_CODE_FAILED: "AUTH_DEVICE_CODE_FAILED",
|
|
47
|
+
AUTH_REFRESH_FAILED: "AUTH_REFRESH_FAILED",
|
|
48
|
+
AUTH_XBOX_FAILED: "AUTH_XBOX_FAILED",
|
|
49
|
+
AUTH_XSTS_FAILED: "AUTH_XSTS_FAILED",
|
|
50
|
+
AUTH_MINECRAFT_FAILED: "AUTH_MINECRAFT_FAILED",
|
|
51
|
+
AUTH_NO_GAME_OWNERSHIP: "AUTH_NO_GAME_OWNERSHIP",
|
|
52
|
+
AUTH_MISSING_CLIENT_ID: "AUTH_MISSING_CLIENT_ID",
|
|
53
|
+
AUTH_CANCELLED: "AUTH_CANCELLED"
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// src/core/errors.ts
|
|
57
|
+
var MinecraftKitError = class extends Error {
|
|
58
|
+
name = "MinecraftKitError";
|
|
59
|
+
/** Stable discriminator. */
|
|
60
|
+
code;
|
|
61
|
+
/** Structured context; safe to serialize. */
|
|
62
|
+
context;
|
|
63
|
+
constructor(code, message, options = {}) {
|
|
64
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
65
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
66
|
+
this.code = code;
|
|
67
|
+
this.context = Object.freeze({ ...options.context ?? {} });
|
|
68
|
+
}
|
|
69
|
+
/** JSON-friendly representation. */
|
|
70
|
+
toJSON() {
|
|
71
|
+
return {
|
|
72
|
+
name: this.name,
|
|
73
|
+
code: this.code,
|
|
74
|
+
message: this.message,
|
|
75
|
+
context: this.context,
|
|
76
|
+
cause: this.cause instanceof Error ? { name: this.cause.name, message: this.cause.message } : this.cause
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var isMinecraftKitError = (e) => {
|
|
81
|
+
return e instanceof MinecraftKitError;
|
|
82
|
+
};
|
|
83
|
+
var isErrorCode = (e, code) => {
|
|
84
|
+
return isMinecraftKitError(e) && e.code === code;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// src/types/auth.ts
|
|
88
|
+
var AuthModes = {
|
|
89
|
+
/** Offline-mode play with a chosen username and synthetic UUID. */
|
|
90
|
+
OFFLINE: "offline",
|
|
91
|
+
/** Pre-authenticated session — caller provides the access token and identity. */
|
|
92
|
+
ONLINE: "online"
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/auth/microsoft.ts
|
|
96
|
+
var TENANT = "consumers";
|
|
97
|
+
var DEVICE_CODE_URL = `https://login.microsoftonline.com/${TENANT}/oauth2/v2.0/devicecode`;
|
|
98
|
+
var TOKEN_URL = `https://login.microsoftonline.com/${TENANT}/oauth2/v2.0/token`;
|
|
99
|
+
var SCOPE = "XboxLive.signin offline_access";
|
|
100
|
+
var startDeviceCode = async (input) => {
|
|
101
|
+
const body = new URLSearchParams({ client_id: input.clientId, scope: SCOPE });
|
|
102
|
+
const response = await input.http.request(DEVICE_CODE_URL, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
105
|
+
body: body.toString(),
|
|
106
|
+
// We need to read the body of 400/401 responses to surface Microsoft's actual
|
|
107
|
+
// `error_description` rather than reporting "HTTP 400" — the most common cause is
|
|
108
|
+
// an app registration that doesn't allow personal MSA accounts or hasn't enabled
|
|
109
|
+
// public client flows.
|
|
110
|
+
acceptNonOk: true,
|
|
111
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
112
|
+
});
|
|
113
|
+
if (response.status < 200 || response.status >= 300) {
|
|
114
|
+
const err = await response.json().catch(() => ({}));
|
|
115
|
+
throw new MinecraftKitError(
|
|
116
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_FAILED,
|
|
117
|
+
explainDeviceCodeError(err, input.clientId),
|
|
118
|
+
{
|
|
119
|
+
context: {
|
|
120
|
+
httpStatus: response.status,
|
|
121
|
+
microsoftError: err.error,
|
|
122
|
+
clientId: input.clientId
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const data = await response.json();
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
const state = {
|
|
130
|
+
deviceCode: data.device_code,
|
|
131
|
+
userCode: data.user_code,
|
|
132
|
+
verificationUri: data.verification_uri,
|
|
133
|
+
message: data.message,
|
|
134
|
+
expiresIn: data.expires_in,
|
|
135
|
+
interval: data.interval,
|
|
136
|
+
clientId: input.clientId,
|
|
137
|
+
expiresAt: now + data.expires_in * 1e3
|
|
138
|
+
};
|
|
139
|
+
const prompt = {
|
|
140
|
+
userCode: data.user_code,
|
|
141
|
+
verificationUri: data.verification_uri,
|
|
142
|
+
message: data.message,
|
|
143
|
+
expiresIn: data.expires_in,
|
|
144
|
+
interval: data.interval
|
|
145
|
+
};
|
|
146
|
+
return { prompt, state };
|
|
147
|
+
};
|
|
148
|
+
var pollDeviceCode = async (input) => {
|
|
149
|
+
let intervalSec = input.state.interval;
|
|
150
|
+
while (true) {
|
|
151
|
+
if (input.signal?.aborted) {
|
|
152
|
+
throw new MinecraftKitError(
|
|
153
|
+
MinecraftKitErrorCodes.AUTH_CANCELLED,
|
|
154
|
+
"Device-code polling aborted.",
|
|
155
|
+
{
|
|
156
|
+
context: { reason: input.signal.reason }
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (Date.now() >= input.state.expiresAt) {
|
|
161
|
+
throw new MinecraftKitError(
|
|
162
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_EXPIRED,
|
|
163
|
+
"Device code expired before the user signed in."
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const delayMs = intervalSec * 1e3;
|
|
167
|
+
input.onTick?.({ nextDelayMs: delayMs, expiresAt: input.state.expiresAt });
|
|
168
|
+
await wait(delayMs, input.signal);
|
|
169
|
+
const body = new URLSearchParams({
|
|
170
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
171
|
+
client_id: input.state.clientId,
|
|
172
|
+
device_code: input.state.deviceCode
|
|
173
|
+
});
|
|
174
|
+
const response = await input.http.request(TOKEN_URL, {
|
|
175
|
+
method: "POST",
|
|
176
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
177
|
+
body: body.toString(),
|
|
178
|
+
acceptNonOk: true
|
|
179
|
+
});
|
|
180
|
+
if (response.status >= 200 && response.status < 300) {
|
|
181
|
+
const ok = await response.json();
|
|
182
|
+
if (!ok.refresh_token) {
|
|
183
|
+
throw new MinecraftKitError(
|
|
184
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_FAILED,
|
|
185
|
+
"Microsoft did not return a refresh token. Make sure `offline_access` is in the requested scopes."
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
accessToken: ok.access_token,
|
|
190
|
+
refreshToken: ok.refresh_token,
|
|
191
|
+
expiresIn: ok.expires_in
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const err = await response.json().catch(() => ({}));
|
|
195
|
+
switch (err.error) {
|
|
196
|
+
case "authorization_pending":
|
|
197
|
+
continue;
|
|
198
|
+
case "slow_down":
|
|
199
|
+
intervalSec += 5;
|
|
200
|
+
continue;
|
|
201
|
+
case "authorization_declined":
|
|
202
|
+
throw new MinecraftKitError(
|
|
203
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_DECLINED,
|
|
204
|
+
"The user declined the sign-in request."
|
|
205
|
+
);
|
|
206
|
+
case "expired_token":
|
|
207
|
+
throw new MinecraftKitError(
|
|
208
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_EXPIRED,
|
|
209
|
+
"Device code expired before the user signed in."
|
|
210
|
+
);
|
|
211
|
+
default:
|
|
212
|
+
throw new MinecraftKitError(
|
|
213
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_FAILED,
|
|
214
|
+
`Microsoft device-code exchange failed: ${err.error ?? "unknown_error"}${err.error_description ? ` \u2014 ${err.error_description}` : ""}`,
|
|
215
|
+
{ context: { httpStatus: response.status, microsoftError: err.error } }
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
var refreshMicrosoftToken = async (input) => {
|
|
221
|
+
const body = new URLSearchParams({
|
|
222
|
+
grant_type: "refresh_token",
|
|
223
|
+
client_id: input.clientId,
|
|
224
|
+
refresh_token: input.refreshToken,
|
|
225
|
+
scope: SCOPE
|
|
226
|
+
});
|
|
227
|
+
const response = await input.http.request(TOKEN_URL, {
|
|
228
|
+
method: "POST",
|
|
229
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
230
|
+
body: body.toString(),
|
|
231
|
+
acceptNonOk: true,
|
|
232
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
233
|
+
});
|
|
234
|
+
if (response.status < 200 || response.status >= 300) {
|
|
235
|
+
const err = await response.json().catch(() => ({}));
|
|
236
|
+
throw new MinecraftKitError(
|
|
237
|
+
MinecraftKitErrorCodes.AUTH_REFRESH_FAILED,
|
|
238
|
+
`Microsoft refused to refresh the token: ${err.error ?? "unknown_error"}${err.error_description ? ` \u2014 ${err.error_description}` : ""}`,
|
|
239
|
+
{ context: { httpStatus: response.status, microsoftError: err.error } }
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
const ok = await response.json();
|
|
243
|
+
return {
|
|
244
|
+
accessToken: ok.access_token,
|
|
245
|
+
refreshToken: ok.refresh_token ?? input.refreshToken,
|
|
246
|
+
expiresIn: ok.expires_in
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
var explainDeviceCodeError = (err, clientId) => {
|
|
250
|
+
const desc = err.error_description ?? "";
|
|
251
|
+
const ms = desc ? ` \u2014 ${desc}` : "";
|
|
252
|
+
if (/AADSTS700016/i.test(desc) || /not found in the directory/i.test(desc)) {
|
|
253
|
+
return `Microsoft cannot see app ${clientId} from the consumers tenant. Fix: Azure portal \u2192 your app \u2192 Authentication \u2192 "Supported account types" \u2192 choose "Personal Microsoft accounts only" or "Multitenant + personal accounts" \u2192 Save. Wait ~30s for propagation.${ms}`;
|
|
254
|
+
}
|
|
255
|
+
if (/AADSTS7000218/i.test(desc) || /must either be a confidential client/i.test(desc)) {
|
|
256
|
+
return `Microsoft rejected the client_id (${clientId}): "Allow public client flows" is OFF. Fix: Azure portal \u2192 your app \u2192 Authentication \u2192 bottom of the page \u2192 toggle "Allow public client flows" to Yes \u2192 Save.${ms}`;
|
|
257
|
+
}
|
|
258
|
+
if (/AADSTS50059/i.test(desc) || /tenant identifier/i.test(desc)) {
|
|
259
|
+
return `Microsoft Entra cannot route the request \u2014 the app's "Supported account types" excludes consumers. Fix: Azure portal \u2192 Authentication \u2192 set Supported account types to include personal MSA \u2192 Save.${ms}`;
|
|
260
|
+
}
|
|
261
|
+
switch (err.error) {
|
|
262
|
+
case "unauthorized_client":
|
|
263
|
+
return `Microsoft rejected the client_id (${clientId}). Likely cause: "Supported account types" excludes personal Microsoft accounts, OR "Allow public client flows" is disabled. Fix both in Azure portal \u2192 your app \u2192 Authentication.${ms}`;
|
|
264
|
+
case "invalid_client":
|
|
265
|
+
return `Microsoft does not recognise client_id ${clientId}. Make sure you pasted the Application (client) ID \u2014 not the Object ID or Tenant ID \u2014 and that the app exists.${ms}`;
|
|
266
|
+
case "invalid_request":
|
|
267
|
+
return `Microsoft rejected the device-code request as malformed.${ms}`;
|
|
268
|
+
case "invalid_scope":
|
|
269
|
+
return `Microsoft refused the requested scope (XboxLive.signin offline_access). Make sure the app is configured for Microsoft account sign-in.${ms}`;
|
|
270
|
+
default:
|
|
271
|
+
return `Microsoft device-code request failed: ${err.error ?? "unknown_error"}${ms}`;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
var wait = (ms, signal) => {
|
|
275
|
+
return new Promise((resolve, reject) => {
|
|
276
|
+
if (signal?.aborted) {
|
|
277
|
+
reject(
|
|
278
|
+
new MinecraftKitError(
|
|
279
|
+
MinecraftKitErrorCodes.AUTH_CANCELLED,
|
|
280
|
+
"Device-code polling aborted.",
|
|
281
|
+
{
|
|
282
|
+
context: { reason: signal.reason }
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const timer = setTimeout(() => {
|
|
289
|
+
signal?.removeEventListener("abort", onAbort);
|
|
290
|
+
resolve();
|
|
291
|
+
}, ms);
|
|
292
|
+
const onAbort = () => {
|
|
293
|
+
clearTimeout(timer);
|
|
294
|
+
reject(
|
|
295
|
+
new MinecraftKitError(
|
|
296
|
+
MinecraftKitErrorCodes.AUTH_CANCELLED,
|
|
297
|
+
"Device-code polling aborted.",
|
|
298
|
+
{
|
|
299
|
+
context: { reason: signal?.reason }
|
|
300
|
+
}
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
};
|
|
304
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// src/core/json.ts
|
|
309
|
+
var parseJsonStrict = (text, options) => {
|
|
310
|
+
try {
|
|
311
|
+
return JSON.parse(text);
|
|
312
|
+
} catch (cause) {
|
|
313
|
+
throw new MinecraftKitError(options.code, options.message, {
|
|
314
|
+
cause,
|
|
315
|
+
...options.context !== void 0 ? { context: options.context } : {}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
var parseJsonOrUndefined = (text) => {
|
|
320
|
+
try {
|
|
321
|
+
return JSON.parse(text);
|
|
322
|
+
} catch {
|
|
323
|
+
return void 0;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
15
327
|
// src/types/logger.ts
|
|
16
328
|
var LogLevels = {
|
|
17
329
|
DEBUG: "debug",
|
|
@@ -35,12 +347,361 @@ var consoleLogger = {
|
|
|
35
347
|
}
|
|
36
348
|
}
|
|
37
349
|
};
|
|
38
|
-
|
|
350
|
+
var scopedLogger = (base, scope, baseFields) => {
|
|
351
|
+
if (base === silentLogger) return silentLogger;
|
|
352
|
+
return {
|
|
353
|
+
log(level, message, fields) {
|
|
354
|
+
const merged = baseFields !== void 0 || fields !== void 0 ? { ...baseFields, ...fields } : void 0;
|
|
355
|
+
if (merged !== void 0) {
|
|
356
|
+
base.log(level, `[${scope}] ${message}`, merged);
|
|
357
|
+
} else {
|
|
358
|
+
base.log(level, `[${scope}] ${message}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
};
|
|
363
|
+
var pickConsole = (level) => {
|
|
39
364
|
if (level === LogLevels.ERROR) return console.error.bind(console);
|
|
40
365
|
if (level === LogLevels.WARN) return console.warn.bind(console);
|
|
41
366
|
if (level === LogLevels.INFO) return console.info.bind(console);
|
|
42
367
|
return console.debug.bind(console);
|
|
43
|
-
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// src/auth/debug.ts
|
|
371
|
+
var DEBUG_ENV_VAR = "MINECRAFT_KIT_AUTH_DEBUG";
|
|
372
|
+
var authDebug = (message) => {
|
|
373
|
+
if (process2.env[DEBUG_ENV_VAR]) {
|
|
374
|
+
process2.stderr.write(`[auth] ${message}
|
|
375
|
+
`);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// src/auth/minecraft.ts
|
|
380
|
+
var MC_LOGIN_URL = "https://api.minecraftservices.com/authentication/login_with_xbox";
|
|
381
|
+
var MC_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile";
|
|
382
|
+
var loginWithXbox = async (input) => {
|
|
383
|
+
const body = JSON.stringify({
|
|
384
|
+
identityToken: `XBL3.0 x=${input.userHash};${input.xstsToken}`
|
|
385
|
+
});
|
|
386
|
+
authDebug(
|
|
387
|
+
`login_with_xbox POST \u2014 userHashLen=${input.userHash.length}, xstsTokenLen=${input.xstsToken.length}`
|
|
388
|
+
);
|
|
389
|
+
const response = await input.http.request(MC_LOGIN_URL, {
|
|
390
|
+
method: "POST",
|
|
391
|
+
headers: {
|
|
392
|
+
"content-type": "application/json",
|
|
393
|
+
accept: "application/json",
|
|
394
|
+
// Mojang sometimes rejects unknown user-agents on the auth endpoints. Override the
|
|
395
|
+
// library default with a UA that matches what real Minecraft launchers send, so we
|
|
396
|
+
// don't trip an "anomalous client" filter.
|
|
397
|
+
"user-agent": "Minecraft Launcher/2.0 (minecraft-kit)"
|
|
398
|
+
},
|
|
399
|
+
body,
|
|
400
|
+
acceptNonOk: true,
|
|
401
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
402
|
+
});
|
|
403
|
+
if (response.status < 200 || response.status >= 300) {
|
|
404
|
+
const rawBody = await response.text().catch(() => "");
|
|
405
|
+
const detail = rawBody.slice(0, 400);
|
|
406
|
+
authDebug(`login_with_xbox failed status=${response.status} body=${detail}`);
|
|
407
|
+
if (response.status === 403) {
|
|
408
|
+
if (/invalid app registration/i.test(detail)) {
|
|
409
|
+
throw new MinecraftKitError(
|
|
410
|
+
MinecraftKitErrorCodes.AUTH_MINECRAFT_FAILED,
|
|
411
|
+
`Mojang has not approved this Azure AD application id for the Minecraft API. The OAuth + Xbox/XSTS exchange all succeeded, but api.minecraftservices.com only accepts client_ids that are on its allow-list. Apply at https://aka.ms/mce-reviewappid (Application ID, contact email, purpose) \u2014 approval typically takes a few days. Raw response: ${detail}`,
|
|
412
|
+
{ context: { httpStatus: 403, body: detail, reason: "invalid_app_registration" } }
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
throw new MinecraftKitError(
|
|
416
|
+
MinecraftKitErrorCodes.AUTH_NO_GAME_OWNERSHIP,
|
|
417
|
+
`Mojang refused login_with_xbox (HTTP 403). The Xbox/Microsoft exchange succeeded, but api.minecraftservices.com declined to issue a Minecraft token. Most common causes: (1) you signed in to the browser with a DIFFERENT Microsoft account than the one owning Java Edition \u2014 re-check the email on https://www.minecraft.net/profile and make sure it matches what you typed at the device-code page; (2) this account never used Xbox services before \u2014 open https://www.xbox.com once with this account, then retry; (3) transient Mojang 5xx/403, just retry in 60s. Raw response: ${detail || "<empty>"}`,
|
|
418
|
+
{ context: { httpStatus: 403, body: detail } }
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
throw new MinecraftKitError(
|
|
422
|
+
MinecraftKitErrorCodes.AUTH_MINECRAFT_FAILED,
|
|
423
|
+
`Minecraft sign-in failed with HTTP ${response.status}. Response: ${detail || "<empty>"}`,
|
|
424
|
+
{ context: { httpStatus: response.status, body: detail } }
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
const parsed = await response.json();
|
|
428
|
+
if (!parsed.access_token) {
|
|
429
|
+
throw new MinecraftKitError(
|
|
430
|
+
MinecraftKitErrorCodes.AUTH_MINECRAFT_FAILED,
|
|
431
|
+
"Minecraft sign-in returned no access token."
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
return { accessToken: parsed.access_token, expiresIn: parsed.expires_in };
|
|
435
|
+
};
|
|
436
|
+
var fetchMinecraftProfile = async (input) => {
|
|
437
|
+
const response = await input.http.request(MC_PROFILE_URL, {
|
|
438
|
+
headers: {
|
|
439
|
+
authorization: `Bearer ${input.accessToken}`,
|
|
440
|
+
accept: "application/json",
|
|
441
|
+
"user-agent": "Minecraft Launcher/2.0 (minecraft-kit)"
|
|
442
|
+
},
|
|
443
|
+
acceptNonOk: true,
|
|
444
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
445
|
+
});
|
|
446
|
+
if (response.status === 404) {
|
|
447
|
+
throw new MinecraftKitError(
|
|
448
|
+
MinecraftKitErrorCodes.AUTH_NO_GAME_OWNERSHIP,
|
|
449
|
+
"This Microsoft account does not own Minecraft: Java Edition.",
|
|
450
|
+
{ context: { httpStatus: 404 } }
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
if (response.status < 200 || response.status >= 300) {
|
|
454
|
+
throw new MinecraftKitError(
|
|
455
|
+
MinecraftKitErrorCodes.AUTH_MINECRAFT_FAILED,
|
|
456
|
+
`Failed to load Minecraft profile (HTTP ${response.status}).`,
|
|
457
|
+
{ context: { httpStatus: response.status } }
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
const parsed = await response.json();
|
|
461
|
+
if (parsed.errorMessage || !parsed.id || !parsed.name) {
|
|
462
|
+
throw new MinecraftKitError(
|
|
463
|
+
MinecraftKitErrorCodes.AUTH_MINECRAFT_FAILED,
|
|
464
|
+
parsed.errorMessage ?? "Minecraft profile response was malformed."
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
return { uuid: dashUuid(parsed.id), username: parsed.name };
|
|
468
|
+
};
|
|
469
|
+
var extractXuid = (accessToken) => {
|
|
470
|
+
const parts = accessToken.split(".");
|
|
471
|
+
const payload = parts[1];
|
|
472
|
+
if (typeof payload !== "string") return "";
|
|
473
|
+
const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString(
|
|
474
|
+
"utf8"
|
|
475
|
+
);
|
|
476
|
+
const parsed = parseJsonOrUndefined(json);
|
|
477
|
+
return typeof parsed?.xuid === "string" ? parsed.xuid : "";
|
|
478
|
+
};
|
|
479
|
+
var dashUuid = (raw) => {
|
|
480
|
+
if (raw.includes("-")) return raw;
|
|
481
|
+
if (raw.length !== 32) return raw;
|
|
482
|
+
return `${raw.slice(0, 8)}-${raw.slice(8, 12)}-${raw.slice(12, 16)}-${raw.slice(
|
|
483
|
+
16,
|
|
484
|
+
20
|
|
485
|
+
)}-${raw.slice(20)}`;
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// src/auth/xbox.ts
|
|
489
|
+
var XBL_URL = "https://user.auth.xboxlive.com/user/authenticate";
|
|
490
|
+
var XSTS_URL = "https://xsts.auth.xboxlive.com/xsts/authorize";
|
|
491
|
+
var authenticateXbl = async (input) => {
|
|
492
|
+
const body = JSON.stringify({
|
|
493
|
+
Properties: {
|
|
494
|
+
AuthMethod: "RPS",
|
|
495
|
+
SiteName: "user.auth.xboxlive.com",
|
|
496
|
+
RpsTicket: `d=${input.accessToken}`
|
|
497
|
+
},
|
|
498
|
+
RelyingParty: "http://auth.xboxlive.com",
|
|
499
|
+
TokenType: "JWT"
|
|
500
|
+
});
|
|
501
|
+
let response;
|
|
502
|
+
try {
|
|
503
|
+
response = await input.http.request(XBL_URL, {
|
|
504
|
+
method: "POST",
|
|
505
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
506
|
+
body,
|
|
507
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
508
|
+
});
|
|
509
|
+
} catch (cause) {
|
|
510
|
+
throw new MinecraftKitError(
|
|
511
|
+
MinecraftKitErrorCodes.AUTH_XBOX_FAILED,
|
|
512
|
+
"Xbox Live authentication failed.",
|
|
513
|
+
{
|
|
514
|
+
cause
|
|
515
|
+
}
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
const parsed = await response.json();
|
|
519
|
+
const userHash = parsed.DisplayClaims?.xui?.[0]?.uhs;
|
|
520
|
+
if (!parsed.Token || !userHash) {
|
|
521
|
+
throw new MinecraftKitError(
|
|
522
|
+
MinecraftKitErrorCodes.AUTH_XBOX_FAILED,
|
|
523
|
+
"Xbox Live authentication returned an incomplete response."
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
input.logger?.log("debug", `XBL ok \u2014 tokenLen=${parsed.Token.length}, userHash=${userHash}`);
|
|
527
|
+
return { token: parsed.Token, userHash };
|
|
528
|
+
};
|
|
529
|
+
var authenticateXsts = async (input) => {
|
|
530
|
+
const body = JSON.stringify({
|
|
531
|
+
Properties: { SandboxId: "RETAIL", UserTokens: [input.xblToken] },
|
|
532
|
+
RelyingParty: "rp://api.minecraftservices.com/",
|
|
533
|
+
TokenType: "JWT"
|
|
534
|
+
});
|
|
535
|
+
const response = await input.http.request(XSTS_URL, {
|
|
536
|
+
method: "POST",
|
|
537
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
538
|
+
body,
|
|
539
|
+
acceptNonOk: true,
|
|
540
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
541
|
+
});
|
|
542
|
+
if (response.status === 401) {
|
|
543
|
+
const err = await response.json().catch(() => ({}));
|
|
544
|
+
throw new MinecraftKitError(MinecraftKitErrorCodes.AUTH_XSTS_FAILED, explainXErr(err.XErr), {
|
|
545
|
+
context: { xerr: err.XErr ?? null, message: err.Message ?? null }
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
if (response.status < 200 || response.status >= 300) {
|
|
549
|
+
throw new MinecraftKitError(
|
|
550
|
+
MinecraftKitErrorCodes.AUTH_XSTS_FAILED,
|
|
551
|
+
`XSTS authorization failed with HTTP ${response.status}.`,
|
|
552
|
+
{ context: { httpStatus: response.status } }
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
const parsed = await response.json();
|
|
556
|
+
const userHash = parsed.DisplayClaims?.xui?.[0]?.uhs;
|
|
557
|
+
if (!parsed.Token || !userHash) {
|
|
558
|
+
throw new MinecraftKitError(
|
|
559
|
+
MinecraftKitErrorCodes.AUTH_XSTS_FAILED,
|
|
560
|
+
"XSTS authorization returned an incomplete response."
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
input.logger?.log("debug", `XSTS ok \u2014 tokenLen=${parsed.Token.length}, userHash=${userHash}`);
|
|
564
|
+
return { token: parsed.Token, userHash };
|
|
565
|
+
};
|
|
566
|
+
var explainXErr = (xerr) => {
|
|
567
|
+
switch (xerr) {
|
|
568
|
+
case 2148916227:
|
|
569
|
+
return "This account has been banned from Xbox.";
|
|
570
|
+
case 2148916233:
|
|
571
|
+
return "This Microsoft account has no Xbox profile yet \u2014 sign in once at https://minecraft.net to create one.";
|
|
572
|
+
case 2148916235:
|
|
573
|
+
return "Xbox Live is not available in this account's country/region.";
|
|
574
|
+
case 2148916236:
|
|
575
|
+
case 2148916237:
|
|
576
|
+
return "This account needs an adult to verify it. Sign in on Xbox to complete the prompt.";
|
|
577
|
+
case 2148916238:
|
|
578
|
+
return "This is a child account. Add it to a Microsoft family group with an adult to continue.";
|
|
579
|
+
default:
|
|
580
|
+
return xerr ? `XSTS authorization failed (XErr ${xerr}).` : "XSTS authorization failed.";
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
// src/auth/index.ts
|
|
585
|
+
var CLIENT_ID_ENV_VAR = "MINECRAFT_KIT_MSA_CLIENT_ID";
|
|
586
|
+
var MojangAuthApi = class {
|
|
587
|
+
constructor(http) {
|
|
588
|
+
this.http = http;
|
|
589
|
+
}
|
|
590
|
+
http;
|
|
591
|
+
/**
|
|
592
|
+
* Run the full device-code flow end-to-end. The caller supplies an `onPrompt` callback to
|
|
593
|
+
* render the URL + code to the user; this method polls until they finish.
|
|
594
|
+
*/
|
|
595
|
+
async login(options) {
|
|
596
|
+
const clientId = resolveClientId(options.clientId);
|
|
597
|
+
const { prompt, state } = await startDeviceCode({
|
|
598
|
+
http: this.http,
|
|
599
|
+
clientId,
|
|
600
|
+
...options.signal !== void 0 ? { signal: options.signal } : {}
|
|
601
|
+
});
|
|
602
|
+
await options.onPrompt(prompt);
|
|
603
|
+
const msToken = await pollDeviceCode({
|
|
604
|
+
http: this.http,
|
|
605
|
+
state,
|
|
606
|
+
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
607
|
+
...options.onPoll !== void 0 ? { onTick: options.onPoll } : {}
|
|
608
|
+
});
|
|
609
|
+
return this.completeMicrosoftToken(msToken, clientId, options.signal);
|
|
610
|
+
}
|
|
611
|
+
/** Refresh a previously obtained session. The Microsoft refresh token may be rotated. */
|
|
612
|
+
async refresh(refreshToken, options = {}) {
|
|
613
|
+
const clientId = resolveClientId(options.clientId);
|
|
614
|
+
const msToken = await refreshMicrosoftToken({
|
|
615
|
+
http: this.http,
|
|
616
|
+
refreshToken,
|
|
617
|
+
clientId,
|
|
618
|
+
...options.signal !== void 0 ? { signal: options.signal } : {}
|
|
619
|
+
});
|
|
620
|
+
return this.completeMicrosoftToken(msToken, clientId, options.signal);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Lower-level device-code surface — exposed so callers can decouple "show prompt" from
|
|
624
|
+
* "block on poll" (e.g. a GUI that lets the user dismiss the modal). Most callers should
|
|
625
|
+
* use {@link login} instead.
|
|
626
|
+
*/
|
|
627
|
+
deviceCode = {
|
|
628
|
+
start: (options = {}) => {
|
|
629
|
+
const clientId = resolveClientId(options.clientId);
|
|
630
|
+
return startDeviceCode({
|
|
631
|
+
http: this.http,
|
|
632
|
+
clientId,
|
|
633
|
+
...options.signal !== void 0 ? { signal: options.signal } : {}
|
|
634
|
+
});
|
|
635
|
+
},
|
|
636
|
+
poll: async (state, options = {}) => {
|
|
637
|
+
const msToken = await pollDeviceCode({
|
|
638
|
+
http: this.http,
|
|
639
|
+
state,
|
|
640
|
+
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
641
|
+
...options.onTick !== void 0 ? { onTick: options.onTick } : {}
|
|
642
|
+
});
|
|
643
|
+
return this.completeMicrosoftToken(msToken, state.clientId, options.signal);
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
/** Steps 2 → 5: given a Microsoft access token, finish the flow. */
|
|
647
|
+
async completeMicrosoftToken(msToken, clientId, signal) {
|
|
648
|
+
const signalOpt = signal !== void 0 ? { signal } : {};
|
|
649
|
+
const xbl = await authenticateXbl({
|
|
650
|
+
http: this.http,
|
|
651
|
+
accessToken: msToken.accessToken,
|
|
652
|
+
...signalOpt
|
|
653
|
+
});
|
|
654
|
+
const xsts = await authenticateXsts({
|
|
655
|
+
http: this.http,
|
|
656
|
+
xblToken: xbl.token,
|
|
657
|
+
...signalOpt
|
|
658
|
+
});
|
|
659
|
+
const mc = await loginWithXbox({
|
|
660
|
+
http: this.http,
|
|
661
|
+
xstsToken: xsts.token,
|
|
662
|
+
userHash: xsts.userHash,
|
|
663
|
+
...signalOpt
|
|
664
|
+
});
|
|
665
|
+
const profile = await fetchMinecraftProfile({
|
|
666
|
+
http: this.http,
|
|
667
|
+
accessToken: mc.accessToken,
|
|
668
|
+
...signalOpt
|
|
669
|
+
});
|
|
670
|
+
return {
|
|
671
|
+
minecraft: {
|
|
672
|
+
username: profile.username,
|
|
673
|
+
uuid: profile.uuid,
|
|
674
|
+
accessToken: mc.accessToken,
|
|
675
|
+
expiresAt: Date.now() + mc.expiresIn * 1e3,
|
|
676
|
+
xuid: extractXuid(mc.accessToken)
|
|
677
|
+
},
|
|
678
|
+
microsoft: {
|
|
679
|
+
refreshToken: msToken.refreshToken,
|
|
680
|
+
clientId
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
var toOnlineAuth = (session) => {
|
|
686
|
+
return {
|
|
687
|
+
mode: AuthModes.ONLINE,
|
|
688
|
+
username: session.minecraft.username,
|
|
689
|
+
uuid: session.minecraft.uuid,
|
|
690
|
+
accessToken: session.minecraft.accessToken,
|
|
691
|
+
userType: "msa",
|
|
692
|
+
clientId: session.microsoft.clientId,
|
|
693
|
+
xuid: session.minecraft.xuid
|
|
694
|
+
};
|
|
695
|
+
};
|
|
696
|
+
var resolveClientId = (explicit) => {
|
|
697
|
+
if (typeof explicit === "string" && explicit.trim().length > 0) return explicit.trim();
|
|
698
|
+
const fromEnv = process.env[CLIENT_ID_ENV_VAR];
|
|
699
|
+
if (typeof fromEnv === "string" && fromEnv.trim().length > 0) return fromEnv.trim();
|
|
700
|
+
throw new MinecraftKitError(
|
|
701
|
+
MinecraftKitErrorCodes.AUTH_MISSING_CLIENT_ID,
|
|
702
|
+
`No Azure AD client id supplied. Pass \`clientId\` explicitly or set ${CLIENT_ID_ENV_VAR}. Register an Azure AD application in the 'Personal Microsoft accounts' audience with XboxLive.signin + offline_access scopes to obtain one.`
|
|
703
|
+
);
|
|
704
|
+
};
|
|
44
705
|
|
|
45
706
|
// src/constants/platform.ts
|
|
46
707
|
var NODE_PLATFORM_TO_MOJANG_OS = {
|
|
@@ -59,39 +720,8 @@ var RUNTIME_PLATFORM_KEYS = {
|
|
|
59
720
|
linux: { x64: "linux", x86: "linux-i386", arm64: "linux" }
|
|
60
721
|
};
|
|
61
722
|
|
|
62
|
-
// src/core/errors.ts
|
|
63
|
-
var MinecraftKitError = class extends Error {
|
|
64
|
-
name = "MinecraftKitError";
|
|
65
|
-
/** Stable discriminator. */
|
|
66
|
-
code;
|
|
67
|
-
/** Structured context; safe to serialize. */
|
|
68
|
-
context;
|
|
69
|
-
constructor(code, message, options = {}) {
|
|
70
|
-
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
71
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
72
|
-
this.code = code;
|
|
73
|
-
this.context = Object.freeze({ ...options.context ?? {} });
|
|
74
|
-
}
|
|
75
|
-
/** JSON-friendly representation. */
|
|
76
|
-
toJSON() {
|
|
77
|
-
return {
|
|
78
|
-
name: this.name,
|
|
79
|
-
code: this.code,
|
|
80
|
-
message: this.message,
|
|
81
|
-
context: this.context,
|
|
82
|
-
cause: this.cause instanceof Error ? { name: this.cause.name, message: this.cause.message } : this.cause
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
function isMinecraftKitError(e) {
|
|
87
|
-
return e instanceof MinecraftKitError;
|
|
88
|
-
}
|
|
89
|
-
function isErrorCode(e, code) {
|
|
90
|
-
return isMinecraftKitError(e) && e.code === code;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
723
|
// src/core/system.ts
|
|
94
|
-
|
|
724
|
+
var detectSystem = (input = {}) => {
|
|
95
725
|
const platform = input.platform ?? process.platform;
|
|
96
726
|
const arch = input.arch ?? process.arch;
|
|
97
727
|
const osVersion = input.osVersion ?? os.release();
|
|
@@ -99,13 +729,13 @@ function detectSystem(input = {}) {
|
|
|
99
729
|
const mojangArch = NODE_ARCH_TO_MOJANG_ARCH[arch];
|
|
100
730
|
if (mojangOs === void 0 || mojangArch === void 0) {
|
|
101
731
|
throw new MinecraftKitError(
|
|
102
|
-
|
|
732
|
+
MinecraftKitErrorCodes.RUNTIME_UNSUPPORTED_PLATFORM,
|
|
103
733
|
`Unsupported platform/arch combination: ${platform}/${arch}`,
|
|
104
734
|
{ context: { platform, arch: String(arch) } }
|
|
105
735
|
);
|
|
106
736
|
}
|
|
107
737
|
return { os: mojangOs, arch: mojangArch, osVersion };
|
|
108
|
-
}
|
|
738
|
+
};
|
|
109
739
|
|
|
110
740
|
// src/constants/defaults.ts
|
|
111
741
|
var HTTP_TIMEOUT_MS = 3e4;
|
|
@@ -126,7 +756,7 @@ var MAX_PROCESSOR_STDERR_LINES = 20;
|
|
|
126
756
|
var SPAWNER_MAX_LINE_BYTES = 64 * 1024;
|
|
127
757
|
|
|
128
758
|
// src/http/cache.ts
|
|
129
|
-
|
|
759
|
+
var createMemoryCache = (options = {}) => {
|
|
130
760
|
const cache = new LRUCache({
|
|
131
761
|
max: options.maxEntries ?? CACHE_MAX_ENTRIES,
|
|
132
762
|
ttl: options.ttlMs ?? CACHE_TTL_MS
|
|
@@ -151,7 +781,7 @@ function createMemoryCache(options = {}) {
|
|
|
151
781
|
cache.clear();
|
|
152
782
|
}
|
|
153
783
|
};
|
|
154
|
-
}
|
|
784
|
+
};
|
|
155
785
|
|
|
156
786
|
// src/http/client.ts
|
|
157
787
|
var TIMEOUT_REASON = /* @__PURE__ */ Symbol("http-timeout");
|
|
@@ -169,39 +799,60 @@ var FetchHttpClient = class {
|
|
|
169
799
|
}
|
|
170
800
|
const timer = setTimeout(() => controller.abort(TIMEOUT_REASON), timeoutMs);
|
|
171
801
|
let response;
|
|
802
|
+
const method = options.method ?? "GET";
|
|
172
803
|
try {
|
|
173
|
-
|
|
174
|
-
method
|
|
804
|
+
const init = {
|
|
805
|
+
method,
|
|
175
806
|
headers: { "user-agent": USER_AGENT, ...options.headers ?? {} },
|
|
176
807
|
signal: controller.signal,
|
|
177
808
|
redirect: "follow"
|
|
178
|
-
}
|
|
809
|
+
};
|
|
810
|
+
if (method !== "GET" && options.body !== void 0) {
|
|
811
|
+
init.body = options.body;
|
|
812
|
+
}
|
|
813
|
+
response = await fetch(url, init);
|
|
179
814
|
} catch (cause) {
|
|
180
815
|
clearTimeout(timer);
|
|
181
816
|
options.signal?.removeEventListener("abort", onParentAbort);
|
|
182
817
|
if (controller.signal.reason === TIMEOUT_REASON) {
|
|
183
|
-
throw new MinecraftKitError(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
818
|
+
throw new MinecraftKitError(
|
|
819
|
+
MinecraftKitErrorCodes.NETWORK_TIMEOUT,
|
|
820
|
+
`Request timed out: ${url}`,
|
|
821
|
+
{
|
|
822
|
+
cause,
|
|
823
|
+
context: { url, timeoutMs }
|
|
824
|
+
}
|
|
825
|
+
);
|
|
187
826
|
}
|
|
188
827
|
if (options.signal?.aborted) {
|
|
189
|
-
throw new MinecraftKitError(
|
|
828
|
+
throw new MinecraftKitError(
|
|
829
|
+
MinecraftKitErrorCodes.NETWORK_ABORTED,
|
|
830
|
+
`Request aborted: ${url}`,
|
|
831
|
+
{
|
|
832
|
+
cause,
|
|
833
|
+
context: { url }
|
|
834
|
+
}
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
throw new MinecraftKitError(
|
|
838
|
+
MinecraftKitErrorCodes.NETWORK_HTTP_ERROR,
|
|
839
|
+
`Network request failed: ${url}`,
|
|
840
|
+
{
|
|
190
841
|
cause,
|
|
191
842
|
context: { url }
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
throw new MinecraftKitError("NETWORK_HTTP_ERROR", `Network request failed: ${url}`, {
|
|
195
|
-
cause,
|
|
196
|
-
context: { url }
|
|
197
|
-
});
|
|
843
|
+
}
|
|
844
|
+
);
|
|
198
845
|
}
|
|
199
846
|
clearTimeout(timer);
|
|
200
847
|
options.signal?.removeEventListener("abort", onParentAbort);
|
|
201
|
-
if (!response.ok) {
|
|
202
|
-
throw new MinecraftKitError(
|
|
203
|
-
|
|
204
|
-
|
|
848
|
+
if (!response.ok && options.acceptNonOk !== true) {
|
|
849
|
+
throw new MinecraftKitError(
|
|
850
|
+
MinecraftKitErrorCodes.NETWORK_HTTP_ERROR,
|
|
851
|
+
`HTTP ${response.status} for ${url}`,
|
|
852
|
+
{
|
|
853
|
+
context: { url, httpStatus: response.status }
|
|
854
|
+
}
|
|
855
|
+
);
|
|
205
856
|
}
|
|
206
857
|
return new FetchHttpResponse(response, url);
|
|
207
858
|
}
|
|
@@ -325,6 +976,17 @@ var InstallActionKinds = {
|
|
|
325
976
|
WRITE_VERSION_JSON: "write-version-json",
|
|
326
977
|
WRITE_LOGGING_CONFIG: "write-logging-config"
|
|
327
978
|
};
|
|
979
|
+
var DownloadCategories = {
|
|
980
|
+
CLIENT_JAR: "client-jar",
|
|
981
|
+
LIBRARY: "library",
|
|
982
|
+
ASSET_INDEX: "asset-index",
|
|
983
|
+
ASSET: "asset",
|
|
984
|
+
LOGGING_CONFIG: "logging-config",
|
|
985
|
+
FABRIC_LIBRARY: "fabric-library",
|
|
986
|
+
FORGE_LIBRARY: "forge-library",
|
|
987
|
+
RUNTIME_FILE: "runtime-file",
|
|
988
|
+
FORGE_INSTALLER: "forge-installer"
|
|
989
|
+
};
|
|
328
990
|
|
|
329
991
|
// src/types/loader.ts
|
|
330
992
|
var Loaders = {
|
|
@@ -378,7 +1040,7 @@ var ApiEndpoints = {
|
|
|
378
1040
|
};
|
|
379
1041
|
|
|
380
1042
|
// src/http/metadata.ts
|
|
381
|
-
async
|
|
1043
|
+
var fetchJson = async (http, cache, input) => {
|
|
382
1044
|
const key = input.cacheKey ?? `json:${input.url}`;
|
|
383
1045
|
const cached = cache.get(key);
|
|
384
1046
|
if (cached !== void 0) {
|
|
@@ -390,8 +1052,8 @@ async function fetchJson(http, cache, input) {
|
|
|
390
1052
|
const value = await response.json();
|
|
391
1053
|
cache.set(key, value, input.ttlMs ?? CACHE_TTL_MS);
|
|
392
1054
|
return value;
|
|
393
|
-
}
|
|
394
|
-
async
|
|
1055
|
+
};
|
|
1056
|
+
var fetchText = async (http, cache, input) => {
|
|
395
1057
|
const key = input.cacheKey ?? `text:${input.url}`;
|
|
396
1058
|
const cached = cache.get(key);
|
|
397
1059
|
if (cached !== void 0) {
|
|
@@ -403,10 +1065,10 @@ async function fetchText(http, cache, input) {
|
|
|
403
1065
|
const text = await response.text();
|
|
404
1066
|
cache.set(key, text, input.ttlMs ?? CACHE_TTL_MS);
|
|
405
1067
|
return text;
|
|
406
|
-
}
|
|
1068
|
+
};
|
|
407
1069
|
|
|
408
1070
|
// src/install/assets.ts
|
|
409
|
-
async
|
|
1071
|
+
var planAssetDownloads = async (input) => {
|
|
410
1072
|
const indexUrl = input.assetIndex.url;
|
|
411
1073
|
const indexPath = targetPaths.assetIndex(input.directory, input.assetIndex.id);
|
|
412
1074
|
const indexDocument = await fetchJson(input.http, input.cache, {
|
|
@@ -421,7 +1083,7 @@ async function planAssetDownloads(input) {
|
|
|
421
1083
|
target: indexPath,
|
|
422
1084
|
expectedSha1: input.assetIndex.sha1,
|
|
423
1085
|
expectedSize: input.assetIndex.size,
|
|
424
|
-
category:
|
|
1086
|
+
category: DownloadCategories.ASSET_INDEX
|
|
425
1087
|
}
|
|
426
1088
|
];
|
|
427
1089
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -434,11 +1096,11 @@ async function planAssetDownloads(input) {
|
|
|
434
1096
|
target: targetPaths.assetObject(input.directory, entry.hash),
|
|
435
1097
|
expectedSha1: entry.hash,
|
|
436
1098
|
expectedSize: entry.size,
|
|
437
|
-
category:
|
|
1099
|
+
category: DownloadCategories.ASSET
|
|
438
1100
|
});
|
|
439
1101
|
}
|
|
440
1102
|
return { actions, indexDocument };
|
|
441
|
-
}
|
|
1103
|
+
};
|
|
442
1104
|
|
|
443
1105
|
// src/constants/maven.ts
|
|
444
1106
|
var DEFAULT_LIBRARY_REPOSITORY = "https://libraries.minecraft.net/";
|
|
@@ -446,21 +1108,25 @@ var FABRIC_MAVEN_BASE = "https://maven.fabricmc.net/";
|
|
|
446
1108
|
var FORGE_MAVEN_BASE = "https://maven.minecraftforge.net/";
|
|
447
1109
|
|
|
448
1110
|
// src/core/maven.ts
|
|
449
|
-
|
|
1111
|
+
var parseMavenCoordinate = (input) => {
|
|
450
1112
|
const trimmed = input.startsWith("[") && input.endsWith("]") ? input.slice(1, -1) : input;
|
|
451
1113
|
const atIndex = trimmed.indexOf("@");
|
|
452
1114
|
const extension = atIndex === -1 ? "jar" : trimmed.slice(atIndex + 1);
|
|
453
1115
|
const body = atIndex === -1 ? trimmed : trimmed.slice(0, atIndex);
|
|
454
1116
|
const parts = body.split(":");
|
|
455
1117
|
if (parts.length < 3 || parts.length > 4) {
|
|
456
|
-
throw new MinecraftKitError(
|
|
457
|
-
|
|
458
|
-
|
|
1118
|
+
throw new MinecraftKitError(
|
|
1119
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
1120
|
+
`Invalid Maven coordinate: ${input}`,
|
|
1121
|
+
{
|
|
1122
|
+
context: { input }
|
|
1123
|
+
}
|
|
1124
|
+
);
|
|
459
1125
|
}
|
|
460
1126
|
const [group, artifact, version, classifier] = parts;
|
|
461
1127
|
if (!group || !artifact || !version) {
|
|
462
1128
|
throw new MinecraftKitError(
|
|
463
|
-
|
|
1129
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
464
1130
|
`Invalid Maven coordinate (missing component): ${input}`,
|
|
465
1131
|
{ context: { input } }
|
|
466
1132
|
);
|
|
@@ -469,19 +1135,19 @@ function parseMavenCoordinate(input) {
|
|
|
469
1135
|
return { group, artifact, version, extension };
|
|
470
1136
|
}
|
|
471
1137
|
return { group, artifact, version, classifier, extension };
|
|
472
|
-
}
|
|
473
|
-
|
|
1138
|
+
};
|
|
1139
|
+
var mavenRelativePath = (coord) => {
|
|
474
1140
|
const groupPath = coord.group.replaceAll(".", "/");
|
|
475
1141
|
const classifierSegment = coord.classifier === void 0 ? "" : `-${coord.classifier}`;
|
|
476
1142
|
const filename = `${coord.artifact}-${coord.version}${classifierSegment}.${coord.extension}`;
|
|
477
1143
|
return `${groupPath}/${coord.artifact}/${coord.version}/${filename}`;
|
|
478
|
-
}
|
|
479
|
-
|
|
1144
|
+
};
|
|
1145
|
+
var mavenRelativePathFor = (input) => {
|
|
480
1146
|
return mavenRelativePath(parseMavenCoordinate(input));
|
|
481
|
-
}
|
|
1147
|
+
};
|
|
482
1148
|
|
|
483
1149
|
// src/core/rules.ts
|
|
484
|
-
|
|
1150
|
+
var evaluateRules = (rules, context) => {
|
|
485
1151
|
if (!rules || rules.length === 0) {
|
|
486
1152
|
return true;
|
|
487
1153
|
}
|
|
@@ -492,8 +1158,8 @@ function evaluateRules(rules, context) {
|
|
|
492
1158
|
}
|
|
493
1159
|
}
|
|
494
1160
|
return allowed;
|
|
495
|
-
}
|
|
496
|
-
|
|
1161
|
+
};
|
|
1162
|
+
var matchesRule = (rule, context) => {
|
|
497
1163
|
if (rule.os !== void 0) {
|
|
498
1164
|
if (rule.os.name !== void 0 && rule.os.name !== context.system.os) {
|
|
499
1165
|
return false;
|
|
@@ -521,21 +1187,21 @@ function matchesRule(rule, context) {
|
|
|
521
1187
|
}
|
|
522
1188
|
}
|
|
523
1189
|
return true;
|
|
524
|
-
}
|
|
525
|
-
|
|
1190
|
+
};
|
|
1191
|
+
var normalizeArch = (arch) => {
|
|
526
1192
|
return arch === "ia32" ? "x86" : arch;
|
|
527
|
-
}
|
|
528
|
-
|
|
1193
|
+
};
|
|
1194
|
+
var resolveArchPlaceholder = (template, archDigit2) => {
|
|
529
1195
|
return template.replaceAll("${arch}", archDigit2);
|
|
530
|
-
}
|
|
531
|
-
|
|
1196
|
+
};
|
|
1197
|
+
var archDigit = (arch) => {
|
|
532
1198
|
if (arch === "x86") return "32";
|
|
533
1199
|
if (arch === "x64") return "64";
|
|
534
1200
|
return "64";
|
|
535
|
-
}
|
|
1201
|
+
};
|
|
536
1202
|
|
|
537
1203
|
// src/install/libraries.ts
|
|
538
|
-
|
|
1204
|
+
var planLibraryDownloads = (input) => {
|
|
539
1205
|
const downloads = [];
|
|
540
1206
|
const nativeExtractions = [];
|
|
541
1207
|
const classpathFiles = [];
|
|
@@ -589,8 +1255,8 @@ function planLibraryDownloads(input) {
|
|
|
589
1255
|
}
|
|
590
1256
|
}
|
|
591
1257
|
return { downloads, nativeExtractions, classpathFiles };
|
|
592
|
-
}
|
|
593
|
-
|
|
1258
|
+
};
|
|
1259
|
+
var pickPrimaryArtifact = (library) => {
|
|
594
1260
|
if (library.downloads?.artifact) {
|
|
595
1261
|
return artifactFromDownload(library.downloads.artifact);
|
|
596
1262
|
}
|
|
@@ -601,8 +1267,8 @@ function pickPrimaryArtifact(library) {
|
|
|
601
1267
|
return mavenArtifactFromCoord(library.name, DEFAULT_LIBRARY_REPOSITORY);
|
|
602
1268
|
}
|
|
603
1269
|
return null;
|
|
604
|
-
}
|
|
605
|
-
|
|
1270
|
+
};
|
|
1271
|
+
var pickNative = (library, system) => {
|
|
606
1272
|
if (!library.natives) return null;
|
|
607
1273
|
const classifierTemplate = library.natives[system.os];
|
|
608
1274
|
if (!classifierTemplate) return null;
|
|
@@ -617,22 +1283,26 @@ function pickNative(library, system) {
|
|
|
617
1283
|
return mavenArtifactFromCoord(withClassifier, library.url ?? DEFAULT_LIBRARY_REPOSITORY);
|
|
618
1284
|
}
|
|
619
1285
|
return null;
|
|
620
|
-
}
|
|
621
|
-
|
|
1286
|
+
};
|
|
1287
|
+
var artifactFromDownload = (artifact) => {
|
|
622
1288
|
return {
|
|
623
1289
|
relativePath: artifact.path,
|
|
624
1290
|
url: artifact.url,
|
|
625
1291
|
sha1: artifact.sha1,
|
|
626
1292
|
size: artifact.size
|
|
627
1293
|
};
|
|
628
|
-
}
|
|
629
|
-
|
|
1294
|
+
};
|
|
1295
|
+
var mavenArtifactFromCoord = (coord, baseUrl) => {
|
|
630
1296
|
const relativePath = mavenRelativePathFor(coord);
|
|
631
1297
|
const normalizedBase = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
632
1298
|
if (!relativePath) {
|
|
633
|
-
throw new MinecraftKitError(
|
|
634
|
-
|
|
635
|
-
|
|
1299
|
+
throw new MinecraftKitError(
|
|
1300
|
+
MinecraftKitErrorCodes.MANIFEST_INVALID,
|
|
1301
|
+
`Invalid library coordinate: ${coord}`,
|
|
1302
|
+
{
|
|
1303
|
+
context: { input: coord }
|
|
1304
|
+
}
|
|
1305
|
+
);
|
|
636
1306
|
}
|
|
637
1307
|
return {
|
|
638
1308
|
relativePath,
|
|
@@ -640,10 +1310,10 @@ function mavenArtifactFromCoord(coord, baseUrl) {
|
|
|
640
1310
|
sha1: void 0,
|
|
641
1311
|
size: void 0
|
|
642
1312
|
};
|
|
643
|
-
}
|
|
1313
|
+
};
|
|
644
1314
|
|
|
645
1315
|
// src/install/fabric-install.ts
|
|
646
|
-
|
|
1316
|
+
var planFabricInstall = (input) => {
|
|
647
1317
|
const versionId = input.loader.profile.id;
|
|
648
1318
|
const versionJsonPath = targetPaths.versionJson(input.directory, versionId);
|
|
649
1319
|
const versionJson = {
|
|
@@ -657,7 +1327,7 @@ function planFabricInstall(input) {
|
|
|
657
1327
|
directory: input.directory,
|
|
658
1328
|
system: input.system,
|
|
659
1329
|
versionId: input.minecraft.version,
|
|
660
|
-
category:
|
|
1330
|
+
category: DownloadCategories.FABRIC_LIBRARY
|
|
661
1331
|
});
|
|
662
1332
|
return {
|
|
663
1333
|
versionJson,
|
|
@@ -665,7 +1335,7 @@ function planFabricInstall(input) {
|
|
|
665
1335
|
classpathFiles: plan.classpathFiles,
|
|
666
1336
|
versionId
|
|
667
1337
|
};
|
|
668
|
-
}
|
|
1338
|
+
};
|
|
669
1339
|
|
|
670
1340
|
// src/constants/limits.ts
|
|
671
1341
|
var EXTRACTION_MAX_FILE_SIZE = 256 * 1024 * 1024;
|
|
@@ -673,42 +1343,42 @@ var EXTRACTION_MAX_TOTAL_SIZE = 2 * 1024 * 1024 * 1024;
|
|
|
673
1343
|
var EXTRACTION_MAX_COMPRESSION_RATIO = 200;
|
|
674
1344
|
var EXTRACTION_MAX_ENTRY_COUNT = 1e5;
|
|
675
1345
|
var FORGE_INSTALLER_MAX_SIZE = 256 * 1024 * 1024;
|
|
676
|
-
async
|
|
1346
|
+
var ensureDir = async (directory) => {
|
|
677
1347
|
try {
|
|
678
1348
|
await fs.mkdir(directory, { recursive: true });
|
|
679
1349
|
} catch (cause) {
|
|
680
1350
|
throw new MinecraftKitError(
|
|
681
|
-
|
|
1351
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
682
1352
|
`Failed to create directory: ${directory}`,
|
|
683
1353
|
{ cause, context: { filePath: directory } }
|
|
684
1354
|
);
|
|
685
1355
|
}
|
|
686
|
-
}
|
|
687
|
-
async
|
|
1356
|
+
};
|
|
1357
|
+
var fileExists = async (filePath) => {
|
|
688
1358
|
try {
|
|
689
1359
|
const stat = await fs.stat(filePath);
|
|
690
1360
|
return stat.isFile();
|
|
691
1361
|
} catch {
|
|
692
1362
|
return false;
|
|
693
1363
|
}
|
|
694
|
-
}
|
|
695
|
-
async
|
|
1364
|
+
};
|
|
1365
|
+
var dirExists = async (filePath) => {
|
|
696
1366
|
try {
|
|
697
1367
|
const stat = await fs.stat(filePath);
|
|
698
1368
|
return stat.isDirectory();
|
|
699
1369
|
} catch {
|
|
700
1370
|
return false;
|
|
701
1371
|
}
|
|
702
|
-
}
|
|
703
|
-
async
|
|
1372
|
+
};
|
|
1373
|
+
var fileSize = async (filePath) => {
|
|
704
1374
|
try {
|
|
705
1375
|
const stat = await fs.stat(filePath);
|
|
706
1376
|
return stat.size;
|
|
707
1377
|
} catch {
|
|
708
1378
|
return -1;
|
|
709
1379
|
}
|
|
710
|
-
}
|
|
711
|
-
async
|
|
1380
|
+
};
|
|
1381
|
+
var atomicWrite = async (target, data) => {
|
|
712
1382
|
await ensureDir(path.dirname(target));
|
|
713
1383
|
const tmp = `${target}.${crypto2.randomBytes(4).toString("hex")}.tmp`;
|
|
714
1384
|
try {
|
|
@@ -723,31 +1393,39 @@ async function atomicWrite(target, data) {
|
|
|
723
1393
|
await fs.unlink(tmp);
|
|
724
1394
|
} catch {
|
|
725
1395
|
}
|
|
726
|
-
throw new MinecraftKitError(
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1396
|
+
throw new MinecraftKitError(
|
|
1397
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
1398
|
+
`Failed to write file: ${target}`,
|
|
1399
|
+
{
|
|
1400
|
+
cause,
|
|
1401
|
+
context: { filePath: target }
|
|
1402
|
+
}
|
|
1403
|
+
);
|
|
730
1404
|
}
|
|
731
|
-
}
|
|
732
|
-
async
|
|
1405
|
+
};
|
|
1406
|
+
var readText = async (filePath) => {
|
|
733
1407
|
try {
|
|
734
1408
|
return await fs.readFile(filePath, "utf8");
|
|
735
1409
|
} catch (cause) {
|
|
736
|
-
throw new MinecraftKitError(
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1410
|
+
throw new MinecraftKitError(
|
|
1411
|
+
MinecraftKitErrorCodes.FILESYSTEM_READ_ERROR,
|
|
1412
|
+
`Failed to read file: ${filePath}`,
|
|
1413
|
+
{
|
|
1414
|
+
cause,
|
|
1415
|
+
context: { filePath }
|
|
1416
|
+
}
|
|
1417
|
+
);
|
|
740
1418
|
}
|
|
741
|
-
}
|
|
742
|
-
async
|
|
1419
|
+
};
|
|
1420
|
+
var listChildDirectories = async (directory) => {
|
|
743
1421
|
try {
|
|
744
1422
|
const entries = await fs.readdir(directory, { withFileTypes: true });
|
|
745
1423
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
746
1424
|
} catch {
|
|
747
1425
|
return [];
|
|
748
1426
|
}
|
|
749
|
-
}
|
|
750
|
-
async
|
|
1427
|
+
};
|
|
1428
|
+
var chmodExecutable = async (filePath) => {
|
|
751
1429
|
if (process.platform === "win32") {
|
|
752
1430
|
return;
|
|
753
1431
|
}
|
|
@@ -755,35 +1433,43 @@ async function chmodExecutable(filePath) {
|
|
|
755
1433
|
await fs.chmod(filePath, 493);
|
|
756
1434
|
} catch {
|
|
757
1435
|
}
|
|
758
|
-
}
|
|
759
|
-
|
|
1436
|
+
};
|
|
1437
|
+
var assertWithinRoot = (root, child) => {
|
|
760
1438
|
const normalizedRoot = path.resolve(root);
|
|
761
1439
|
const normalizedChild = path.resolve(root, child);
|
|
762
1440
|
const sep = path.sep;
|
|
763
1441
|
if (normalizedChild !== normalizedRoot && !normalizedChild.startsWith(normalizedRoot + sep)) {
|
|
764
|
-
throw new MinecraftKitError(
|
|
765
|
-
|
|
766
|
-
|
|
1442
|
+
throw new MinecraftKitError(
|
|
1443
|
+
MinecraftKitErrorCodes.FILESYSTEM_PATH_TRAVERSAL,
|
|
1444
|
+
`Path escapes root: ${child}`,
|
|
1445
|
+
{
|
|
1446
|
+
context: { filePath: child, rootDirectory: root }
|
|
1447
|
+
}
|
|
1448
|
+
);
|
|
767
1449
|
}
|
|
768
|
-
}
|
|
1450
|
+
};
|
|
769
1451
|
|
|
770
1452
|
// src/core/archive.ts
|
|
771
|
-
|
|
1453
|
+
var openZip = (filePath) => {
|
|
772
1454
|
return new Promise((resolve, reject) => {
|
|
773
1455
|
yauzl.open(filePath, { lazyEntries: true, autoClose: false }, (err, zipFile) => {
|
|
774
1456
|
if (err || !zipFile) {
|
|
775
1457
|
reject(
|
|
776
|
-
new MinecraftKitError(
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1458
|
+
new MinecraftKitError(
|
|
1459
|
+
MinecraftKitErrorCodes.ARCHIVE_INVALID,
|
|
1460
|
+
`Failed to open archive: ${filePath}`,
|
|
1461
|
+
{
|
|
1462
|
+
cause: err,
|
|
1463
|
+
context: { filePath }
|
|
1464
|
+
}
|
|
1465
|
+
)
|
|
780
1466
|
);
|
|
781
1467
|
return;
|
|
782
1468
|
}
|
|
783
1469
|
resolve(new ZipReader(zipFile, filePath));
|
|
784
1470
|
});
|
|
785
1471
|
});
|
|
786
|
-
}
|
|
1472
|
+
};
|
|
787
1473
|
var ZipReader = class {
|
|
788
1474
|
constructor(file, filePath) {
|
|
789
1475
|
this.file = file;
|
|
@@ -801,7 +1487,7 @@ var ZipReader = class {
|
|
|
801
1487
|
count++;
|
|
802
1488
|
if (count > EXTRACTION_MAX_ENTRY_COUNT) {
|
|
803
1489
|
throw new MinecraftKitError(
|
|
804
|
-
|
|
1490
|
+
MinecraftKitErrorCodes.ARCHIVE_TOO_LARGE,
|
|
805
1491
|
`Archive contains too many entries: ${this.filePath}`,
|
|
806
1492
|
{ context: { filePath: this.filePath } }
|
|
807
1493
|
);
|
|
@@ -833,10 +1519,14 @@ var ZipReader = class {
|
|
|
833
1519
|
const onError = (err) => {
|
|
834
1520
|
cleanup();
|
|
835
1521
|
reject(
|
|
836
|
-
new MinecraftKitError(
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1522
|
+
new MinecraftKitError(
|
|
1523
|
+
MinecraftKitErrorCodes.ARCHIVE_INVALID,
|
|
1524
|
+
"Failed to read archive entry",
|
|
1525
|
+
{
|
|
1526
|
+
cause: err,
|
|
1527
|
+
context: { filePath: this.filePath }
|
|
1528
|
+
}
|
|
1529
|
+
)
|
|
840
1530
|
);
|
|
841
1531
|
};
|
|
842
1532
|
const cleanup = () => {
|
|
@@ -861,14 +1551,14 @@ var ZipReader = class {
|
|
|
861
1551
|
readBuffer: async () => {
|
|
862
1552
|
if (entry.uncompressedSize > EXTRACTION_MAX_FILE_SIZE) {
|
|
863
1553
|
throw new MinecraftKitError(
|
|
864
|
-
|
|
1554
|
+
MinecraftKitErrorCodes.ARCHIVE_TOO_LARGE,
|
|
865
1555
|
`Archive entry exceeds size cap: ${name}`,
|
|
866
1556
|
{ context: { filePath: this.filePath, entryName: name, size: entry.uncompressedSize } }
|
|
867
1557
|
);
|
|
868
1558
|
}
|
|
869
1559
|
if (entry.compressedSize > 0 && entry.uncompressedSize / entry.compressedSize > EXTRACTION_MAX_COMPRESSION_RATIO) {
|
|
870
1560
|
throw new MinecraftKitError(
|
|
871
|
-
|
|
1561
|
+
MinecraftKitErrorCodes.ARCHIVE_TOO_LARGE,
|
|
872
1562
|
`Archive entry exceeds compression-ratio cap: ${name}`,
|
|
873
1563
|
{ context: { filePath: this.filePath, entryName: name } }
|
|
874
1564
|
);
|
|
@@ -884,13 +1574,13 @@ var ZipReader = class {
|
|
|
884
1574
|
};
|
|
885
1575
|
}
|
|
886
1576
|
};
|
|
887
|
-
|
|
1577
|
+
var openStream = (file, entry, archivePath) => {
|
|
888
1578
|
return new Promise((resolve, reject) => {
|
|
889
1579
|
file.openReadStream(entry, (err, stream) => {
|
|
890
1580
|
if (err || !stream) {
|
|
891
1581
|
reject(
|
|
892
1582
|
new MinecraftKitError(
|
|
893
|
-
|
|
1583
|
+
MinecraftKitErrorCodes.ARCHIVE_INVALID,
|
|
894
1584
|
`Failed to open archive entry: ${entry.fileName}`,
|
|
895
1585
|
{ cause: err, context: { filePath: archivePath, entryName: entry.fileName } }
|
|
896
1586
|
)
|
|
@@ -900,8 +1590,8 @@ function openStream(file, entry, archivePath) {
|
|
|
900
1590
|
resolve(stream);
|
|
901
1591
|
});
|
|
902
1592
|
});
|
|
903
|
-
}
|
|
904
|
-
async
|
|
1593
|
+
};
|
|
1594
|
+
var extractAllToDir = async (zipPath, targetDir, options = {}) => {
|
|
905
1595
|
const exclude = options.excludePrefixes ?? ["META-INF/"];
|
|
906
1596
|
let fileCount = 0;
|
|
907
1597
|
let totalSize = 0;
|
|
@@ -917,14 +1607,14 @@ async function extractAllToDir(zipPath, targetDir, options = {}) {
|
|
|
917
1607
|
totalSize += entry.uncompressedSize;
|
|
918
1608
|
if (totalSize > EXTRACTION_MAX_TOTAL_SIZE) {
|
|
919
1609
|
throw new MinecraftKitError(
|
|
920
|
-
|
|
1610
|
+
MinecraftKitErrorCodes.ARCHIVE_TOO_LARGE,
|
|
921
1611
|
`Archive total size cap exceeded: ${zipPath}`,
|
|
922
1612
|
{ context: { filePath: zipPath } }
|
|
923
1613
|
);
|
|
924
1614
|
}
|
|
925
1615
|
if (entry.uncompressedSize > EXTRACTION_MAX_FILE_SIZE) {
|
|
926
1616
|
throw new MinecraftKitError(
|
|
927
|
-
|
|
1617
|
+
MinecraftKitErrorCodes.ARCHIVE_TOO_LARGE,
|
|
928
1618
|
`Archive entry exceeds size cap: ${entry.name}`,
|
|
929
1619
|
{ context: { filePath: zipPath, entryName: entry.name } }
|
|
930
1620
|
);
|
|
@@ -941,9 +1631,9 @@ async function extractAllToDir(zipPath, targetDir, options = {}) {
|
|
|
941
1631
|
reader.close();
|
|
942
1632
|
}
|
|
943
1633
|
return { fileCount };
|
|
944
|
-
}
|
|
1634
|
+
};
|
|
945
1635
|
var RESERVED_NAME = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\..*)?$/i;
|
|
946
|
-
|
|
1636
|
+
var assertSafeEntryName = (name) => {
|
|
947
1637
|
if (!name) {
|
|
948
1638
|
throw rejectEntry(name, "empty entry name");
|
|
949
1639
|
}
|
|
@@ -965,15 +1655,15 @@ function assertSafeEntryName(name) {
|
|
|
965
1655
|
throw rejectEntry(name, "trailing dot or whitespace");
|
|
966
1656
|
}
|
|
967
1657
|
}
|
|
968
|
-
}
|
|
969
|
-
|
|
1658
|
+
};
|
|
1659
|
+
var rejectEntry = (name, reason) => {
|
|
970
1660
|
return new MinecraftKitError(
|
|
971
|
-
|
|
1661
|
+
MinecraftKitErrorCodes.ARCHIVE_ENTRY_REJECTED,
|
|
972
1662
|
`Archive entry rejected (${reason}): ${name}`,
|
|
973
1663
|
{ context: { entryName: name, reason } }
|
|
974
1664
|
);
|
|
975
|
-
}
|
|
976
|
-
async
|
|
1665
|
+
};
|
|
1666
|
+
var readEntryBuffer = async (zipPath, entryName) => {
|
|
977
1667
|
const reader = await openZip(zipPath);
|
|
978
1668
|
try {
|
|
979
1669
|
const entry = await reader.findEntry(entryName);
|
|
@@ -982,19 +1672,23 @@ async function readEntryBuffer(zipPath, entryName) {
|
|
|
982
1672
|
} finally {
|
|
983
1673
|
reader.close();
|
|
984
1674
|
}
|
|
985
|
-
}
|
|
986
|
-
async
|
|
1675
|
+
};
|
|
1676
|
+
var extractSingleEntry = async (zipPath, entryName, destination) => {
|
|
987
1677
|
const buffer = await readEntryBuffer(zipPath, entryName);
|
|
988
1678
|
if (!buffer) {
|
|
989
|
-
throw new MinecraftKitError(
|
|
990
|
-
|
|
991
|
-
|
|
1679
|
+
throw new MinecraftKitError(
|
|
1680
|
+
MinecraftKitErrorCodes.ARCHIVE_INVALID,
|
|
1681
|
+
`Archive entry not found: ${entryName}`,
|
|
1682
|
+
{
|
|
1683
|
+
context: { filePath: zipPath, entryName }
|
|
1684
|
+
}
|
|
1685
|
+
);
|
|
992
1686
|
}
|
|
993
1687
|
await atomicWrite(destination, buffer);
|
|
994
|
-
}
|
|
1688
|
+
};
|
|
995
1689
|
var MANIFEST_LINE_CONTINUATION = /\r?\n[ \t]/g;
|
|
996
1690
|
var MANIFEST_MAIN_CLASS = /^Main-Class:\s*(.+)$/i;
|
|
997
|
-
async
|
|
1691
|
+
var readJarMainClass = async (zipPath) => {
|
|
998
1692
|
const buf = await readEntryBuffer(zipPath, "META-INF/MANIFEST.MF");
|
|
999
1693
|
if (!buf) return void 0;
|
|
1000
1694
|
const text = buf.toString("utf8").replaceAll(MANIFEST_LINE_CONTINUATION, "");
|
|
@@ -1003,10 +1697,10 @@ async function readJarMainClass(zipPath) {
|
|
|
1003
1697
|
if (match?.[1]) return match[1].trim();
|
|
1004
1698
|
}
|
|
1005
1699
|
return void 0;
|
|
1006
|
-
}
|
|
1700
|
+
};
|
|
1007
1701
|
|
|
1008
1702
|
// src/core/collections.ts
|
|
1009
|
-
|
|
1703
|
+
var dedupeBy = (values, key) => {
|
|
1010
1704
|
const seen = /* @__PURE__ */ new Set();
|
|
1011
1705
|
const result = [];
|
|
1012
1706
|
for (const value of values) {
|
|
@@ -1016,13 +1710,25 @@ function dedupeBy(values, key) {
|
|
|
1016
1710
|
result.push(value);
|
|
1017
1711
|
}
|
|
1018
1712
|
return result;
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1713
|
+
};
|
|
1714
|
+
var dedupe = (values) => {
|
|
1021
1715
|
return dedupeBy(values, (v) => v);
|
|
1022
|
-
}
|
|
1716
|
+
};
|
|
1717
|
+
|
|
1718
|
+
// src/core/abort.ts
|
|
1719
|
+
var assertNotAborted = (signal, message) => {
|
|
1720
|
+
if (signal?.aborted) {
|
|
1721
|
+
throw new MinecraftKitError(MinecraftKitErrorCodes.LAUNCH_ABORTED, message);
|
|
1722
|
+
}
|
|
1723
|
+
};
|
|
1724
|
+
var checkpoint = async (sources, message = "Operation aborted by signal") => {
|
|
1725
|
+
assertNotAborted(sources.signal, message);
|
|
1726
|
+
await sources.pauseController?.waitWhilePaused();
|
|
1727
|
+
assertNotAborted(sources.signal, message);
|
|
1728
|
+
};
|
|
1023
1729
|
|
|
1024
1730
|
// src/core/retry.ts
|
|
1025
|
-
|
|
1731
|
+
var abortableSleep = (ms, signal) => {
|
|
1026
1732
|
return new Promise((resolve, reject) => {
|
|
1027
1733
|
if (signal?.aborted) {
|
|
1028
1734
|
reject(toAbortError(signal));
|
|
@@ -1038,13 +1744,13 @@ function abortableSleep(ms, signal) {
|
|
|
1038
1744
|
};
|
|
1039
1745
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1040
1746
|
});
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
return new MinecraftKitError(
|
|
1747
|
+
};
|
|
1748
|
+
var toAbortError = (signal) => {
|
|
1749
|
+
return new MinecraftKitError(MinecraftKitErrorCodes.NETWORK_ABORTED, "Operation aborted", {
|
|
1044
1750
|
context: { reason: signal?.reason }
|
|
1045
1751
|
});
|
|
1046
|
-
}
|
|
1047
|
-
async
|
|
1752
|
+
};
|
|
1753
|
+
var withRetry = async (op, isRetryable, options = {}) => {
|
|
1048
1754
|
const max = options.maxAttempts ?? HTTP_RETRY_MAX;
|
|
1049
1755
|
const base = options.baseMs ?? HTTP_RETRY_BACKOFF_BASE_MS;
|
|
1050
1756
|
const cap = options.capMs ?? HTTP_RETRY_BACKOFF_CAP_MS;
|
|
@@ -1069,8 +1775,8 @@ async function withRetry(op, isRetryable, options = {}) {
|
|
|
1069
1775
|
}
|
|
1070
1776
|
}
|
|
1071
1777
|
throw lastError ?? new Error("withRetry exhausted attempts");
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1778
|
+
};
|
|
1779
|
+
var isHttpRetryable = (error) => {
|
|
1074
1780
|
if (!isMinecraftKitError(error)) {
|
|
1075
1781
|
return false;
|
|
1076
1782
|
}
|
|
@@ -1083,15 +1789,45 @@ function isHttpRetryable(error) {
|
|
|
1083
1789
|
return status === 0;
|
|
1084
1790
|
}
|
|
1085
1791
|
return false;
|
|
1086
|
-
}
|
|
1792
|
+
};
|
|
1793
|
+
|
|
1794
|
+
// src/types/events.ts
|
|
1795
|
+
var EventTypes = {
|
|
1796
|
+
INSTALL_PHASE_CHANGED: "install:phase-changed",
|
|
1797
|
+
DOWNLOAD_STARTED: "download:started",
|
|
1798
|
+
DOWNLOAD_PROGRESS: "download:progress",
|
|
1799
|
+
DOWNLOAD_SKIPPED: "download:skipped",
|
|
1800
|
+
DOWNLOAD_COMPLETED: "download:completed",
|
|
1801
|
+
DOWNLOAD_FAILED: "download:failed",
|
|
1802
|
+
INTEGRITY_VERIFIED: "integrity:verified",
|
|
1803
|
+
INTEGRITY_MISMATCH: "integrity:mismatch",
|
|
1804
|
+
ARCHIVE_EXTRACTED: "archive:extracted",
|
|
1805
|
+
FORGE_PROCESSOR_STARTED: "forge:processor-started",
|
|
1806
|
+
FORGE_PROCESSOR_COMPLETED: "forge:processor-completed",
|
|
1807
|
+
FORGE_PROCESSOR_OUTPUT_VERIFIED: "forge:processor-output-verified",
|
|
1808
|
+
VERIFY_FILE_CHECKED: "verify:file-checked",
|
|
1809
|
+
VERIFY_COMPLETED: "verify:completed",
|
|
1810
|
+
REPAIR_PHASE_CHANGED: "repair:phase-changed",
|
|
1811
|
+
LAUNCH_STARTING: "launch:starting",
|
|
1812
|
+
LAUNCH_STARTED: "launch:started",
|
|
1813
|
+
LAUNCH_STDOUT: "launch:stdout",
|
|
1814
|
+
LAUNCH_STDERR: "launch:stderr",
|
|
1815
|
+
LAUNCH_EXITED: "launch:exited",
|
|
1816
|
+
LAUNCH_ABORTED: "launch:aborted"
|
|
1817
|
+
};
|
|
1087
1818
|
|
|
1088
1819
|
// src/http/download.ts
|
|
1089
|
-
async
|
|
1090
|
-
|
|
1820
|
+
var downloadFile = async (http, input) => {
|
|
1821
|
+
assertSafeDownloadUrl(input.url, input.hostAllowList);
|
|
1822
|
+
const fileRef = {
|
|
1823
|
+
url: input.url,
|
|
1824
|
+
target: input.target,
|
|
1825
|
+
...input.category !== void 0 ? { category: input.category } : {}
|
|
1826
|
+
};
|
|
1091
1827
|
if (input.expectedSha1 !== void 0) {
|
|
1092
1828
|
const existing = await checkExistingFile(input.target, input.expectedSha1, input.expectedSize);
|
|
1093
1829
|
if (existing.matches) {
|
|
1094
|
-
input.onEvent?.({ type:
|
|
1830
|
+
input.onEvent?.({ type: EventTypes.DOWNLOAD_SKIPPED, file: fileRef });
|
|
1095
1831
|
return { bytesDownloaded: 0, sha1: existing.sha1, skipped: true };
|
|
1096
1832
|
}
|
|
1097
1833
|
}
|
|
@@ -1100,29 +1836,32 @@ async function downloadFile(http, input) {
|
|
|
1100
1836
|
return withRetry(
|
|
1101
1837
|
async () => {
|
|
1102
1838
|
input.onEvent?.({
|
|
1103
|
-
type:
|
|
1839
|
+
type: EventTypes.DOWNLOAD_STARTED,
|
|
1104
1840
|
file: fileRef,
|
|
1105
1841
|
expectedSize: input.expectedSize ?? 0
|
|
1106
1842
|
});
|
|
1107
1843
|
const startedAt = Date.now();
|
|
1108
1844
|
let bytesDownloaded = 0;
|
|
1109
1845
|
const hash = crypto2.createHash("sha1");
|
|
1110
|
-
const response = await http.request(input.url, {
|
|
1846
|
+
const response = await http.request(input.url, {
|
|
1847
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
1848
|
+
});
|
|
1111
1849
|
const contentLength = Number(response.headers["content-length"] ?? "0");
|
|
1112
1850
|
const total = input.expectedSize ?? (Number.isFinite(contentLength) ? contentLength : 0);
|
|
1113
1851
|
const sourceIterable = response.stream();
|
|
1114
1852
|
const counting = (async function* () {
|
|
1115
1853
|
for await (const chunk of sourceIterable) {
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1854
|
+
await checkpoint(
|
|
1855
|
+
{
|
|
1856
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1857
|
+
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1858
|
+
},
|
|
1859
|
+
"Download aborted by signal"
|
|
1860
|
+
);
|
|
1122
1861
|
bytesDownloaded += chunk.byteLength;
|
|
1123
1862
|
hash.update(chunk);
|
|
1124
1863
|
input.onEvent?.({
|
|
1125
|
-
type:
|
|
1864
|
+
type: EventTypes.DOWNLOAD_PROGRESS,
|
|
1126
1865
|
file: fileRef,
|
|
1127
1866
|
bytesDownloaded,
|
|
1128
1867
|
totalBytes: total
|
|
@@ -1135,7 +1874,7 @@ async function downloadFile(http, input) {
|
|
|
1135
1874
|
} catch (cause) {
|
|
1136
1875
|
await safeUnlink(tmp);
|
|
1137
1876
|
throw new MinecraftKitError(
|
|
1138
|
-
|
|
1877
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
1139
1878
|
`Failed to write download: ${input.target}`,
|
|
1140
1879
|
{ cause, context: { filePath: input.target, url: input.url } }
|
|
1141
1880
|
);
|
|
@@ -1143,50 +1882,58 @@ async function downloadFile(http, input) {
|
|
|
1143
1882
|
const computedSha1 = hash.digest("hex");
|
|
1144
1883
|
if (input.expectedSize !== void 0 && bytesDownloaded !== input.expectedSize) {
|
|
1145
1884
|
await safeUnlink(tmp);
|
|
1146
|
-
throw new MinecraftKitError(
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1885
|
+
throw new MinecraftKitError(
|
|
1886
|
+
MinecraftKitErrorCodes.INTEGRITY_SIZE_MISMATCH,
|
|
1887
|
+
`Size mismatch for ${input.url}`,
|
|
1888
|
+
{
|
|
1889
|
+
context: {
|
|
1890
|
+
url: input.url,
|
|
1891
|
+
expectedSize: input.expectedSize,
|
|
1892
|
+
actualSize: bytesDownloaded
|
|
1893
|
+
}
|
|
1151
1894
|
}
|
|
1152
|
-
|
|
1895
|
+
);
|
|
1153
1896
|
}
|
|
1154
1897
|
if (input.expectedSha1 !== void 0 && computedSha1 !== input.expectedSha1) {
|
|
1155
1898
|
await safeUnlink(tmp);
|
|
1156
1899
|
input.onEvent?.({
|
|
1157
|
-
type:
|
|
1900
|
+
type: EventTypes.INTEGRITY_MISMATCH,
|
|
1158
1901
|
file: fileRef,
|
|
1159
1902
|
algorithm: "sha1",
|
|
1160
1903
|
expected: input.expectedSha1,
|
|
1161
1904
|
actual: computedSha1
|
|
1162
1905
|
});
|
|
1163
|
-
throw new MinecraftKitError(
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1906
|
+
throw new MinecraftKitError(
|
|
1907
|
+
MinecraftKitErrorCodes.INTEGRITY_HASH_MISMATCH,
|
|
1908
|
+
`SHA-1 mismatch for ${input.url}`,
|
|
1909
|
+
{
|
|
1910
|
+
context: {
|
|
1911
|
+
url: input.url,
|
|
1912
|
+
expectedHash: input.expectedSha1,
|
|
1913
|
+
actualHash: computedSha1
|
|
1914
|
+
}
|
|
1168
1915
|
}
|
|
1169
|
-
|
|
1916
|
+
);
|
|
1170
1917
|
}
|
|
1171
1918
|
try {
|
|
1172
1919
|
await fs.rename(tmp, input.target);
|
|
1173
1920
|
} catch (cause) {
|
|
1174
1921
|
await safeUnlink(tmp);
|
|
1175
1922
|
throw new MinecraftKitError(
|
|
1176
|
-
|
|
1923
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
1177
1924
|
`Failed to finalize download: ${input.target}`,
|
|
1178
1925
|
{ cause, context: { filePath: input.target } }
|
|
1179
1926
|
);
|
|
1180
1927
|
}
|
|
1181
1928
|
input.onEvent?.({
|
|
1182
|
-
type:
|
|
1929
|
+
type: EventTypes.DOWNLOAD_COMPLETED,
|
|
1183
1930
|
file: fileRef,
|
|
1184
1931
|
durationMs: Date.now() - startedAt,
|
|
1185
1932
|
bytes: bytesDownloaded
|
|
1186
1933
|
});
|
|
1187
1934
|
if (input.expectedSha1 !== void 0) {
|
|
1188
1935
|
input.onEvent?.({
|
|
1189
|
-
type:
|
|
1936
|
+
type: EventTypes.INTEGRITY_VERIFIED,
|
|
1190
1937
|
file: fileRef,
|
|
1191
1938
|
algorithm: "sha1",
|
|
1192
1939
|
hash: computedSha1
|
|
@@ -1199,7 +1946,7 @@ async function downloadFile(http, input) {
|
|
|
1199
1946
|
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1200
1947
|
onAttemptFailed: (error, attempt) => {
|
|
1201
1948
|
input.onEvent?.({
|
|
1202
|
-
type:
|
|
1949
|
+
type: EventTypes.DOWNLOAD_FAILED,
|
|
1203
1950
|
file: fileRef,
|
|
1204
1951
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
1205
1952
|
willRetry: isHttpRetryable(error) && attempt < HTTP_RETRY_MAX - 1
|
|
@@ -1207,8 +1954,8 @@ async function downloadFile(http, input) {
|
|
|
1207
1954
|
}
|
|
1208
1955
|
}
|
|
1209
1956
|
);
|
|
1210
|
-
}
|
|
1211
|
-
async
|
|
1957
|
+
};
|
|
1958
|
+
var checkExistingFile = async (target, expectedSha1, expectedSize) => {
|
|
1212
1959
|
let stat;
|
|
1213
1960
|
try {
|
|
1214
1961
|
stat = await fs.stat(target);
|
|
@@ -1224,21 +1971,60 @@ async function checkExistingFile(target, expectedSha1, expectedSize) {
|
|
|
1224
1971
|
const buf = await fs.readFile(target);
|
|
1225
1972
|
const sha1 = crypto2.createHash("sha1").update(buf).digest("hex");
|
|
1226
1973
|
return { matches: sha1 === expectedSha1, sha1 };
|
|
1227
|
-
}
|
|
1228
|
-
async
|
|
1974
|
+
};
|
|
1975
|
+
var safeUnlink = async (filePath) => {
|
|
1229
1976
|
try {
|
|
1230
1977
|
await fs.unlink(filePath);
|
|
1231
1978
|
} catch {
|
|
1232
1979
|
}
|
|
1233
|
-
}
|
|
1980
|
+
};
|
|
1981
|
+
var assertSafeDownloadUrl = (url, allowList) => {
|
|
1982
|
+
let parsed;
|
|
1983
|
+
try {
|
|
1984
|
+
parsed = new URL(url);
|
|
1985
|
+
} catch {
|
|
1986
|
+
throw new MinecraftKitError(
|
|
1987
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
1988
|
+
`Download URL is not parseable: ${url}`,
|
|
1989
|
+
{
|
|
1990
|
+
context: { url }
|
|
1991
|
+
}
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
1995
|
+
throw new MinecraftKitError(
|
|
1996
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
1997
|
+
`Download URL must use http(s); refusing scheme ${parsed.protocol}`,
|
|
1998
|
+
{ context: { url, scheme: parsed.protocol } }
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
if (allowList !== void 0 && !matchesHostAllowList(parsed.hostname, allowList)) {
|
|
2002
|
+
throw new MinecraftKitError(
|
|
2003
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2004
|
+
`Download URL host is not in the allow-list: ${parsed.hostname}`,
|
|
2005
|
+
{ context: { url, host: parsed.hostname } }
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
var matchesHostAllowList = (hostname, allowList) => {
|
|
2010
|
+
const host = hostname.toLowerCase();
|
|
2011
|
+
return allowList.some((entry) => matchesHostEntry(host, entry.toLowerCase()));
|
|
2012
|
+
};
|
|
2013
|
+
var matchesHostEntry = (host, entry) => {
|
|
2014
|
+
if (entry.startsWith("*.")) {
|
|
2015
|
+
const suffix = entry.slice(1);
|
|
2016
|
+
return host === entry.slice(2) || host.endsWith(suffix);
|
|
2017
|
+
}
|
|
2018
|
+
return host === entry;
|
|
2019
|
+
};
|
|
1234
2020
|
|
|
1235
2021
|
// src/install/forge-install.ts
|
|
1236
|
-
async
|
|
2022
|
+
var planForgeInstall = async (input) => {
|
|
1237
2023
|
const installerPath = targetPaths.forgeInstaller(input.directory, input.loader.fullVersion);
|
|
1238
2024
|
await downloadFile(input.http, {
|
|
1239
2025
|
url: input.loader.installerUrl,
|
|
1240
2026
|
target: installerPath,
|
|
1241
|
-
category:
|
|
2027
|
+
category: DownloadCategories.FORGE_INSTALLER,
|
|
1242
2028
|
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1243
2029
|
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
1244
2030
|
});
|
|
@@ -1246,7 +2032,7 @@ async function planForgeInstall(input) {
|
|
|
1246
2032
|
kind: InstallActionKinds.DOWNLOAD_FILE,
|
|
1247
2033
|
url: input.loader.installerUrl,
|
|
1248
2034
|
target: installerPath,
|
|
1249
|
-
category:
|
|
2035
|
+
category: DownloadCategories.FORGE_INSTALLER
|
|
1250
2036
|
};
|
|
1251
2037
|
const profile = await readJsonEntry(installerPath, "install_profile.json");
|
|
1252
2038
|
const versionRelative = profile.json.startsWith("/") ? profile.json.slice(1) : profile.json;
|
|
@@ -1262,14 +2048,14 @@ async function planForgeInstall(input) {
|
|
|
1262
2048
|
directory: input.directory,
|
|
1263
2049
|
system: input.system,
|
|
1264
2050
|
versionId: input.minecraft.version,
|
|
1265
|
-
category:
|
|
2051
|
+
category: DownloadCategories.FORGE_LIBRARY
|
|
1266
2052
|
});
|
|
1267
2053
|
const versionLibraries = planLibraryDownloads({
|
|
1268
2054
|
libraries: version.libraries,
|
|
1269
2055
|
directory: input.directory,
|
|
1270
2056
|
system: input.system,
|
|
1271
2057
|
versionId: version.id,
|
|
1272
|
-
category:
|
|
2058
|
+
category: DownloadCategories.FORGE_LIBRARY
|
|
1273
2059
|
});
|
|
1274
2060
|
const dedupedDownloads = dedupeBy(
|
|
1275
2061
|
[...installerLibraries.downloads, ...versionLibraries.downloads],
|
|
@@ -1304,27 +2090,23 @@ async function planForgeInstall(input) {
|
|
|
1304
2090
|
profile,
|
|
1305
2091
|
version
|
|
1306
2092
|
};
|
|
1307
|
-
}
|
|
1308
|
-
async
|
|
2093
|
+
};
|
|
2094
|
+
var readJsonEntry = async (zipPath, entryName) => {
|
|
1309
2095
|
const buffer = await readEntryBuffer(zipPath, entryName);
|
|
1310
2096
|
if (!buffer) {
|
|
1311
2097
|
throw new MinecraftKitError(
|
|
1312
|
-
|
|
2098
|
+
MinecraftKitErrorCodes.FORGE_INSTALLER_INVALID,
|
|
1313
2099
|
`Forge installer is missing required entry: ${entryName}`,
|
|
1314
2100
|
{ context: { filePath: zipPath, entryName } }
|
|
1315
2101
|
);
|
|
1316
2102
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
async function extractInstallerMavenEntries(installerPath, directory) {
|
|
2103
|
+
return parseJsonStrict(buffer.toString("utf8"), {
|
|
2104
|
+
code: MinecraftKitErrorCodes.FORGE_INSTALLER_INVALID,
|
|
2105
|
+
message: `Forge installer entry is not valid JSON: ${entryName}`,
|
|
2106
|
+
context: { filePath: zipPath, entryName }
|
|
2107
|
+
});
|
|
2108
|
+
};
|
|
2109
|
+
var extractInstallerMavenEntries = async (installerPath, directory) => {
|
|
1328
2110
|
const reader = await openZip(installerPath);
|
|
1329
2111
|
try {
|
|
1330
2112
|
for await (const entry of reader.entries()) {
|
|
@@ -1337,16 +2119,16 @@ async function extractInstallerMavenEntries(installerPath, directory) {
|
|
|
1337
2119
|
} finally {
|
|
1338
2120
|
reader.close();
|
|
1339
2121
|
}
|
|
1340
|
-
}
|
|
1341
|
-
async
|
|
2122
|
+
};
|
|
2123
|
+
var resolveProfileData = async (input) => {
|
|
1342
2124
|
const tokens = {};
|
|
1343
2125
|
for (const [key, sided] of Object.entries(input.profile.data)) {
|
|
1344
2126
|
const raw = sided.client;
|
|
1345
2127
|
tokens[key] = await resolveDataValue(raw, input.installerPath, input.directory);
|
|
1346
2128
|
}
|
|
1347
2129
|
return { tokens };
|
|
1348
|
-
}
|
|
1349
|
-
async
|
|
2130
|
+
};
|
|
2131
|
+
var resolveDataValue = async (raw, installerPath, directory) => {
|
|
1350
2132
|
if (raw.startsWith("[") && raw.endsWith("]")) {
|
|
1351
2133
|
const coord = raw.slice(1, -1);
|
|
1352
2134
|
const relativePath = mavenRelativePathFor(coord);
|
|
@@ -1365,8 +2147,8 @@ async function resolveDataValue(raw, installerPath, directory) {
|
|
|
1365
2147
|
return { value: destination, isPath: true };
|
|
1366
2148
|
}
|
|
1367
2149
|
return { value: raw, isPath: false };
|
|
1368
|
-
}
|
|
1369
|
-
async
|
|
2150
|
+
};
|
|
2151
|
+
var buildProcessorActions = async (input) => {
|
|
1370
2152
|
const builtIns = {
|
|
1371
2153
|
SIDE: { value: "client", isPath: false },
|
|
1372
2154
|
MINECRAFT_JAR: {
|
|
@@ -1399,12 +2181,12 @@ async function buildProcessorActions(input) {
|
|
|
1399
2181
|
index++;
|
|
1400
2182
|
}
|
|
1401
2183
|
return actions;
|
|
1402
|
-
}
|
|
1403
|
-
|
|
2184
|
+
};
|
|
2185
|
+
var processorAppliesToClient = (processor) => {
|
|
1404
2186
|
if (!processor.sides || processor.sides.length === 0) return true;
|
|
1405
2187
|
return processor.sides.includes("client");
|
|
1406
|
-
}
|
|
1407
|
-
|
|
2188
|
+
};
|
|
2189
|
+
var buildProcessorAction = (input) => {
|
|
1408
2190
|
const jarPath = path.join(
|
|
1409
2191
|
targetPaths.librariesDir(input.directory),
|
|
1410
2192
|
mavenRelativePathFor(input.processor.jar)
|
|
@@ -1431,26 +2213,30 @@ function buildProcessorAction(input) {
|
|
|
1431
2213
|
args,
|
|
1432
2214
|
outputs
|
|
1433
2215
|
};
|
|
1434
|
-
}
|
|
1435
|
-
|
|
2216
|
+
};
|
|
2217
|
+
var substituteToken = (raw, tokens) => {
|
|
1436
2218
|
if (raw.startsWith("[") && raw.endsWith("]")) {
|
|
1437
2219
|
return path.join(...mavenRelativePathFor(raw.slice(1, -1)).split("/"));
|
|
1438
2220
|
}
|
|
1439
2221
|
return raw.replaceAll(/\{([A-Z0-9_]+)\}/g, (match, key) => {
|
|
1440
2222
|
const token = tokens[key];
|
|
1441
2223
|
if (token === void 0) {
|
|
1442
|
-
throw new MinecraftKitError(
|
|
1443
|
-
|
|
1444
|
-
|
|
2224
|
+
throw new MinecraftKitError(
|
|
2225
|
+
MinecraftKitErrorCodes.FORGE_INSTALLER_INVALID,
|
|
2226
|
+
`Unknown processor token: ${match}`,
|
|
2227
|
+
{
|
|
2228
|
+
context: { token: key }
|
|
2229
|
+
}
|
|
2230
|
+
);
|
|
1445
2231
|
}
|
|
1446
2232
|
return token.value;
|
|
1447
2233
|
});
|
|
1448
|
-
}
|
|
1449
|
-
|
|
2234
|
+
};
|
|
2235
|
+
var stripLiteralPrefix = (value) => {
|
|
1450
2236
|
const stripped = value.startsWith("'") ? value.slice(1) : value;
|
|
1451
2237
|
return stripped.endsWith("'") ? stripped.slice(0, -1) : stripped;
|
|
1452
|
-
}
|
|
1453
|
-
async
|
|
2238
|
+
};
|
|
2239
|
+
var planRuntimeDownloads = async (input) => {
|
|
1454
2240
|
const manifest = await fetchJson(input.http, input.cache, {
|
|
1455
2241
|
url: input.runtime.manifestUrl,
|
|
1456
2242
|
cacheKey: `runtime-manifest:${input.runtime.component}:${input.runtime.platformKey}:${input.runtime.manifestSha1}`,
|
|
@@ -1471,14 +2257,14 @@ async function planRuntimeDownloads(input) {
|
|
|
1471
2257
|
target,
|
|
1472
2258
|
expectedSha1: entry.downloads.raw.sha1,
|
|
1473
2259
|
expectedSize: entry.downloads.raw.size,
|
|
1474
|
-
category:
|
|
2260
|
+
category: DownloadCategories.RUNTIME_FILE
|
|
1475
2261
|
});
|
|
1476
2262
|
}
|
|
1477
2263
|
return { actions, manifest };
|
|
1478
|
-
}
|
|
2264
|
+
};
|
|
1479
2265
|
|
|
1480
2266
|
// src/install/planner.ts
|
|
1481
|
-
async
|
|
2267
|
+
var planInstall = async (input) => {
|
|
1482
2268
|
const { target } = input;
|
|
1483
2269
|
const actions = [];
|
|
1484
2270
|
actions.push({
|
|
@@ -1487,7 +2273,7 @@ async function planInstall(input) {
|
|
|
1487
2273
|
target: targetPaths.versionJar(target.directory, target.minecraft.version),
|
|
1488
2274
|
expectedSha1: target.minecraft.manifest.downloads.client.sha1,
|
|
1489
2275
|
expectedSize: target.minecraft.manifest.downloads.client.size,
|
|
1490
|
-
category:
|
|
2276
|
+
category: DownloadCategories.CLIENT_JAR
|
|
1491
2277
|
});
|
|
1492
2278
|
actions.push({
|
|
1493
2279
|
kind: InstallActionKinds.WRITE_VERSION_JSON,
|
|
@@ -1500,7 +2286,7 @@ async function planInstall(input) {
|
|
|
1500
2286
|
directory: target.directory,
|
|
1501
2287
|
system: target.runtime.system,
|
|
1502
2288
|
versionId: target.minecraft.version,
|
|
1503
|
-
category:
|
|
2289
|
+
category: DownloadCategories.LIBRARY
|
|
1504
2290
|
});
|
|
1505
2291
|
actions.push(...vanillaLibraries.downloads);
|
|
1506
2292
|
actions.push(...vanillaLibraries.nativeExtractions);
|
|
@@ -1520,7 +2306,7 @@ async function planInstall(input) {
|
|
|
1520
2306
|
target: targetPaths.loggingConfig(target.directory, logging.file.id),
|
|
1521
2307
|
expectedSha1: logging.file.sha1,
|
|
1522
2308
|
expectedSize: logging.file.size,
|
|
1523
|
-
category:
|
|
2309
|
+
category: DownloadCategories.LOGGING_CONFIG
|
|
1524
2310
|
});
|
|
1525
2311
|
}
|
|
1526
2312
|
const runtimePlan = await planRuntimeDownloads({
|
|
@@ -1570,7 +2356,7 @@ async function planInstall(input) {
|
|
|
1570
2356
|
totalActions: actions.length,
|
|
1571
2357
|
totalBytes
|
|
1572
2358
|
};
|
|
1573
|
-
}
|
|
2359
|
+
};
|
|
1574
2360
|
|
|
1575
2361
|
// node_modules/yocto-queue/index.js
|
|
1576
2362
|
var Node = class {
|
|
@@ -1673,22 +2459,126 @@ function pLimit(concurrency) {
|
|
|
1673
2459
|
const generator = (function_, ...arguments_) => new Promise((resolve) => {
|
|
1674
2460
|
enqueue(function_, resolve, arguments_);
|
|
1675
2461
|
});
|
|
1676
|
-
Object.defineProperties(generator, {
|
|
1677
|
-
activeCount: {
|
|
1678
|
-
get: () => activeCount
|
|
1679
|
-
},
|
|
1680
|
-
pendingCount: {
|
|
1681
|
-
get: () => queue.size
|
|
1682
|
-
},
|
|
1683
|
-
clearQueue: {
|
|
1684
|
-
value() {
|
|
1685
|
-
queue.clear();
|
|
1686
|
-
}
|
|
2462
|
+
Object.defineProperties(generator, {
|
|
2463
|
+
activeCount: {
|
|
2464
|
+
get: () => activeCount
|
|
2465
|
+
},
|
|
2466
|
+
pendingCount: {
|
|
2467
|
+
get: () => queue.size
|
|
2468
|
+
},
|
|
2469
|
+
clearQueue: {
|
|
2470
|
+
value() {
|
|
2471
|
+
queue.clear();
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
});
|
|
2475
|
+
return generator;
|
|
2476
|
+
}
|
|
2477
|
+
var sha1OfFile = async (filePath) => {
|
|
2478
|
+
const hash = crypto2.createHash("sha1");
|
|
2479
|
+
const stream = createReadStream(filePath);
|
|
2480
|
+
try {
|
|
2481
|
+
await new Promise((resolve, reject) => {
|
|
2482
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
2483
|
+
stream.on("end", resolve);
|
|
2484
|
+
stream.on("error", reject);
|
|
2485
|
+
});
|
|
2486
|
+
} finally {
|
|
2487
|
+
if (!stream.destroyed) stream.destroy();
|
|
2488
|
+
}
|
|
2489
|
+
return hash.digest("hex");
|
|
2490
|
+
};
|
|
2491
|
+
|
|
2492
|
+
// src/install/processor.ts
|
|
2493
|
+
var runProcessor = async (input) => {
|
|
2494
|
+
const startedAt = Date.now();
|
|
2495
|
+
const mainClass = await resolveProcessorMainClass(input.action);
|
|
2496
|
+
emit(input, { type: EventTypes.FORGE_PROCESSOR_STARTED, index: input.action.index, mainClass });
|
|
2497
|
+
const exit = await spawnProcessor(input, mainClass);
|
|
2498
|
+
if (exit.code !== 0) {
|
|
2499
|
+
throw new MinecraftKitError(
|
|
2500
|
+
MinecraftKitErrorCodes.FORGE_PROCESSOR_FAILED,
|
|
2501
|
+
`Forge processor exited with code ${exit.code ?? "(signal)"}: ${mainClass}`,
|
|
2502
|
+
{
|
|
2503
|
+
context: {
|
|
2504
|
+
mainClass,
|
|
2505
|
+
stderr: exit.stderr,
|
|
2506
|
+
...exit.code !== null ? { exitCode: exit.code } : {}
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
);
|
|
2510
|
+
}
|
|
2511
|
+
input.onEvent?.({
|
|
2512
|
+
type: EventTypes.FORGE_PROCESSOR_COMPLETED,
|
|
2513
|
+
processor: { index: input.action.index, mainClass },
|
|
2514
|
+
exitCode: exit.code ?? 0,
|
|
2515
|
+
durationMs: Date.now() - startedAt
|
|
2516
|
+
});
|
|
2517
|
+
await verifyProcessorOutputs(input, mainClass);
|
|
2518
|
+
};
|
|
2519
|
+
var resolveProcessorMainClass = async (action) => {
|
|
2520
|
+
const processorJar = action.classpath[0];
|
|
2521
|
+
if (processorJar === void 0) {
|
|
2522
|
+
throw new MinecraftKitError(
|
|
2523
|
+
MinecraftKitErrorCodes.FORGE_INSTALLER_INVALID,
|
|
2524
|
+
"Forge processor has an empty classpath",
|
|
2525
|
+
{ context: { processorIndex: action.index } }
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
const mainClass = await readJarMainClass(processorJar);
|
|
2529
|
+
if (!mainClass) {
|
|
2530
|
+
throw new MinecraftKitError(
|
|
2531
|
+
MinecraftKitErrorCodes.FORGE_INSTALLER_INVALID,
|
|
2532
|
+
`Forge processor jar has no Main-Class: ${processorJar}`,
|
|
2533
|
+
{ context: { filePath: processorJar } }
|
|
2534
|
+
);
|
|
2535
|
+
}
|
|
2536
|
+
return mainClass;
|
|
2537
|
+
};
|
|
2538
|
+
var spawnProcessor = async (input, mainClass) => {
|
|
2539
|
+
const classpathSeparator = process.platform === "win32" ? ";" : ":";
|
|
2540
|
+
const args = [
|
|
2541
|
+
"-cp",
|
|
2542
|
+
input.action.classpath.join(classpathSeparator),
|
|
2543
|
+
mainClass,
|
|
2544
|
+
...input.action.args
|
|
2545
|
+
];
|
|
2546
|
+
const stderrTail = [];
|
|
2547
|
+
const child = input.spawner.spawn(input.javaPath, args, { cwd: process.cwd() });
|
|
2548
|
+
child.stdout.on("data", () => {
|
|
2549
|
+
});
|
|
2550
|
+
child.stderr.on("data", (line) => {
|
|
2551
|
+
if (stderrTail.length >= MAX_PROCESSOR_STDERR_LINES) stderrTail.shift();
|
|
2552
|
+
stderrTail.push(line);
|
|
2553
|
+
});
|
|
2554
|
+
const exit = await child.exited;
|
|
2555
|
+
return { code: exit.code, stderr: stderrTail.join("\n") };
|
|
2556
|
+
};
|
|
2557
|
+
var verifyProcessorOutputs = async (input, mainClass) => {
|
|
2558
|
+
for (const [outputPath, expectedSha1] of Object.entries(input.action.outputs)) {
|
|
2559
|
+
const sha1 = await sha1OfFile(outputPath);
|
|
2560
|
+
if (sha1 !== expectedSha1) {
|
|
2561
|
+
throw new MinecraftKitError(
|
|
2562
|
+
MinecraftKitErrorCodes.FORGE_PROCESSOR_FAILED,
|
|
2563
|
+
`Processor output hash mismatch: ${outputPath}`,
|
|
2564
|
+
{ context: { filePath: outputPath, expectedHash: expectedSha1, actualHash: sha1 } }
|
|
2565
|
+
);
|
|
1687
2566
|
}
|
|
2567
|
+
input.onEvent?.({
|
|
2568
|
+
type: EventTypes.FORGE_PROCESSOR_OUTPUT_VERIFIED,
|
|
2569
|
+
processor: { index: input.action.index, mainClass },
|
|
2570
|
+
path: outputPath
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2574
|
+
var emit = (input, event) => {
|
|
2575
|
+
input.onEvent?.({
|
|
2576
|
+
type: event.type,
|
|
2577
|
+
processor: { index: event.index, mainClass: event.mainClass },
|
|
2578
|
+
total: input.total
|
|
1688
2579
|
});
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
async function materializeRuntimeExtras(input) {
|
|
2580
|
+
};
|
|
2581
|
+
var materializeRuntimeExtras = async (input) => {
|
|
1692
2582
|
const root = targetPaths.runtimeRoot(
|
|
1693
2583
|
input.directory,
|
|
1694
2584
|
input.runtime.component,
|
|
@@ -1707,20 +2597,20 @@ async function materializeRuntimeExtras(input) {
|
|
|
1707
2597
|
});
|
|
1708
2598
|
}
|
|
1709
2599
|
}
|
|
1710
|
-
}
|
|
1711
|
-
async
|
|
2600
|
+
};
|
|
2601
|
+
var unlinkIfPresent = async (target) => {
|
|
1712
2602
|
try {
|
|
1713
2603
|
await fs.unlink(target);
|
|
1714
2604
|
} catch (cause) {
|
|
1715
2605
|
if (isNotFound(cause)) return;
|
|
1716
2606
|
throw new MinecraftKitError(
|
|
1717
|
-
|
|
2607
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
1718
2608
|
`Failed to remove stale runtime entry: ${target}`,
|
|
1719
2609
|
{ cause, context: { filePath: target } }
|
|
1720
2610
|
);
|
|
1721
2611
|
}
|
|
1722
|
-
}
|
|
1723
|
-
async
|
|
2612
|
+
};
|
|
2613
|
+
var createLinkOrCopy = async (root, relativePath, linkTarget, destination) => {
|
|
1724
2614
|
try {
|
|
1725
2615
|
await fs.symlink(linkTarget, destination);
|
|
1726
2616
|
return;
|
|
@@ -1730,7 +2620,7 @@ async function createLinkOrCopy(root, relativePath, linkTarget, destination) {
|
|
|
1730
2620
|
await fs.copyFile(absoluteSource, destination);
|
|
1731
2621
|
} catch (copyError) {
|
|
1732
2622
|
throw new MinecraftKitError(
|
|
1733
|
-
|
|
2623
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
1734
2624
|
`Failed to materialize runtime entry: ${destination}`,
|
|
1735
2625
|
{
|
|
1736
2626
|
cause: copyError,
|
|
@@ -1743,283 +2633,209 @@ async function createLinkOrCopy(root, relativePath, linkTarget, destination) {
|
|
|
1743
2633
|
);
|
|
1744
2634
|
}
|
|
1745
2635
|
}
|
|
1746
|
-
}
|
|
1747
|
-
|
|
2636
|
+
};
|
|
2637
|
+
var isNotFound = (error) => {
|
|
1748
2638
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1749
|
-
}
|
|
1750
|
-
|
|
2639
|
+
};
|
|
2640
|
+
var errorMessage = (error) => {
|
|
1751
2641
|
return error instanceof Error ? error.message : String(error);
|
|
1752
|
-
}
|
|
2642
|
+
};
|
|
1753
2643
|
|
|
1754
2644
|
// src/install/runner.ts
|
|
1755
2645
|
var DOWNLOAD_GROUPS = [
|
|
1756
|
-
{ categories: [
|
|
1757
|
-
{ categories: [
|
|
1758
|
-
{ categories: [
|
|
1759
|
-
{ categories: [
|
|
1760
|
-
{ categories: [
|
|
1761
|
-
{ categories: [
|
|
1762
|
-
{ categories: [
|
|
1763
|
-
{
|
|
2646
|
+
{ categories: [DownloadCategories.RUNTIME_FILE], phase: InstallPhases.INSTALLING_RUNTIME },
|
|
2647
|
+
{ categories: [DownloadCategories.CLIENT_JAR], phase: InstallPhases.DOWNLOADING_CLIENT_JAR },
|
|
2648
|
+
{ categories: [DownloadCategories.LIBRARY], phase: InstallPhases.DOWNLOADING_LIBRARIES },
|
|
2649
|
+
{ categories: [DownloadCategories.ASSET_INDEX], phase: InstallPhases.DOWNLOADING_ASSET_INDEX },
|
|
2650
|
+
{ categories: [DownloadCategories.ASSET], phase: InstallPhases.DOWNLOADING_ASSETS },
|
|
2651
|
+
{ categories: [DownloadCategories.LOGGING_CONFIG], phase: InstallPhases.WRITING_FILES },
|
|
2652
|
+
{ categories: [DownloadCategories.FABRIC_LIBRARY], phase: InstallPhases.INSTALLING_FABRIC },
|
|
2653
|
+
{
|
|
2654
|
+
categories: [DownloadCategories.FORGE_INSTALLER, DownloadCategories.FORGE_LIBRARY],
|
|
2655
|
+
phase: InstallPhases.INSTALLING_FORGE
|
|
2656
|
+
}
|
|
1764
2657
|
];
|
|
1765
|
-
async
|
|
2658
|
+
var runInstall = async (input) => {
|
|
1766
2659
|
const startedAt = Date.now();
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
const
|
|
2660
|
+
const counters = { bytesDownloaded: 0, actionsCompleted: 0, actionsSkipped: 0 };
|
|
2661
|
+
const ctx = createContext(input, counters);
|
|
2662
|
+
ctx.enterPhase(InstallPhases.PLANNING);
|
|
2663
|
+
const plannedActions = partitionActions(input);
|
|
2664
|
+
await runDownloadsStage(ctx, plannedActions.downloads);
|
|
2665
|
+
await runWritesStage(ctx, plannedActions.writes);
|
|
2666
|
+
await runNativesStage(ctx, plannedActions.natives);
|
|
2667
|
+
await runRuntimeStage(ctx);
|
|
2668
|
+
await runProcessorsStage(ctx, plannedActions.processors);
|
|
2669
|
+
ctx.enterPhase(InstallPhases.COMPLETED);
|
|
2670
|
+
return {
|
|
2671
|
+
targetId: input.plan.targetId,
|
|
2672
|
+
bytesDownloaded: counters.bytesDownloaded,
|
|
2673
|
+
actionsCompleted: counters.actionsCompleted,
|
|
2674
|
+
actionsSkipped: counters.actionsSkipped,
|
|
2675
|
+
durationMs: Date.now() - startedAt
|
|
2676
|
+
};
|
|
2677
|
+
};
|
|
2678
|
+
var createContext = (input, counters) => {
|
|
1771
2679
|
let currentPhase = null;
|
|
1772
2680
|
const enterPhase = (phase) => {
|
|
1773
2681
|
if (phase === currentPhase) return;
|
|
1774
|
-
onEvent?.({ type:
|
|
2682
|
+
input.onEvent?.({ type: EventTypes.INSTALL_PHASE_CHANGED, phase, previous: currentPhase });
|
|
1775
2683
|
currentPhase = phase;
|
|
1776
2684
|
};
|
|
1777
|
-
const
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
2685
|
+
const checkpoint2 = () => checkpoint(
|
|
2686
|
+
{
|
|
2687
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
2688
|
+
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
2689
|
+
},
|
|
2690
|
+
"Install aborted by signal"
|
|
2691
|
+
);
|
|
2692
|
+
return {
|
|
2693
|
+
input,
|
|
2694
|
+
counters,
|
|
2695
|
+
checkpoint: checkpoint2,
|
|
2696
|
+
enterPhase,
|
|
2697
|
+
limit: pLimit(input.concurrency ?? DOWNLOAD_CONCURRENCY)
|
|
2698
|
+
};
|
|
2699
|
+
};
|
|
2700
|
+
var partitionActions = (input) => {
|
|
2701
|
+
const filter = input.actionCategories;
|
|
2702
|
+
return {
|
|
2703
|
+
downloads: input.plan.actions.filter(isDownload).filter((a) => filter ? filter.has(a.category) : true),
|
|
2704
|
+
natives: input.plan.actions.filter(isNative),
|
|
2705
|
+
writes: input.plan.actions.filter(isWrite),
|
|
2706
|
+
processors: input.plan.actions.filter(isProcessor)
|
|
1785
2707
|
};
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
const writeActions = input.plan.actions.filter(isWrite);
|
|
1790
|
-
const processors = input.plan.actions.filter(isProcessor);
|
|
1791
|
-
enterPhase(InstallPhases.PLANNING);
|
|
1792
|
-
const limit = pLimit(input.concurrency ?? DOWNLOAD_CONCURRENCY);
|
|
2708
|
+
};
|
|
2709
|
+
var runDownloadsStage = async (ctx, downloads) => {
|
|
2710
|
+
if (downloads.length === 0) return;
|
|
1793
2711
|
for (const group of DOWNLOAD_GROUPS) {
|
|
1794
2712
|
const groupActions = downloads.filter((action) => group.categories.includes(action.category));
|
|
1795
2713
|
if (groupActions.length === 0) continue;
|
|
1796
|
-
await checkpoint();
|
|
1797
|
-
enterPhase(group.phase);
|
|
1798
|
-
await
|
|
1799
|
-
groupActions.map(
|
|
1800
|
-
(action) => limit(async () => {
|
|
1801
|
-
await checkpoint();
|
|
1802
|
-
const result = await downloadFile(input.http, {
|
|
1803
|
-
url: action.url,
|
|
1804
|
-
target: action.target,
|
|
1805
|
-
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
1806
|
-
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
1807
|
-
...action.category !== void 0 ? { category: action.category } : {},
|
|
1808
|
-
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1809
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1810
|
-
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1811
|
-
});
|
|
1812
|
-
bytesDownloaded += result.bytesDownloaded;
|
|
1813
|
-
if (result.skipped) actionsSkipped++;
|
|
1814
|
-
actionsCompleted++;
|
|
1815
|
-
})
|
|
1816
|
-
)
|
|
1817
|
-
);
|
|
2714
|
+
await ctx.checkpoint();
|
|
2715
|
+
ctx.enterPhase(group.phase);
|
|
2716
|
+
await runDownloadGroup(ctx, groupActions);
|
|
1818
2717
|
}
|
|
1819
2718
|
const ungrouped = downloads.filter(
|
|
1820
2719
|
(action) => !DOWNLOAD_GROUPS.some((g) => g.categories.includes(action.category))
|
|
1821
2720
|
);
|
|
1822
2721
|
if (ungrouped.length > 0) {
|
|
1823
|
-
await checkpoint();
|
|
1824
|
-
enterPhase(InstallPhases.DOWNLOADING_LIBRARIES);
|
|
1825
|
-
await
|
|
1826
|
-
ungrouped.map(
|
|
1827
|
-
(action) => limit(async () => {
|
|
1828
|
-
await checkpoint();
|
|
1829
|
-
const result = await downloadFile(input.http, {
|
|
1830
|
-
url: action.url,
|
|
1831
|
-
target: action.target,
|
|
1832
|
-
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
1833
|
-
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
1834
|
-
...action.category !== void 0 ? { category: action.category } : {},
|
|
1835
|
-
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1836
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1837
|
-
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1838
|
-
});
|
|
1839
|
-
bytesDownloaded += result.bytesDownloaded;
|
|
1840
|
-
if (result.skipped) actionsSkipped++;
|
|
1841
|
-
actionsCompleted++;
|
|
1842
|
-
})
|
|
1843
|
-
)
|
|
1844
|
-
);
|
|
1845
|
-
}
|
|
1846
|
-
if (writeActions.length > 0) {
|
|
1847
|
-
await checkpoint();
|
|
1848
|
-
enterPhase(InstallPhases.WRITING_FILES);
|
|
1849
|
-
for (const action of writeActions) {
|
|
1850
|
-
await checkpoint();
|
|
1851
|
-
await atomicWrite(action.path, action.content);
|
|
1852
|
-
actionsCompleted++;
|
|
1853
|
-
}
|
|
2722
|
+
await ctx.checkpoint();
|
|
2723
|
+
ctx.enterPhase(InstallPhases.DOWNLOADING_LIBRARIES);
|
|
2724
|
+
await runDownloadGroup(ctx, ungrouped);
|
|
1854
2725
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2726
|
+
};
|
|
2727
|
+
var runDownloadGroup = async (ctx, groupActions) => {
|
|
2728
|
+
await Promise.all(
|
|
2729
|
+
groupActions.map(
|
|
2730
|
+
(action) => ctx.limit(async () => {
|
|
2731
|
+
await ctx.checkpoint();
|
|
2732
|
+
const result = await downloadFile(ctx.input.http, {
|
|
2733
|
+
url: action.url,
|
|
2734
|
+
target: action.target,
|
|
2735
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
2736
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
2737
|
+
...action.category !== void 0 ? { category: action.category } : {},
|
|
2738
|
+
...ctx.input.signal !== void 0 ? { signal: ctx.input.signal } : {},
|
|
2739
|
+
...ctx.input.onEvent !== void 0 ? { onEvent: ctx.input.onEvent } : {},
|
|
2740
|
+
...ctx.input.pauseController !== void 0 ? { pauseController: ctx.input.pauseController } : {}
|
|
2741
|
+
});
|
|
2742
|
+
ctx.counters.bytesDownloaded += result.bytesDownloaded;
|
|
2743
|
+
if (result.skipped) ctx.counters.actionsSkipped++;
|
|
2744
|
+
ctx.counters.actionsCompleted++;
|
|
2745
|
+
})
|
|
2746
|
+
)
|
|
2747
|
+
);
|
|
2748
|
+
};
|
|
2749
|
+
var runWritesStage = async (ctx, writes) => {
|
|
2750
|
+
if (writes.length === 0) return;
|
|
2751
|
+
await ctx.checkpoint();
|
|
2752
|
+
ctx.enterPhase(InstallPhases.WRITING_FILES);
|
|
2753
|
+
for (const action of writes) {
|
|
2754
|
+
await ctx.checkpoint();
|
|
2755
|
+
await atomicWrite(action.path, action.content);
|
|
2756
|
+
ctx.counters.actionsCompleted++;
|
|
1871
2757
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
2758
|
+
};
|
|
2759
|
+
var runNativesStage = async (ctx, natives) => {
|
|
2760
|
+
if (natives.length === 0) return;
|
|
2761
|
+
await ctx.checkpoint();
|
|
2762
|
+
ctx.enterPhase(InstallPhases.EXTRACTING_NATIVES);
|
|
2763
|
+
for (const action of natives) {
|
|
2764
|
+
await ctx.checkpoint();
|
|
2765
|
+
const { fileCount } = await extractAllToDir(action.source, action.destination, {
|
|
2766
|
+
excludePrefixes: action.exclude
|
|
1881
2767
|
});
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2768
|
+
ctx.input.onEvent?.({
|
|
2769
|
+
type: EventTypes.ARCHIVE_EXTRACTED,
|
|
2770
|
+
archive: action.source,
|
|
2771
|
+
target: action.destination,
|
|
2772
|
+
fileCount
|
|
1886
2773
|
});
|
|
2774
|
+
ctx.counters.actionsCompleted++;
|
|
1887
2775
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
input.plan.target.runtime.system.os,
|
|
1901
|
-
input.plan.target.runtime.installRoot
|
|
1902
|
-
);
|
|
1903
|
-
for (const action of processors) {
|
|
1904
|
-
await checkpoint();
|
|
1905
|
-
await runProcessor({
|
|
1906
|
-
action,
|
|
1907
|
-
javaPath,
|
|
1908
|
-
spawner: input.spawner,
|
|
1909
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1910
|
-
total: processors.length
|
|
1911
|
-
});
|
|
1912
|
-
actionsCompleted++;
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
enterPhase(InstallPhases.COMPLETED);
|
|
1916
|
-
return {
|
|
1917
|
-
targetId: input.plan.targetId,
|
|
1918
|
-
bytesDownloaded,
|
|
1919
|
-
actionsCompleted,
|
|
1920
|
-
actionsSkipped,
|
|
1921
|
-
durationMs: Date.now() - startedAt
|
|
1922
|
-
};
|
|
1923
|
-
}
|
|
1924
|
-
function isDownload(action) {
|
|
1925
|
-
return action.kind === InstallActionKinds.DOWNLOAD_FILE;
|
|
1926
|
-
}
|
|
1927
|
-
function isNative(action) {
|
|
1928
|
-
return action.kind === InstallActionKinds.EXTRACT_NATIVE;
|
|
1929
|
-
}
|
|
1930
|
-
function isProcessor(action) {
|
|
1931
|
-
return action.kind === InstallActionKinds.RUN_FORGE_PROCESSOR;
|
|
1932
|
-
}
|
|
1933
|
-
function isWrite(action) {
|
|
1934
|
-
return action.kind === InstallActionKinds.WRITE_VERSION_JSON || action.kind === InstallActionKinds.WRITE_LOGGING_CONFIG;
|
|
1935
|
-
}
|
|
1936
|
-
async function runProcessor(input) {
|
|
1937
|
-
const startedAt = Date.now();
|
|
1938
|
-
const processorJar = input.action.classpath[0];
|
|
1939
|
-
if (processorJar === void 0) {
|
|
1940
|
-
throw new MinecraftKitError(
|
|
1941
|
-
"FORGE_INSTALLER_INVALID",
|
|
1942
|
-
"Forge processor has an empty classpath",
|
|
1943
|
-
{ context: { processorIndex: input.action.index } }
|
|
1944
|
-
);
|
|
1945
|
-
}
|
|
1946
|
-
const mainClass = await readJarMainClass(processorJar);
|
|
1947
|
-
if (!mainClass) {
|
|
1948
|
-
throw new MinecraftKitError(
|
|
1949
|
-
"FORGE_INSTALLER_INVALID",
|
|
1950
|
-
`Forge processor jar has no Main-Class: ${processorJar}`,
|
|
1951
|
-
{ context: { filePath: processorJar } }
|
|
1952
|
-
);
|
|
1953
|
-
}
|
|
1954
|
-
const classpathSeparator = process.platform === "win32" ? ";" : ":";
|
|
1955
|
-
const args = [
|
|
1956
|
-
"-cp",
|
|
1957
|
-
input.action.classpath.join(classpathSeparator),
|
|
1958
|
-
mainClass,
|
|
1959
|
-
...input.action.args
|
|
1960
|
-
];
|
|
1961
|
-
input.onEvent?.({
|
|
1962
|
-
type: "forge:processor-started",
|
|
1963
|
-
processor: { index: input.action.index, mainClass },
|
|
1964
|
-
total: input.total
|
|
1965
|
-
});
|
|
1966
|
-
const stderrTail = [];
|
|
1967
|
-
const child = input.spawner.spawn(input.javaPath, args, { cwd: process.cwd() });
|
|
1968
|
-
child.stdout.on("data", () => {
|
|
2776
|
+
};
|
|
2777
|
+
var runRuntimeStage = async (ctx) => {
|
|
2778
|
+
const runtime = ctx.input.plan.target.runtime;
|
|
2779
|
+
if (runtime === void 0) return;
|
|
2780
|
+
await ctx.checkpoint();
|
|
2781
|
+
ctx.enterPhase(InstallPhases.INSTALLING_RUNTIME);
|
|
2782
|
+
const runtimePlan = await planRuntimeDownloads({
|
|
2783
|
+
runtime,
|
|
2784
|
+
directory: ctx.input.plan.directory,
|
|
2785
|
+
http: ctx.input.http,
|
|
2786
|
+
cache: ctx.input.cache,
|
|
2787
|
+
...ctx.input.signal !== void 0 ? { signal: ctx.input.signal } : {}
|
|
1969
2788
|
});
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2789
|
+
await materializeRuntimeExtras({
|
|
2790
|
+
runtime,
|
|
2791
|
+
directory: ctx.input.plan.directory,
|
|
2792
|
+
manifest: runtimePlan.manifest
|
|
1973
2793
|
});
|
|
1974
|
-
|
|
1975
|
-
|
|
2794
|
+
};
|
|
2795
|
+
var runProcessorsStage = async (ctx, processors) => {
|
|
2796
|
+
if (processors.length === 0) return;
|
|
2797
|
+
await ctx.checkpoint();
|
|
2798
|
+
ctx.enterPhase(InstallPhases.RUNNING_FORGE_PROCESSORS);
|
|
2799
|
+
const target = ctx.input.plan.target;
|
|
2800
|
+
if (target.loader?.type !== Loaders.FORGE) {
|
|
1976
2801
|
throw new MinecraftKitError(
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
{
|
|
1980
|
-
context: {
|
|
1981
|
-
exitCode: exit.code ?? void 0,
|
|
1982
|
-
mainClass,
|
|
1983
|
-
stderr: stderrTail.join("\n")
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
2802
|
+
MinecraftKitErrorCodes.FORGE_PROCESSOR_FAILED,
|
|
2803
|
+
"Forge processors planned for a non-Forge target"
|
|
1986
2804
|
);
|
|
1987
2805
|
}
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
for (const
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
}
|
|
2003
|
-
input.onEvent?.({
|
|
2004
|
-
type: "forge:processor-output-verified",
|
|
2005
|
-
processor: { index: input.action.index, mainClass },
|
|
2006
|
-
path: outputPath
|
|
2806
|
+
const javaPath = targetPaths.runtimeJavaExecutable(
|
|
2807
|
+
ctx.input.plan.directory,
|
|
2808
|
+
target.runtime.component,
|
|
2809
|
+
target.runtime.system.os,
|
|
2810
|
+
target.runtime.installRoot
|
|
2811
|
+
);
|
|
2812
|
+
for (const action of processors) {
|
|
2813
|
+
await ctx.checkpoint();
|
|
2814
|
+
await runProcessor({
|
|
2815
|
+
action,
|
|
2816
|
+
javaPath,
|
|
2817
|
+
spawner: ctx.input.spawner,
|
|
2818
|
+
...ctx.input.onEvent !== void 0 ? { onEvent: ctx.input.onEvent } : {},
|
|
2819
|
+
total: processors.length
|
|
2007
2820
|
});
|
|
2821
|
+
ctx.counters.actionsCompleted++;
|
|
2008
2822
|
}
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2823
|
+
};
|
|
2824
|
+
var isDownload = (action) => {
|
|
2825
|
+
return action.kind === InstallActionKinds.DOWNLOAD_FILE;
|
|
2826
|
+
};
|
|
2827
|
+
var isNative = (action) => {
|
|
2828
|
+
return action.kind === InstallActionKinds.EXTRACT_NATIVE;
|
|
2829
|
+
};
|
|
2830
|
+
var isProcessor = (action) => {
|
|
2831
|
+
return action.kind === InstallActionKinds.RUN_FORGE_PROCESSOR;
|
|
2832
|
+
};
|
|
2833
|
+
var isWrite = (action) => {
|
|
2834
|
+
return action.kind === InstallActionKinds.WRITE_VERSION_JSON || action.kind === InstallActionKinds.WRITE_LOGGING_CONFIG;
|
|
2835
|
+
};
|
|
2020
2836
|
|
|
2021
2837
|
// src/install/runtime-install.ts
|
|
2022
|
-
async
|
|
2838
|
+
var planRuntimeInstall = async (input) => {
|
|
2023
2839
|
const runtimePlan = await planRuntimeDownloads({
|
|
2024
2840
|
runtime: input.target.runtime,
|
|
2025
2841
|
directory: input.target.directory,
|
|
@@ -2040,8 +2856,8 @@ async function planRuntimeInstall(input) {
|
|
|
2040
2856
|
totalActions: actions.length,
|
|
2041
2857
|
totalBytes
|
|
2042
2858
|
};
|
|
2043
|
-
}
|
|
2044
|
-
async
|
|
2859
|
+
};
|
|
2860
|
+
var planStandaloneRuntimeInstall = async (input) => {
|
|
2045
2861
|
const runtimePlan = await planRuntimeDownloads({
|
|
2046
2862
|
runtime: input.runtime,
|
|
2047
2863
|
directory: input.directory,
|
|
@@ -2057,9 +2873,7 @@ async function planStandaloneRuntimeInstall(input) {
|
|
|
2057
2873
|
const target = {
|
|
2058
2874
|
id: input.id,
|
|
2059
2875
|
directory: input.directory,
|
|
2060
|
-
runtime: input.runtime
|
|
2061
|
-
minecraft: void 0,
|
|
2062
|
-
loader: void 0
|
|
2876
|
+
runtime: input.runtime
|
|
2063
2877
|
};
|
|
2064
2878
|
return {
|
|
2065
2879
|
targetId: input.id,
|
|
@@ -2069,7 +2883,7 @@ async function planStandaloneRuntimeInstall(input) {
|
|
|
2069
2883
|
totalActions: actions.length,
|
|
2070
2884
|
totalBytes
|
|
2071
2885
|
};
|
|
2072
|
-
}
|
|
2886
|
+
};
|
|
2073
2887
|
|
|
2074
2888
|
// src/constants/launch.ts
|
|
2075
2889
|
var BASE_JVM_ARGS = [
|
|
@@ -2090,7 +2904,7 @@ var LEGACY_JVM_ARGS = [
|
|
|
2090
2904
|
var MACOS_JVM_ARGS = ["-Xdock:name=Minecraft"];
|
|
2091
2905
|
|
|
2092
2906
|
// src/launch/arguments.ts
|
|
2093
|
-
|
|
2907
|
+
var flattenArguments = (entries, context) => {
|
|
2094
2908
|
const result = [];
|
|
2095
2909
|
for (const entry of entries) {
|
|
2096
2910
|
if (typeof entry === "string") {
|
|
@@ -2105,16 +2919,16 @@ function flattenArguments(entries, context) {
|
|
|
2105
2919
|
}
|
|
2106
2920
|
}
|
|
2107
2921
|
return result;
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2922
|
+
};
|
|
2923
|
+
var splitLegacyArguments = (raw) => {
|
|
2110
2924
|
return raw.trim().length === 0 ? [] : raw.trim().split(/\s+/);
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2925
|
+
};
|
|
2926
|
+
var pickArguments = (args, context) => {
|
|
2113
2927
|
return {
|
|
2114
2928
|
game: flattenArguments(args?.game ?? [], context),
|
|
2115
2929
|
jvm: flattenArguments(args?.jvm ?? [], context)
|
|
2116
2930
|
};
|
|
2117
|
-
}
|
|
2931
|
+
};
|
|
2118
2932
|
|
|
2119
2933
|
// src/launch/jvm-compat.ts
|
|
2120
2934
|
var FLAG_MIN_JAVA = [
|
|
@@ -2123,7 +2937,7 @@ var FLAG_MIN_JAVA = [
|
|
|
2123
2937
|
{ prefix: "-XX:+UseCompactObjectHeaders", minJava: 24 },
|
|
2124
2938
|
{ prefix: "-XX:+UseZGC", minJava: 15 }
|
|
2125
2939
|
];
|
|
2126
|
-
|
|
2940
|
+
var filterArgsForJava = (input) => {
|
|
2127
2941
|
if (!Number.isFinite(input.javaMajor) || input.javaMajor <= 0) return input.args;
|
|
2128
2942
|
const out = [];
|
|
2129
2943
|
for (const arg of input.args) {
|
|
@@ -2141,26 +2955,30 @@ function filterArgsForJava(input) {
|
|
|
2141
2955
|
out.push(arg);
|
|
2142
2956
|
}
|
|
2143
2957
|
return out;
|
|
2144
|
-
}
|
|
2958
|
+
};
|
|
2145
2959
|
|
|
2146
2960
|
// src/launch/placeholders.ts
|
|
2147
|
-
|
|
2961
|
+
var substituteArg = (raw, values) => {
|
|
2148
2962
|
return raw.replaceAll(/\$\{([a-zA-Z0-9_]+)\}/g, (match, key) => {
|
|
2149
2963
|
const value = values[key];
|
|
2150
2964
|
if (value === void 0) {
|
|
2151
|
-
throw new MinecraftKitError(
|
|
2152
|
-
|
|
2153
|
-
|
|
2965
|
+
throw new MinecraftKitError(
|
|
2966
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2967
|
+
`Unknown launch placeholder: ${match}`,
|
|
2968
|
+
{
|
|
2969
|
+
context: { placeholder: key }
|
|
2970
|
+
}
|
|
2971
|
+
);
|
|
2154
2972
|
}
|
|
2155
2973
|
return value;
|
|
2156
2974
|
});
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2975
|
+
};
|
|
2976
|
+
var substituteArgs = (args, values) => {
|
|
2159
2977
|
return args.map((arg) => substituteArg(arg, values));
|
|
2160
|
-
}
|
|
2978
|
+
};
|
|
2161
2979
|
|
|
2162
2980
|
// src/launch/args-composition.ts
|
|
2163
|
-
|
|
2981
|
+
var composeArgs = (input) => {
|
|
2164
2982
|
const minMb = input.options.memory?.minMb ?? DEFAULT_MIN_MB;
|
|
2165
2983
|
const maxMb = input.options.memory?.maxMb ?? DEFAULT_MAX_MB;
|
|
2166
2984
|
const memoryArgs = [`-Xms${minMb}M`, `-Xmx${maxMb}M`];
|
|
@@ -2210,8 +3028,8 @@ function composeArgs(input) {
|
|
|
2210
3028
|
);
|
|
2211
3029
|
}
|
|
2212
3030
|
return { jvmArgs: [...jvmArgs, ...extraJvm], gameArgs };
|
|
2213
|
-
}
|
|
2214
|
-
|
|
3031
|
+
};
|
|
3032
|
+
var buildClasspath = (input) => {
|
|
2215
3033
|
const seen = /* @__PURE__ */ new Set();
|
|
2216
3034
|
const entries = [];
|
|
2217
3035
|
for (const library of input.merged.libraries) {
|
|
@@ -2227,41 +3045,33 @@ function buildClasspath(input) {
|
|
|
2227
3045
|
const versionJar = targetPaths.versionJar(input.directory, input.versionId);
|
|
2228
3046
|
if (!seen.has(versionJar)) entries.push(versionJar);
|
|
2229
3047
|
return entries;
|
|
2230
|
-
}
|
|
2231
|
-
|
|
3048
|
+
};
|
|
3049
|
+
var relativeFor = (library) => {
|
|
2232
3050
|
if (library.downloads?.artifact?.path) return library.downloads.artifact.path;
|
|
2233
3051
|
if (library.name) {
|
|
2234
3052
|
const coord = parseMavenCoordinate(library.name);
|
|
2235
3053
|
return mavenRelativePath(coord);
|
|
2236
3054
|
}
|
|
2237
3055
|
return null;
|
|
2238
|
-
}
|
|
2239
|
-
|
|
3056
|
+
};
|
|
3057
|
+
var offlineUuidFor = (username) => {
|
|
2240
3058
|
const md5 = crypto2.createHash("md5");
|
|
2241
3059
|
md5.update(`OfflinePlayer:${username}`, "utf8");
|
|
2242
3060
|
const bytes = md5.digest();
|
|
2243
3061
|
bytes[6] = (bytes[6] ?? 0) & 15 | 48;
|
|
2244
3062
|
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
2245
3063
|
return formatUuid(bytes);
|
|
2246
|
-
}
|
|
2247
|
-
|
|
3064
|
+
};
|
|
3065
|
+
var formatUuid = (bytes) => {
|
|
2248
3066
|
const hex = bytes.toString("hex");
|
|
2249
3067
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
2250
|
-
}
|
|
2251
|
-
|
|
3068
|
+
};
|
|
3069
|
+
var stripUuidDashes = (uuid) => {
|
|
2252
3070
|
return uuid.replaceAll("-", "");
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
// src/types/auth.ts
|
|
2256
|
-
var AuthModes = {
|
|
2257
|
-
/** Offline-mode play with a chosen username and synthetic UUID. */
|
|
2258
|
-
OFFLINE: "offline",
|
|
2259
|
-
/** Pre-authenticated session — caller provides the access token and identity. */
|
|
2260
|
-
ONLINE: "online"
|
|
2261
3071
|
};
|
|
2262
3072
|
|
|
2263
3073
|
// src/launch/placeholder-values.ts
|
|
2264
|
-
|
|
3074
|
+
var buildPlaceholderValues = (input) => {
|
|
2265
3075
|
const cpSeparator = process.platform === "win32" ? ";" : ":";
|
|
2266
3076
|
const directory = input.target.directory;
|
|
2267
3077
|
const username = input.auth.username;
|
|
@@ -2294,11 +3104,20 @@ function buildPlaceholderValues(input) {
|
|
|
2294
3104
|
resolution_width: input.options.resolution?.width.toString() ?? "",
|
|
2295
3105
|
resolution_height: input.options.resolution?.height.toString() ?? ""
|
|
2296
3106
|
};
|
|
2297
|
-
}
|
|
3107
|
+
};
|
|
2298
3108
|
|
|
2299
3109
|
// src/core/manifest-merge.ts
|
|
2300
|
-
|
|
2301
|
-
const
|
|
3110
|
+
var mergeManifest = (parent, child) => {
|
|
3111
|
+
const args = mergeArguments(parent.arguments, child.arguments);
|
|
3112
|
+
const minecraftArguments = child.minecraftArguments ?? parent.minecraftArguments;
|
|
3113
|
+
const javaVersion = child.javaVersion ?? parent.javaVersion;
|
|
3114
|
+
const logging = child.logging ?? parent.logging;
|
|
3115
|
+
const inheritsFrom = child.inheritsFrom ?? parent.inheritsFrom;
|
|
3116
|
+
const releaseTime = child.releaseTime ?? parent.releaseTime;
|
|
3117
|
+
const time = child.time ?? parent.time;
|
|
3118
|
+
const minimumLauncherVersion = child.minimumLauncherVersion ?? parent.minimumLauncherVersion;
|
|
3119
|
+
const complianceLevel = child.complianceLevel ?? parent.complianceLevel;
|
|
3120
|
+
return {
|
|
2302
3121
|
id: child.id || parent.id,
|
|
2303
3122
|
type: child.type ?? parent.type,
|
|
2304
3123
|
mainClass: child.mainClass ?? parent.mainClass,
|
|
@@ -2306,19 +3125,18 @@ function mergeManifest(parent, child) {
|
|
|
2306
3125
|
assets: child.assets ?? parent.assets,
|
|
2307
3126
|
downloads: { ...parent.downloads, ...child.downloads },
|
|
2308
3127
|
libraries: mergeLibraries(parent.libraries, child.libraries),
|
|
2309
|
-
arguments:
|
|
2310
|
-
minecraftArguments
|
|
2311
|
-
javaVersion
|
|
2312
|
-
logging
|
|
2313
|
-
inheritsFrom
|
|
2314
|
-
releaseTime
|
|
2315
|
-
time
|
|
2316
|
-
minimumLauncherVersion
|
|
2317
|
-
complianceLevel
|
|
3128
|
+
...args !== void 0 ? { arguments: args } : {},
|
|
3129
|
+
...minecraftArguments !== void 0 ? { minecraftArguments } : {},
|
|
3130
|
+
...javaVersion !== void 0 ? { javaVersion } : {},
|
|
3131
|
+
...logging !== void 0 ? { logging } : {},
|
|
3132
|
+
...inheritsFrom !== void 0 ? { inheritsFrom } : {},
|
|
3133
|
+
...releaseTime !== void 0 ? { releaseTime } : {},
|
|
3134
|
+
...time !== void 0 ? { time } : {},
|
|
3135
|
+
...minimumLauncherVersion !== void 0 ? { minimumLauncherVersion } : {},
|
|
3136
|
+
...complianceLevel !== void 0 ? { complianceLevel } : {}
|
|
2318
3137
|
};
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
function libraryDedupeKey(library) {
|
|
3138
|
+
};
|
|
3139
|
+
var libraryDedupeKey = (library) => {
|
|
2322
3140
|
if (!library.name) return null;
|
|
2323
3141
|
try {
|
|
2324
3142
|
const coord = parseMavenCoordinate(library.name);
|
|
@@ -2327,8 +3145,8 @@ function libraryDedupeKey(library) {
|
|
|
2327
3145
|
} catch {
|
|
2328
3146
|
return null;
|
|
2329
3147
|
}
|
|
2330
|
-
}
|
|
2331
|
-
|
|
3148
|
+
};
|
|
3149
|
+
var mergeLibraries = (parent, child) => {
|
|
2332
3150
|
const byKey = /* @__PURE__ */ new Map();
|
|
2333
3151
|
const unkeyed = [];
|
|
2334
3152
|
for (const lib of [...parent, ...child]) {
|
|
@@ -2340,8 +3158,8 @@ function mergeLibraries(parent, child) {
|
|
|
2340
3158
|
byKey.set(key, lib);
|
|
2341
3159
|
}
|
|
2342
3160
|
return [...byKey.values(), ...unkeyed];
|
|
2343
|
-
}
|
|
2344
|
-
|
|
3161
|
+
};
|
|
3162
|
+
var mergeArguments = (parent, child) => {
|
|
2345
3163
|
if (!parent && !child) return void 0;
|
|
2346
3164
|
const parentGame = parent?.game ?? [];
|
|
2347
3165
|
const parentJvm = parent?.jvm ?? [];
|
|
@@ -2351,10 +3169,10 @@ function mergeArguments(parent, child) {
|
|
|
2351
3169
|
game: [...parentGame, ...childGame],
|
|
2352
3170
|
jvm: [...parentJvm, ...childJvm]
|
|
2353
3171
|
};
|
|
2354
|
-
}
|
|
3172
|
+
};
|
|
2355
3173
|
|
|
2356
3174
|
// src/launch/version-resolution.ts
|
|
2357
|
-
async
|
|
3175
|
+
var resolveLaunchVersion = async (target) => {
|
|
2358
3176
|
if (target.loader.type === Loaders.VANILLA) {
|
|
2359
3177
|
return {
|
|
2360
3178
|
versionId: target.minecraft.version,
|
|
@@ -2365,8 +3183,8 @@ async function resolveLaunchVersion(target) {
|
|
|
2365
3183
|
const versionId = await pickInstalledVersionId(target);
|
|
2366
3184
|
const merged = await loadAndMerge(target.directory, versionId, target.minecraft.manifest);
|
|
2367
3185
|
return { versionId, merged, chain: [versionId, target.minecraft.version] };
|
|
2368
|
-
}
|
|
2369
|
-
async
|
|
3186
|
+
};
|
|
3187
|
+
var pickClientJarVersionId = async (directory, chain) => {
|
|
2370
3188
|
for (const id of chain) {
|
|
2371
3189
|
const jar = targetPaths.versionJar(directory, id);
|
|
2372
3190
|
if (await fileExists(jar)) return id;
|
|
@@ -2374,13 +3192,13 @@ async function pickClientJarVersionId(directory, chain) {
|
|
|
2374
3192
|
const fallback = chain.at(-1);
|
|
2375
3193
|
if (fallback === void 0) {
|
|
2376
3194
|
throw new MinecraftKitError(
|
|
2377
|
-
|
|
3195
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
2378
3196
|
"Cannot resolve a client jar version id from an empty inheritsFrom chain"
|
|
2379
3197
|
);
|
|
2380
3198
|
}
|
|
2381
3199
|
return fallback;
|
|
2382
|
-
}
|
|
2383
|
-
async
|
|
3200
|
+
};
|
|
3201
|
+
var pickInstalledVersionId = async (target) => {
|
|
2384
3202
|
if (target.loader.type === Loaders.FABRIC) {
|
|
2385
3203
|
const candidate = target.loader.profile.id;
|
|
2386
3204
|
const versionJsonPath = targetPaths.versionJson(target.directory, candidate);
|
|
@@ -2392,46 +3210,38 @@ async function pickInstalledVersionId(target) {
|
|
|
2392
3210
|
const versionJsonPath = targetPaths.versionJson(target.directory, id);
|
|
2393
3211
|
if (!await fileExists(versionJsonPath)) continue;
|
|
2394
3212
|
const text = await readText(versionJsonPath);
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
return id;
|
|
2399
|
-
}
|
|
2400
|
-
} catch {
|
|
3213
|
+
const parsed = parseJsonOrUndefined(text);
|
|
3214
|
+
if (parsed?.inheritsFrom === target.minecraft.version && (id.includes("forge") || (parsed.id ?? "").includes("forge"))) {
|
|
3215
|
+
return id;
|
|
2401
3216
|
}
|
|
2402
3217
|
}
|
|
2403
3218
|
}
|
|
2404
3219
|
throw new MinecraftKitError(
|
|
2405
|
-
|
|
3220
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
2406
3221
|
`Could not find an installed version JSON for target ${target.id}`,
|
|
2407
3222
|
{ context: { targetId: target.id, loaderType: target.loader.type } }
|
|
2408
3223
|
);
|
|
2409
|
-
}
|
|
2410
|
-
async
|
|
3224
|
+
};
|
|
3225
|
+
var loadAndMerge = async (directory, versionId, parentManifest) => {
|
|
2411
3226
|
const versionJsonPath = targetPaths.versionJson(directory, versionId);
|
|
2412
3227
|
const text = await readText(versionJsonPath);
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
"MANIFEST_INVALID",
|
|
2419
|
-
`Version JSON is not valid JSON: ${versionJsonPath}`,
|
|
2420
|
-
{ cause, context: { filePath: versionJsonPath } }
|
|
2421
|
-
);
|
|
2422
|
-
}
|
|
3228
|
+
const child = parseJsonStrict(text, {
|
|
3229
|
+
code: MinecraftKitErrorCodes.MANIFEST_INVALID,
|
|
3230
|
+
message: `Version JSON is not valid JSON: ${versionJsonPath}`,
|
|
3231
|
+
context: { filePath: versionJsonPath }
|
|
3232
|
+
});
|
|
2423
3233
|
if (child.inheritsFrom !== void 0 && child.inheritsFrom !== parentManifest.id) {
|
|
2424
3234
|
return mergeManifest(parentManifest, child);
|
|
2425
3235
|
}
|
|
2426
3236
|
return mergeManifest(parentManifest, child);
|
|
2427
|
-
}
|
|
3237
|
+
};
|
|
2428
3238
|
|
|
2429
3239
|
// src/launch/compose.ts
|
|
2430
|
-
async
|
|
3240
|
+
var composeLaunch = async (input) => {
|
|
2431
3241
|
const { target, options } = input;
|
|
2432
3242
|
if (!options.auth.username || options.auth.username.length === 0) {
|
|
2433
3243
|
throw new MinecraftKitError(
|
|
2434
|
-
|
|
3244
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2435
3245
|
`Auth username must be non-empty (target ${target.id})`,
|
|
2436
3246
|
{ context: { targetId: target.id } }
|
|
2437
3247
|
);
|
|
@@ -2478,22 +3288,22 @@ async function composeLaunch(input) {
|
|
|
2478
3288
|
auth: options.auth,
|
|
2479
3289
|
workingDirectory: target.directory
|
|
2480
3290
|
};
|
|
2481
|
-
}
|
|
2482
|
-
|
|
3291
|
+
};
|
|
3292
|
+
var buildFeatures = (options) => {
|
|
2483
3293
|
const features = { ...options.features ?? {} };
|
|
2484
3294
|
if (options.resolution !== void 0) {
|
|
2485
3295
|
features.has_custom_resolution = true;
|
|
2486
3296
|
}
|
|
2487
3297
|
return features;
|
|
2488
|
-
}
|
|
3298
|
+
};
|
|
2489
3299
|
|
|
2490
3300
|
// src/launch/runner.ts
|
|
2491
|
-
|
|
3301
|
+
var runLaunch = (input) => {
|
|
2492
3302
|
const composition = input.composition;
|
|
2493
3303
|
const options = input.options ?? {};
|
|
2494
3304
|
const args = [...composition.jvmArgs, composition.mainClass, ...composition.gameArgs];
|
|
2495
3305
|
options.onEvent?.({
|
|
2496
|
-
type:
|
|
3306
|
+
type: EventTypes.LAUNCH_STARTING,
|
|
2497
3307
|
command: composition.javaPath,
|
|
2498
3308
|
args,
|
|
2499
3309
|
cwd: composition.workingDirectory
|
|
@@ -2502,19 +3312,19 @@ function runLaunch(input) {
|
|
|
2502
3312
|
cwd: composition.workingDirectory,
|
|
2503
3313
|
...composition.env !== void 0 ? { env: composition.env } : {}
|
|
2504
3314
|
});
|
|
2505
|
-
options.onEvent?.({ type:
|
|
3315
|
+
options.onEvent?.({ type: EventTypes.LAUNCH_STARTED, pid: child.pid });
|
|
2506
3316
|
child.stdout.on("data", (line) => {
|
|
2507
|
-
options.onEvent?.({ type:
|
|
3317
|
+
options.onEvent?.({ type: EventTypes.LAUNCH_STDOUT, line });
|
|
2508
3318
|
});
|
|
2509
3319
|
child.stderr.on("data", (line) => {
|
|
2510
|
-
options.onEvent?.({ type:
|
|
3320
|
+
options.onEvent?.({ type: EventTypes.LAUNCH_STDERR, line });
|
|
2511
3321
|
});
|
|
2512
3322
|
const grace = options.killGracePeriodMs ?? DEFAULT_KILL_GRACE_MS;
|
|
2513
3323
|
let aborted = false;
|
|
2514
3324
|
const doAbort = (reason) => {
|
|
2515
3325
|
if (aborted) return;
|
|
2516
3326
|
aborted = true;
|
|
2517
|
-
options.onEvent?.({ type:
|
|
3327
|
+
options.onEvent?.({ type: EventTypes.LAUNCH_ABORTED, reason });
|
|
2518
3328
|
child.kill("SIGTERM");
|
|
2519
3329
|
setTimeout(() => child.kill("SIGKILL"), grace).unref();
|
|
2520
3330
|
};
|
|
@@ -2529,10 +3339,10 @@ function runLaunch(input) {
|
|
|
2529
3339
|
}
|
|
2530
3340
|
const exited = (async () => {
|
|
2531
3341
|
const { code, signal } = await child.exited;
|
|
2532
|
-
options.onEvent?.({ type:
|
|
3342
|
+
options.onEvent?.({ type: EventTypes.LAUNCH_EXITED, code, signal });
|
|
2533
3343
|
if (!aborted && code !== 0 && code !== null) {
|
|
2534
3344
|
throw new MinecraftKitError(
|
|
2535
|
-
|
|
3345
|
+
MinecraftKitErrorCodes.LAUNCH_PROCESS_FAILED,
|
|
2536
3346
|
`Minecraft process exited with code ${code}`,
|
|
2537
3347
|
{ context: { exitCode: code } }
|
|
2538
3348
|
);
|
|
@@ -2546,13 +3356,13 @@ function runLaunch(input) {
|
|
|
2546
3356
|
doAbort(reason ?? "user");
|
|
2547
3357
|
}
|
|
2548
3358
|
};
|
|
2549
|
-
}
|
|
2550
|
-
|
|
3359
|
+
};
|
|
3360
|
+
var reasonFrom = (value) => {
|
|
2551
3361
|
if (value === void 0) return "aborted";
|
|
2552
3362
|
if (typeof value === "string") return value;
|
|
2553
3363
|
if (value instanceof Error) return value.message;
|
|
2554
3364
|
return String(value);
|
|
2555
|
-
}
|
|
3365
|
+
};
|
|
2556
3366
|
var ChildProcessSpawner = class {
|
|
2557
3367
|
spawn(command, args, options) {
|
|
2558
3368
|
const child = spawn(command, [...args], {
|
|
@@ -2576,14 +3386,14 @@ var ChildProcessSpawner = class {
|
|
|
2576
3386
|
};
|
|
2577
3387
|
}
|
|
2578
3388
|
};
|
|
2579
|
-
|
|
3389
|
+
var streamFromBuffer = (stream) => {
|
|
2580
3390
|
if (!stream) {
|
|
2581
3391
|
return { on() {
|
|
2582
3392
|
} };
|
|
2583
3393
|
}
|
|
2584
3394
|
let buffer = "";
|
|
2585
3395
|
const listeners = /* @__PURE__ */ new Set();
|
|
2586
|
-
const
|
|
3396
|
+
const emit2 = (line) => {
|
|
2587
3397
|
for (const listener of listeners) listener(line);
|
|
2588
3398
|
};
|
|
2589
3399
|
stream.on("data", (chunk) => {
|
|
@@ -2593,35 +3403,36 @@ function streamFromBuffer(stream) {
|
|
|
2593
3403
|
while (index !== -1) {
|
|
2594
3404
|
const line = buffer.slice(0, index).replace(/\r$/, "");
|
|
2595
3405
|
buffer = buffer.slice(index + 1);
|
|
2596
|
-
emitBounded(
|
|
3406
|
+
emitBounded(emit2, line);
|
|
2597
3407
|
index = buffer.indexOf("\n");
|
|
2598
3408
|
}
|
|
2599
3409
|
while (buffer.length > SPAWNER_MAX_LINE_BYTES) {
|
|
2600
|
-
|
|
3410
|
+
emit2(buffer.slice(0, SPAWNER_MAX_LINE_BYTES));
|
|
2601
3411
|
buffer = buffer.slice(SPAWNER_MAX_LINE_BYTES);
|
|
2602
3412
|
}
|
|
2603
3413
|
});
|
|
2604
3414
|
stream.on("end", () => {
|
|
2605
3415
|
if (buffer.length > 0) {
|
|
2606
|
-
emitBounded(
|
|
3416
|
+
emitBounded(emit2, buffer);
|
|
2607
3417
|
buffer = "";
|
|
2608
3418
|
}
|
|
3419
|
+
listeners.clear();
|
|
2609
3420
|
});
|
|
2610
3421
|
return {
|
|
2611
3422
|
on(_event, listener) {
|
|
2612
3423
|
listeners.add(listener);
|
|
2613
3424
|
}
|
|
2614
3425
|
};
|
|
2615
|
-
}
|
|
2616
|
-
|
|
3426
|
+
};
|
|
3427
|
+
var emitBounded = (emit2, line) => {
|
|
2617
3428
|
if (line.length <= SPAWNER_MAX_LINE_BYTES) {
|
|
2618
|
-
|
|
3429
|
+
emit2(line);
|
|
2619
3430
|
return;
|
|
2620
3431
|
}
|
|
2621
3432
|
for (let i = 0; i < line.length; i += SPAWNER_MAX_LINE_BYTES) {
|
|
2622
|
-
|
|
3433
|
+
emit2(line.slice(i, i + SPAWNER_MAX_LINE_BYTES));
|
|
2623
3434
|
}
|
|
2624
|
-
}
|
|
3435
|
+
};
|
|
2625
3436
|
|
|
2626
3437
|
// src/types/verify.ts
|
|
2627
3438
|
var VerificationKinds = {
|
|
@@ -2646,24 +3457,14 @@ var VerifyFileCategories = {
|
|
|
2646
3457
|
RUNTIME_FILE: "runtime-file",
|
|
2647
3458
|
LOGGING_CONFIG: "logging-config"
|
|
2648
3459
|
};
|
|
2649
|
-
async function sha1OfFile(filePath) {
|
|
2650
|
-
const hash = crypto2.createHash("sha1");
|
|
2651
|
-
await new Promise((resolve, reject) => {
|
|
2652
|
-
const stream = createReadStream(filePath);
|
|
2653
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
2654
|
-
stream.on("end", resolve);
|
|
2655
|
-
stream.on("error", reject);
|
|
2656
|
-
});
|
|
2657
|
-
return hash.digest("hex");
|
|
2658
|
-
}
|
|
2659
3460
|
|
|
2660
3461
|
// src/verify/helpers.ts
|
|
2661
|
-
async
|
|
3462
|
+
var runVerification = async (input, check) => {
|
|
2662
3463
|
const startedAt = Date.now();
|
|
2663
3464
|
const results = [];
|
|
2664
3465
|
const record = (result) => {
|
|
2665
3466
|
results.push(result);
|
|
2666
|
-
input.onEvent?.({ type:
|
|
3467
|
+
input.onEvent?.({ type: EventTypes.VERIFY_FILE_CHECKED, file: result });
|
|
2667
3468
|
};
|
|
2668
3469
|
await check(record);
|
|
2669
3470
|
return {
|
|
@@ -2674,8 +3475,8 @@ async function runVerification(input, check) {
|
|
|
2674
3475
|
checkedFiles: results.length,
|
|
2675
3476
|
durationMs: Date.now() - startedAt
|
|
2676
3477
|
};
|
|
2677
|
-
}
|
|
2678
|
-
async
|
|
3478
|
+
};
|
|
3479
|
+
var verifyHashedFile = async (input) => {
|
|
2679
3480
|
if (!await fileExists(input.path)) {
|
|
2680
3481
|
return {
|
|
2681
3482
|
path: input.path,
|
|
@@ -2722,8 +3523,8 @@ async function verifyHashedFile(input) {
|
|
|
2722
3523
|
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2723
3524
|
...input.url !== void 0 ? { url: input.url } : {}
|
|
2724
3525
|
};
|
|
2725
|
-
}
|
|
2726
|
-
async
|
|
3526
|
+
};
|
|
3527
|
+
var verifyExistence = async (input) => {
|
|
2727
3528
|
if (await fileExists(input.path)) {
|
|
2728
3529
|
return {
|
|
2729
3530
|
path: input.path,
|
|
@@ -2738,8 +3539,8 @@ async function verifyExistence(input) {
|
|
|
2738
3539
|
status: VerifyFileStatuses.MISSING,
|
|
2739
3540
|
...input.url !== void 0 ? { url: input.url } : {}
|
|
2740
3541
|
};
|
|
2741
|
-
}
|
|
2742
|
-
async
|
|
3542
|
+
};
|
|
3543
|
+
var findForgeVersionJsonPath = async (directory, minecraftVersion) => {
|
|
2743
3544
|
const versionsDir = targetPaths.versionsDir(directory);
|
|
2744
3545
|
const dirs = await listChildDirectories(versionsDir);
|
|
2745
3546
|
for (const id of dirs) {
|
|
@@ -2752,21 +3553,17 @@ async function findForgeVersionJsonPath(directory, minecraftVersion) {
|
|
|
2752
3553
|
if (parsed === minecraftVersion) return jsonPath;
|
|
2753
3554
|
}
|
|
2754
3555
|
return null;
|
|
2755
|
-
}
|
|
2756
|
-
async
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
} catch {
|
|
2761
|
-
return void 0;
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
3556
|
+
};
|
|
3557
|
+
var tryParseInheritsFrom = async (jsonPath) => {
|
|
3558
|
+
const parsed = parseJsonOrUndefined(await readText(jsonPath));
|
|
3559
|
+
return parsed?.inheritsFrom;
|
|
3560
|
+
};
|
|
2764
3561
|
|
|
2765
3562
|
// src/verify/fabric.ts
|
|
2766
|
-
async
|
|
3563
|
+
var verifyFabric = async (input) => {
|
|
2767
3564
|
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
2768
3565
|
throw new MinecraftKitError(
|
|
2769
|
-
|
|
3566
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2770
3567
|
`verify.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
2771
3568
|
);
|
|
2772
3569
|
}
|
|
@@ -2789,14 +3586,14 @@ async function verifyFabric(input) {
|
|
|
2789
3586
|
directory: input.target.directory,
|
|
2790
3587
|
system: input.target.runtime.system,
|
|
2791
3588
|
versionId: input.target.minecraft.version,
|
|
2792
|
-
category:
|
|
3589
|
+
category: DownloadCategories.FABRIC_LIBRARY
|
|
2793
3590
|
});
|
|
2794
3591
|
for (const action of fabricLibraries.downloads) {
|
|
2795
3592
|
record(
|
|
2796
3593
|
await verifyHashedFile({
|
|
2797
3594
|
path: action.target,
|
|
2798
|
-
expectedSha1: action.expectedSha1,
|
|
2799
|
-
expectedSize: action.expectedSize,
|
|
3595
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
3596
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
2800
3597
|
...action.url ? { url: action.url } : {},
|
|
2801
3598
|
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2802
3599
|
})
|
|
@@ -2804,13 +3601,13 @@ async function verifyFabric(input) {
|
|
|
2804
3601
|
}
|
|
2805
3602
|
}
|
|
2806
3603
|
);
|
|
2807
|
-
}
|
|
3604
|
+
};
|
|
2808
3605
|
|
|
2809
3606
|
// src/verify/forge.ts
|
|
2810
|
-
async
|
|
3607
|
+
var verifyForge = async (input) => {
|
|
2811
3608
|
if (input.target.loader.type !== Loaders.FORGE) {
|
|
2812
3609
|
throw new MinecraftKitError(
|
|
2813
|
-
|
|
3610
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2814
3611
|
`verify.forge requires a Forge target (got ${input.target.loader.type})`
|
|
2815
3612
|
);
|
|
2816
3613
|
}
|
|
@@ -2833,10 +3630,8 @@ async function verifyForge(input) {
|
|
|
2833
3630
|
})
|
|
2834
3631
|
);
|
|
2835
3632
|
if (!await fileExists(forgeVersionJsonPath)) return;
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
parsed = JSON.parse(await readText(forgeVersionJsonPath));
|
|
2839
|
-
} catch {
|
|
3633
|
+
const parsed = parseJsonOrUndefined(await readText(forgeVersionJsonPath));
|
|
3634
|
+
if (parsed === void 0) {
|
|
2840
3635
|
record({
|
|
2841
3636
|
path: forgeVersionJsonPath,
|
|
2842
3637
|
category: VerifyFileCategories.LOADER_LIBRARY,
|
|
@@ -2849,14 +3644,14 @@ async function verifyForge(input) {
|
|
|
2849
3644
|
directory: input.target.directory,
|
|
2850
3645
|
system: input.target.runtime.system,
|
|
2851
3646
|
versionId: input.target.minecraft.version,
|
|
2852
|
-
category:
|
|
3647
|
+
category: DownloadCategories.FORGE_LIBRARY
|
|
2853
3648
|
});
|
|
2854
3649
|
for (const action of forgeLibraries.downloads) {
|
|
2855
3650
|
record(
|
|
2856
3651
|
await verifyHashedFile({
|
|
2857
3652
|
path: action.target,
|
|
2858
|
-
expectedSha1: action.expectedSha1,
|
|
2859
|
-
expectedSize: action.expectedSize,
|
|
3653
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
3654
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
2860
3655
|
...action.url ? { url: action.url } : {},
|
|
2861
3656
|
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2862
3657
|
})
|
|
@@ -2864,10 +3659,10 @@ async function verifyForge(input) {
|
|
|
2864
3659
|
}
|
|
2865
3660
|
}
|
|
2866
3661
|
);
|
|
2867
|
-
}
|
|
3662
|
+
};
|
|
2868
3663
|
|
|
2869
3664
|
// src/verify/minecraft.ts
|
|
2870
|
-
async
|
|
3665
|
+
var verifyMinecraft = async (input) => {
|
|
2871
3666
|
return runVerification(
|
|
2872
3667
|
{
|
|
2873
3668
|
targetId: input.target.id,
|
|
@@ -2908,14 +3703,14 @@ async function verifyMinecraft(input) {
|
|
|
2908
3703
|
directory,
|
|
2909
3704
|
system: runtime.system,
|
|
2910
3705
|
versionId: minecraft.version,
|
|
2911
|
-
category:
|
|
3706
|
+
category: DownloadCategories.LIBRARY
|
|
2912
3707
|
});
|
|
2913
3708
|
for (const action of libraryPlan.downloads) {
|
|
2914
3709
|
record(
|
|
2915
3710
|
await verifyHashedFile({
|
|
2916
3711
|
path: action.target,
|
|
2917
|
-
expectedSha1: action.expectedSha1,
|
|
2918
|
-
expectedSize: action.expectedSize,
|
|
3712
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
3713
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
2919
3714
|
url: action.url,
|
|
2920
3715
|
category: VerifyFileCategories.LIBRARY
|
|
2921
3716
|
})
|
|
@@ -2962,8 +3757,8 @@ async function verifyMinecraft(input) {
|
|
|
2962
3757
|
}
|
|
2963
3758
|
}
|
|
2964
3759
|
);
|
|
2965
|
-
}
|
|
2966
|
-
async
|
|
3760
|
+
};
|
|
3761
|
+
var verifyRuntime = async (input) => {
|
|
2967
3762
|
return runVerification(
|
|
2968
3763
|
{
|
|
2969
3764
|
targetId: input.target.id,
|
|
@@ -3005,13 +3800,13 @@ async function verifyRuntime(input) {
|
|
|
3005
3800
|
}
|
|
3006
3801
|
}
|
|
3007
3802
|
);
|
|
3008
|
-
}
|
|
3803
|
+
};
|
|
3009
3804
|
|
|
3010
3805
|
// src/repair/helpers.ts
|
|
3011
|
-
|
|
3806
|
+
var asResultArray = (from) => {
|
|
3012
3807
|
return Array.isArray(from) ? from : [from];
|
|
3013
|
-
}
|
|
3014
|
-
|
|
3808
|
+
};
|
|
3809
|
+
var buildIssueIndex = (from) => {
|
|
3015
3810
|
const map = /* @__PURE__ */ new Map();
|
|
3016
3811
|
for (const v of asResultArray(from)) {
|
|
3017
3812
|
for (const issue of v.issues) {
|
|
@@ -3032,16 +3827,16 @@ function buildIssueIndex(from) {
|
|
|
3032
3827
|
},
|
|
3033
3828
|
categoriesAt: (path13) => map.get(path13) ?? /* @__PURE__ */ new Set()
|
|
3034
3829
|
};
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3830
|
+
};
|
|
3831
|
+
var sumDownloadBytes = (actions) => {
|
|
3037
3832
|
return actions.reduce((sum, action) => {
|
|
3038
3833
|
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3039
3834
|
return sum + (action.expectedSize ?? 0);
|
|
3040
3835
|
}
|
|
3041
3836
|
return sum;
|
|
3042
3837
|
}, 0);
|
|
3043
|
-
}
|
|
3044
|
-
|
|
3838
|
+
};
|
|
3839
|
+
var buildRepairPlan = (target, actions) => {
|
|
3045
3840
|
return {
|
|
3046
3841
|
targetId: target.id,
|
|
3047
3842
|
directory: target.directory,
|
|
@@ -3050,8 +3845,8 @@ function buildRepairPlan(target, actions) {
|
|
|
3050
3845
|
totalActions: actions.length,
|
|
3051
3846
|
totalBytes: sumDownloadBytes(actions)
|
|
3052
3847
|
};
|
|
3053
|
-
}
|
|
3054
|
-
async
|
|
3848
|
+
};
|
|
3849
|
+
var planAspectRepair = async (input, aspectFilter, postprocess) => {
|
|
3055
3850
|
const installPlan = await planInstall({
|
|
3056
3851
|
target: input.target,
|
|
3057
3852
|
http: input.http,
|
|
@@ -3067,8 +3862,8 @@ async function planAspectRepair(input, aspectFilter, postprocess) {
|
|
|
3067
3862
|
});
|
|
3068
3863
|
postprocess?.({ actions, installPlan, issues });
|
|
3069
3864
|
return buildRepairPlan(input.target, actions);
|
|
3070
|
-
}
|
|
3071
|
-
|
|
3865
|
+
};
|
|
3866
|
+
var selectRepairActions = (input) => {
|
|
3072
3867
|
const matching = [];
|
|
3073
3868
|
for (const action of input.installPlan.actions) {
|
|
3074
3869
|
if (!input.aspectFilter(action)) continue;
|
|
@@ -3089,13 +3884,13 @@ function selectRepairActions(input) {
|
|
|
3089
3884
|
}
|
|
3090
3885
|
}
|
|
3091
3886
|
return matching;
|
|
3092
|
-
}
|
|
3887
|
+
};
|
|
3093
3888
|
|
|
3094
3889
|
// src/repair/fabric.ts
|
|
3095
|
-
async
|
|
3890
|
+
var planFabricRepair = async (input) => {
|
|
3096
3891
|
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
3097
3892
|
throw new MinecraftKitError(
|
|
3098
|
-
|
|
3893
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
3099
3894
|
`repair.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
3100
3895
|
);
|
|
3101
3896
|
}
|
|
@@ -3105,24 +3900,24 @@ async function planFabricRepair(input) {
|
|
|
3105
3900
|
);
|
|
3106
3901
|
return planAspectRepair(input, (action) => {
|
|
3107
3902
|
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3108
|
-
return action.category ===
|
|
3903
|
+
return action.category === DownloadCategories.FABRIC_LIBRARY;
|
|
3109
3904
|
}
|
|
3110
3905
|
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3111
3906
|
return action.path === fabricJsonPath;
|
|
3112
3907
|
}
|
|
3113
3908
|
return false;
|
|
3114
3909
|
});
|
|
3115
|
-
}
|
|
3910
|
+
};
|
|
3116
3911
|
|
|
3117
3912
|
// src/repair/forge.ts
|
|
3118
3913
|
var FORGE_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3119
|
-
|
|
3120
|
-
|
|
3914
|
+
DownloadCategories.FORGE_LIBRARY,
|
|
3915
|
+
DownloadCategories.FORGE_INSTALLER
|
|
3121
3916
|
]);
|
|
3122
|
-
async
|
|
3917
|
+
var planForgeRepair = async (input) => {
|
|
3123
3918
|
if (input.target.loader.type !== Loaders.FORGE) {
|
|
3124
3919
|
throw new MinecraftKitError(
|
|
3125
|
-
|
|
3920
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
3126
3921
|
`repair.forge requires a Forge target (got ${input.target.loader.type})`
|
|
3127
3922
|
);
|
|
3128
3923
|
}
|
|
@@ -3147,7 +3942,7 @@ async function planForgeRepair(input) {
|
|
|
3147
3942
|
actions.filter((a) => a.kind === InstallActionKinds.DOWNLOAD_FILE).map((a) => a.target)
|
|
3148
3943
|
);
|
|
3149
3944
|
for (const action of installPlan.actions) {
|
|
3150
|
-
if (action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category ===
|
|
3945
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category === DownloadCategories.FORGE_LIBRARY && !alreadyIncluded.has(action.target)) {
|
|
3151
3946
|
actions.push(action);
|
|
3152
3947
|
} else if (action.kind === InstallActionKinds.RUN_FORGE_PROCESSOR) {
|
|
3153
3948
|
actions.push(action);
|
|
@@ -3155,17 +3950,17 @@ async function planForgeRepair(input) {
|
|
|
3155
3950
|
}
|
|
3156
3951
|
}
|
|
3157
3952
|
);
|
|
3158
|
-
}
|
|
3953
|
+
};
|
|
3159
3954
|
|
|
3160
3955
|
// src/repair/minecraft.ts
|
|
3161
3956
|
var MINECRAFT_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3957
|
+
DownloadCategories.CLIENT_JAR,
|
|
3958
|
+
DownloadCategories.LIBRARY,
|
|
3959
|
+
DownloadCategories.ASSET_INDEX,
|
|
3960
|
+
DownloadCategories.ASSET,
|
|
3961
|
+
DownloadCategories.LOGGING_CONFIG
|
|
3167
3962
|
]);
|
|
3168
|
-
async
|
|
3963
|
+
var planMinecraftRepair = async (input) => {
|
|
3169
3964
|
const vanillaJsonPath = targetPaths.versionJson(
|
|
3170
3965
|
input.target.directory,
|
|
3171
3966
|
input.target.minecraft.version
|
|
@@ -3182,10 +3977,10 @@ async function planMinecraftRepair(input) {
|
|
|
3182
3977
|
}
|
|
3183
3978
|
return false;
|
|
3184
3979
|
});
|
|
3185
|
-
}
|
|
3980
|
+
};
|
|
3186
3981
|
|
|
3187
3982
|
// src/repair/runner.ts
|
|
3188
|
-
async
|
|
3983
|
+
var runRepair = async (input) => {
|
|
3189
3984
|
const report = await runInstall({
|
|
3190
3985
|
plan: {
|
|
3191
3986
|
...input.plan,
|
|
@@ -3204,18 +3999,18 @@ async function runRepair(input) {
|
|
|
3204
3999
|
actionsCompleted: report.actionsCompleted,
|
|
3205
4000
|
durationMs: report.durationMs
|
|
3206
4001
|
};
|
|
3207
|
-
}
|
|
4002
|
+
};
|
|
3208
4003
|
|
|
3209
4004
|
// src/repair/runtime.ts
|
|
3210
|
-
async
|
|
4005
|
+
var planRuntimeRepair = async (input) => {
|
|
3211
4006
|
return planAspectRepair(
|
|
3212
4007
|
input,
|
|
3213
|
-
(action) => action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category ===
|
|
4008
|
+
(action) => action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category === DownloadCategories.RUNTIME_FILE
|
|
3214
4009
|
);
|
|
3215
|
-
}
|
|
4010
|
+
};
|
|
3216
4011
|
|
|
3217
4012
|
// src/repair/all.ts
|
|
3218
|
-
async
|
|
4013
|
+
var repairAll = async (input) => {
|
|
3219
4014
|
const startedAt = Date.now();
|
|
3220
4015
|
const ctx = {
|
|
3221
4016
|
target: input.target,
|
|
@@ -3258,7 +4053,7 @@ async function repairAll(input) {
|
|
|
3258
4053
|
bytesDownloaded,
|
|
3259
4054
|
durationMs: Date.now() - startedAt
|
|
3260
4055
|
};
|
|
3261
|
-
}
|
|
4056
|
+
};
|
|
3262
4057
|
var PLANNERS = {
|
|
3263
4058
|
minecraft: planMinecraftRepair,
|
|
3264
4059
|
runtime: planRuntimeRepair,
|
|
@@ -3297,14 +4092,27 @@ var TargetsApi = class {
|
|
|
3297
4092
|
/** Build a {@link Target} from already-resolved components. */
|
|
3298
4093
|
create(input) {
|
|
3299
4094
|
if (!input.id) {
|
|
3300
|
-
throw new MinecraftKitError(
|
|
4095
|
+
throw new MinecraftKitError(
|
|
4096
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
4097
|
+
"Target id must be non-empty"
|
|
4098
|
+
);
|
|
3301
4099
|
}
|
|
3302
4100
|
if (!input.directory) {
|
|
3303
|
-
throw new MinecraftKitError(
|
|
4101
|
+
throw new MinecraftKitError(
|
|
4102
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
4103
|
+
"Target directory must be non-empty"
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
4106
|
+
if (!path.isAbsolute(input.directory)) {
|
|
4107
|
+
throw new MinecraftKitError(
|
|
4108
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
4109
|
+
`Target directory must be an absolute path, got: ${input.directory}`,
|
|
4110
|
+
{ context: { directory: input.directory } }
|
|
4111
|
+
);
|
|
3304
4112
|
}
|
|
3305
4113
|
if (input.loader.minecraftVersion !== input.minecraft.version) {
|
|
3306
4114
|
throw new MinecraftKitError(
|
|
3307
|
-
|
|
4115
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
3308
4116
|
`Loader Minecraft version (${input.loader.minecraftVersion}) does not match resolved Minecraft (${input.minecraft.version})`,
|
|
3309
4117
|
{
|
|
3310
4118
|
context: {
|
|
@@ -3381,7 +4189,7 @@ var TargetsApi = class {
|
|
|
3381
4189
|
return results;
|
|
3382
4190
|
}
|
|
3383
4191
|
};
|
|
3384
|
-
async
|
|
4192
|
+
var discoverInstallation = async (id, directory) => {
|
|
3385
4193
|
const versionsDir = path.join(directory, VERSIONS_DIR);
|
|
3386
4194
|
const librariesDir = path.join(directory, LIBRARIES_DIR);
|
|
3387
4195
|
const assetsDir = path.join(directory, ASSETS_DIR);
|
|
@@ -3403,8 +4211,8 @@ async function discoverInstallation(id, directory) {
|
|
|
3403
4211
|
}
|
|
3404
4212
|
const runtime = await discoverRuntime(directory);
|
|
3405
4213
|
return { id, directory, minecraftVersions, loaders, ...runtime ? { runtime } : {} };
|
|
3406
|
-
}
|
|
3407
|
-
async
|
|
4214
|
+
};
|
|
4215
|
+
var discoverRuntime = async (directory) => {
|
|
3408
4216
|
const runtimeDir = path.join(directory, RUNTIMES_DIR);
|
|
3409
4217
|
if (!await dirExists(runtimeDir)) return void 0;
|
|
3410
4218
|
let components;
|
|
@@ -3415,14 +4223,14 @@ async function discoverRuntime(directory) {
|
|
|
3415
4223
|
}
|
|
3416
4224
|
for (const component of components) {
|
|
3417
4225
|
const root = path.join(runtimeDir, component);
|
|
3418
|
-
const javaPath =
|
|
4226
|
+
const javaPath = javaExecutablePath(root);
|
|
3419
4227
|
if (await fileExists(javaPath)) {
|
|
3420
4228
|
return { component, javaPath };
|
|
3421
4229
|
}
|
|
3422
4230
|
}
|
|
3423
4231
|
return void 0;
|
|
3424
|
-
}
|
|
3425
|
-
|
|
4232
|
+
};
|
|
4233
|
+
var inferLoaderFromVersionId = (versionId) => {
|
|
3426
4234
|
const fabricMatch = /^fabric-loader-([^-]+)-(.+)$/.exec(versionId);
|
|
3427
4235
|
if (fabricMatch?.[1] && fabricMatch[2]) {
|
|
3428
4236
|
return { type: Loaders.FABRIC, version: fabricMatch[1], minecraftVersion: fabricMatch[2] };
|
|
@@ -3432,18 +4240,25 @@ function inferLoaderFromVersionId(versionId) {
|
|
|
3432
4240
|
return { type: Loaders.FORGE, minecraftVersion: forgeMatch[1], version: forgeMatch[2] };
|
|
3433
4241
|
}
|
|
3434
4242
|
return null;
|
|
3435
|
-
}
|
|
4243
|
+
};
|
|
4244
|
+
var javaExecutablePath = (runtimeRoot) => {
|
|
4245
|
+
if (process.platform === "win32") return path.join(runtimeRoot, "bin", "javaw.exe");
|
|
4246
|
+
if (process.platform === "darwin") {
|
|
4247
|
+
return path.join(runtimeRoot, "jre.bundle", "Contents", "Home", "bin", "java");
|
|
4248
|
+
}
|
|
4249
|
+
return path.join(runtimeRoot, "bin", "java");
|
|
4250
|
+
};
|
|
3436
4251
|
|
|
3437
4252
|
// src/update/runner.ts
|
|
3438
|
-
async
|
|
4253
|
+
var planUpdate = async (input) => {
|
|
3439
4254
|
return planInstall({
|
|
3440
4255
|
target: input.target,
|
|
3441
4256
|
http: input.http,
|
|
3442
4257
|
cache: input.cache,
|
|
3443
4258
|
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3444
4259
|
});
|
|
3445
|
-
}
|
|
3446
|
-
async
|
|
4260
|
+
};
|
|
4261
|
+
var runUpdate = async (input) => {
|
|
3447
4262
|
const report = await runInstall({
|
|
3448
4263
|
plan: input.plan,
|
|
3449
4264
|
http: input.http,
|
|
@@ -3459,7 +4274,7 @@ async function runUpdate(input) {
|
|
|
3459
4274
|
actionsSkipped: report.actionsSkipped,
|
|
3460
4275
|
durationMs: report.durationMs
|
|
3461
4276
|
};
|
|
3462
|
-
}
|
|
4277
|
+
};
|
|
3463
4278
|
|
|
3464
4279
|
// src/versions/fabric.ts
|
|
3465
4280
|
var FabricVersionsApi = class {
|
|
@@ -3495,7 +4310,7 @@ var FabricVersionsApi = class {
|
|
|
3495
4310
|
});
|
|
3496
4311
|
if (loaders.length === 0) {
|
|
3497
4312
|
throw new MinecraftKitError(
|
|
3498
|
-
|
|
4313
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3499
4314
|
`No Fabric loader available for Minecraft ${input.minecraftVersion}`,
|
|
3500
4315
|
{ context: { version: input.minecraftVersion } }
|
|
3501
4316
|
);
|
|
@@ -3503,9 +4318,11 @@ var FabricVersionsApi = class {
|
|
|
3503
4318
|
const chosen = pickFabricLoader(loaders, input);
|
|
3504
4319
|
if (!chosen) {
|
|
3505
4320
|
throw new MinecraftKitError(
|
|
3506
|
-
|
|
4321
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3507
4322
|
`Fabric loader version not found: ${input.loaderVersion ?? "(none matched)"}`,
|
|
3508
|
-
{
|
|
4323
|
+
{
|
|
4324
|
+
context: input.loaderVersion !== void 0 ? { version: input.loaderVersion } : {}
|
|
4325
|
+
}
|
|
3509
4326
|
);
|
|
3510
4327
|
}
|
|
3511
4328
|
const profile = await fetchJson(this.ctx.http, this.ctx.cache, {
|
|
@@ -3521,7 +4338,7 @@ var FabricVersionsApi = class {
|
|
|
3521
4338
|
};
|
|
3522
4339
|
}
|
|
3523
4340
|
};
|
|
3524
|
-
|
|
4341
|
+
var pickFabricLoader = (loaders, input) => {
|
|
3525
4342
|
if (input.loaderVersion !== void 0) {
|
|
3526
4343
|
return loaders.find((l) => l.version === input.loaderVersion);
|
|
3527
4344
|
}
|
|
@@ -3531,17 +4348,17 @@ function pickFabricLoader(loaders, input) {
|
|
|
3531
4348
|
if (stable) return stable;
|
|
3532
4349
|
}
|
|
3533
4350
|
return loaders[0];
|
|
3534
|
-
}
|
|
4351
|
+
};
|
|
3535
4352
|
|
|
3536
4353
|
// src/core/xml.ts
|
|
3537
|
-
|
|
4354
|
+
var parseMavenMetadataVersions = (xml) => {
|
|
3538
4355
|
const versions = [];
|
|
3539
4356
|
const regex = /<version>\s*([^<]+?)\s*<\/version>/g;
|
|
3540
4357
|
for (const match of xml.matchAll(regex)) {
|
|
3541
4358
|
if (match[1]) versions.push(match[1]);
|
|
3542
4359
|
}
|
|
3543
4360
|
return versions;
|
|
3544
|
-
}
|
|
4361
|
+
};
|
|
3545
4362
|
|
|
3546
4363
|
// src/versions/forge.ts
|
|
3547
4364
|
var ForgeVersionsApi = class {
|
|
@@ -3574,7 +4391,7 @@ var ForgeVersionsApi = class {
|
|
|
3574
4391
|
});
|
|
3575
4392
|
if (builds.length === 0) {
|
|
3576
4393
|
throw new MinecraftKitError(
|
|
3577
|
-
|
|
4394
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3578
4395
|
`No Forge build available for Minecraft ${input.minecraftVersion}`,
|
|
3579
4396
|
{ context: { version: input.minecraftVersion } }
|
|
3580
4397
|
);
|
|
@@ -3582,9 +4399,11 @@ var ForgeVersionsApi = class {
|
|
|
3582
4399
|
const chosen = pickForge(builds, input);
|
|
3583
4400
|
if (!chosen) {
|
|
3584
4401
|
throw new MinecraftKitError(
|
|
3585
|
-
|
|
4402
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3586
4403
|
`Forge build not found for ${input.minecraftVersion}: ${input.forgeVersion ?? "(none matched)"}`,
|
|
3587
|
-
{
|
|
4404
|
+
{
|
|
4405
|
+
context: input.forgeVersion !== void 0 ? { version: input.forgeVersion } : {}
|
|
4406
|
+
}
|
|
3588
4407
|
);
|
|
3589
4408
|
}
|
|
3590
4409
|
return {
|
|
@@ -3596,7 +4415,7 @@ var ForgeVersionsApi = class {
|
|
|
3596
4415
|
};
|
|
3597
4416
|
}
|
|
3598
4417
|
};
|
|
3599
|
-
|
|
4418
|
+
var buildSummary = (fullVersion, promotions) => {
|
|
3600
4419
|
const dashIndex = fullVersion.indexOf("-");
|
|
3601
4420
|
if (dashIndex <= 0 || dashIndex === fullVersion.length - 1) return null;
|
|
3602
4421
|
const minecraftVersion = fullVersion.slice(0, dashIndex);
|
|
@@ -3611,8 +4430,8 @@ function buildSummary(fullVersion, promotions) {
|
|
|
3611
4430
|
isRecommended: recommended === forgeVersion,
|
|
3612
4431
|
isLatest: latest === forgeVersion
|
|
3613
4432
|
};
|
|
3614
|
-
}
|
|
3615
|
-
|
|
4433
|
+
};
|
|
4434
|
+
var pickForge = (builds, input) => {
|
|
3616
4435
|
if (input.forgeVersion !== void 0) {
|
|
3617
4436
|
return builds.find(
|
|
3618
4437
|
(b) => b.forgeVersion === input.forgeVersion || b.fullVersion === input.forgeVersion
|
|
@@ -3626,7 +4445,30 @@ function pickForge(builds, input) {
|
|
|
3626
4445
|
const latest = builds.find((b) => b.isLatest);
|
|
3627
4446
|
if (latest) return latest;
|
|
3628
4447
|
return builds[builds.length - 1];
|
|
3629
|
-
}
|
|
4448
|
+
};
|
|
4449
|
+
|
|
4450
|
+
// src/core/guards.ts
|
|
4451
|
+
var isPlainObject = (value) => {
|
|
4452
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4453
|
+
};
|
|
4454
|
+
var isNonEmptyString = (value) => {
|
|
4455
|
+
return typeof value === "string" && value.length > 0;
|
|
4456
|
+
};
|
|
4457
|
+
var isArtifactDownload = (value) => {
|
|
4458
|
+
if (!isPlainObject(value)) return false;
|
|
4459
|
+
return typeof value.sha1 === "string" && typeof value.size === "number" && isNonEmptyString(value.url);
|
|
4460
|
+
};
|
|
4461
|
+
var isMinecraftVersionManifestShape = (value) => {
|
|
4462
|
+
if (!isPlainObject(value)) return false;
|
|
4463
|
+
if (!isNonEmptyString(value.id)) return false;
|
|
4464
|
+
if (!isNonEmptyString(value.mainClass)) return false;
|
|
4465
|
+
if (!isPlainObject(value.assetIndex)) return false;
|
|
4466
|
+
if (!isNonEmptyString(value.assetIndex.id) || typeof value.assetIndex.sha1 !== "string" || typeof value.assetIndex.size !== "number" || !isNonEmptyString(value.assetIndex.url)) {
|
|
4467
|
+
return false;
|
|
4468
|
+
}
|
|
4469
|
+
if (!isPlainObject(value.downloads)) return false;
|
|
4470
|
+
return isArtifactDownload(value.downloads.client);
|
|
4471
|
+
};
|
|
3630
4472
|
|
|
3631
4473
|
// src/versions/minecraft.ts
|
|
3632
4474
|
var MinecraftVersionsApi = class {
|
|
@@ -3647,7 +4489,7 @@ var MinecraftVersionsApi = class {
|
|
|
3647
4489
|
const summary = root.versions.find((v) => v.id === targetId);
|
|
3648
4490
|
if (!summary) {
|
|
3649
4491
|
throw new MinecraftKitError(
|
|
3650
|
-
|
|
4492
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3651
4493
|
`Latest version ${targetId} not found in manifest`
|
|
3652
4494
|
);
|
|
3653
4495
|
}
|
|
@@ -3659,7 +4501,7 @@ var MinecraftVersionsApi = class {
|
|
|
3659
4501
|
const summary = root.versions.find((v) => v.id === input.version);
|
|
3660
4502
|
if (!summary) {
|
|
3661
4503
|
throw new MinecraftKitError(
|
|
3662
|
-
|
|
4504
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3663
4505
|
`Minecraft version not found: ${input.version}`,
|
|
3664
4506
|
{ context: { version: input.version } }
|
|
3665
4507
|
);
|
|
@@ -3669,18 +4511,19 @@ var MinecraftVersionsApi = class {
|
|
|
3669
4511
|
/** Fetch and parse the per-version manifest in addition to the summary. */
|
|
3670
4512
|
async resolve(input) {
|
|
3671
4513
|
const summary = await this.get(input);
|
|
3672
|
-
const
|
|
4514
|
+
const raw = await fetchJson(this.ctx.http, this.ctx.cache, {
|
|
3673
4515
|
url: summary.url,
|
|
3674
4516
|
cacheKey: `minecraft-manifest:${summary.id}:${summary.sha1}`,
|
|
3675
4517
|
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3676
4518
|
});
|
|
3677
|
-
if (!
|
|
4519
|
+
if (!isMinecraftVersionManifestShape(raw)) {
|
|
3678
4520
|
throw new MinecraftKitError(
|
|
3679
|
-
|
|
3680
|
-
`Per-version manifest
|
|
4521
|
+
MinecraftKitErrorCodes.MANIFEST_INVALID,
|
|
4522
|
+
`Per-version manifest does not match the expected shape: ${summary.id}`,
|
|
3681
4523
|
{ context: { version: summary.id, url: summary.url } }
|
|
3682
4524
|
);
|
|
3683
4525
|
}
|
|
4526
|
+
const manifest = raw;
|
|
3684
4527
|
return {
|
|
3685
4528
|
version: summary.id,
|
|
3686
4529
|
channel: summary.type,
|
|
@@ -3733,7 +4576,7 @@ var RuntimeVersionsApi = class {
|
|
|
3733
4576
|
const platform = index[platformKey];
|
|
3734
4577
|
if (!platform) {
|
|
3735
4578
|
throw new MinecraftKitError(
|
|
3736
|
-
|
|
4579
|
+
MinecraftKitErrorCodes.RUNTIME_UNSUPPORTED_PLATFORM,
|
|
3737
4580
|
`No runtimes published for platform: ${platformKey}`,
|
|
3738
4581
|
{ context: { platform: platformKey } }
|
|
3739
4582
|
);
|
|
@@ -3750,7 +4593,7 @@ var RuntimeVersionsApi = class {
|
|
|
3750
4593
|
}
|
|
3751
4594
|
}
|
|
3752
4595
|
throw new MinecraftKitError(
|
|
3753
|
-
|
|
4596
|
+
MinecraftKitErrorCodes.RUNTIME_NOT_FOUND,
|
|
3754
4597
|
`Runtime component ${component} not available on ${platformKey}`,
|
|
3755
4598
|
{ context: { platform: platformKey, version: component } }
|
|
3756
4599
|
);
|
|
@@ -3758,7 +4601,7 @@ var RuntimeVersionsApi = class {
|
|
|
3758
4601
|
const entry = candidates[0];
|
|
3759
4602
|
if (!entry) {
|
|
3760
4603
|
throw new MinecraftKitError(
|
|
3761
|
-
|
|
4604
|
+
MinecraftKitErrorCodes.RUNTIME_NOT_FOUND,
|
|
3762
4605
|
`Runtime component ${component} list is empty for ${platformKey}`,
|
|
3763
4606
|
{ context: { platform: platformKey, version: component } }
|
|
3764
4607
|
);
|
|
@@ -3773,11 +4616,11 @@ var RuntimeVersionsApi = class {
|
|
|
3773
4616
|
});
|
|
3774
4617
|
}
|
|
3775
4618
|
};
|
|
3776
|
-
|
|
4619
|
+
var pickPlatformKey = (system) => {
|
|
3777
4620
|
const archMap = RUNTIME_PLATFORM_KEYS[system.os];
|
|
3778
4621
|
return archMap[system.arch];
|
|
3779
|
-
}
|
|
3780
|
-
|
|
4622
|
+
};
|
|
4623
|
+
var pickLatestAcrossComponents = (entries) => {
|
|
3781
4624
|
let bestComponent = null;
|
|
3782
4625
|
let bestEntry = null;
|
|
3783
4626
|
for (const [component, list] of entries) {
|
|
@@ -3790,8 +4633,8 @@ function pickLatestAcrossComponents(entries) {
|
|
|
3790
4633
|
}
|
|
3791
4634
|
if (!bestComponent || !bestEntry) return null;
|
|
3792
4635
|
return { component: bestComponent, entry: bestEntry };
|
|
3793
|
-
}
|
|
3794
|
-
|
|
4636
|
+
};
|
|
4637
|
+
var toResolved = (component, platformKey, entry, system) => {
|
|
3795
4638
|
const majorVersion = parseMajorVersion(entry.version.name);
|
|
3796
4639
|
return {
|
|
3797
4640
|
component,
|
|
@@ -3802,13 +4645,13 @@ function toResolved(component, platformKey, entry, system) {
|
|
|
3802
4645
|
manifestUrl: entry.manifest.url,
|
|
3803
4646
|
manifestSha1: entry.manifest.sha1
|
|
3804
4647
|
};
|
|
3805
|
-
}
|
|
3806
|
-
|
|
4648
|
+
};
|
|
4649
|
+
var parseMajorVersion = (versionName) => {
|
|
3807
4650
|
const match = /^(\d+)/.exec(versionName);
|
|
3808
4651
|
if (!match || !match[1]) return void 0;
|
|
3809
4652
|
const parsed = Number.parseInt(match[1], 10);
|
|
3810
4653
|
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3811
|
-
}
|
|
4654
|
+
};
|
|
3812
4655
|
|
|
3813
4656
|
// src/kit.ts
|
|
3814
4657
|
var MinecraftKit = class {
|
|
@@ -3819,6 +4662,12 @@ var MinecraftKit = class {
|
|
|
3819
4662
|
verify;
|
|
3820
4663
|
repair;
|
|
3821
4664
|
launch;
|
|
4665
|
+
/**
|
|
4666
|
+
* Microsoft / Mojang authentication. Implements the device-code flow against Microsoft
|
|
4667
|
+
* Entra, exchanges the resulting tokens for an XSTS + Minecraft session, and returns
|
|
4668
|
+
* everything needed to compose an `OnlineAuth` for `launch.compose`.
|
|
4669
|
+
*/
|
|
4670
|
+
auth;
|
|
3822
4671
|
/** Cache surface useful for advanced consumers (e.g. clearing between operations). */
|
|
3823
4672
|
cache;
|
|
3824
4673
|
constructor(options = {}) {
|
|
@@ -3834,6 +4683,7 @@ var MinecraftKit = class {
|
|
|
3834
4683
|
const runtime = new RuntimeVersionsApi(ctx);
|
|
3835
4684
|
this.versions = { minecraft, fabric, forge, runtime };
|
|
3836
4685
|
this.targets = new TargetsApi({ minecraft, fabric, forge, runtime, system });
|
|
4686
|
+
this.auth = new MojangAuthApi(http);
|
|
3837
4687
|
this.cache = cache;
|
|
3838
4688
|
const carry = (opts) => ({
|
|
3839
4689
|
...opts?.signal !== void 0 ? { signal: opts.signal } : {},
|
|
@@ -3914,26 +4764,9 @@ var MinecraftKit = class {
|
|
|
3914
4764
|
}
|
|
3915
4765
|
};
|
|
3916
4766
|
|
|
3917
|
-
// src/core/
|
|
3918
|
-
var
|
|
3919
|
-
|
|
3920
|
-
#waiters = [];
|
|
3921
|
-
get paused() {
|
|
3922
|
-
return this.#paused;
|
|
3923
|
-
}
|
|
3924
|
-
pause() {
|
|
3925
|
-
this.#paused = true;
|
|
3926
|
-
}
|
|
3927
|
-
resume() {
|
|
3928
|
-
this.#paused = false;
|
|
3929
|
-
const list = this.#waiters;
|
|
3930
|
-
this.#waiters = [];
|
|
3931
|
-
for (const resolve of list) resolve();
|
|
3932
|
-
}
|
|
3933
|
-
waitWhilePaused() {
|
|
3934
|
-
if (!this.#paused) return Promise.resolve();
|
|
3935
|
-
return new Promise((resolve) => this.#waiters.push(resolve));
|
|
3936
|
-
}
|
|
4767
|
+
// src/core/assert-never.ts
|
|
4768
|
+
var assertNever = (value) => {
|
|
4769
|
+
throw new Error(`Unhandled variant: ${JSON.stringify(value)}`);
|
|
3937
4770
|
};
|
|
3938
4771
|
|
|
3939
4772
|
// src/install/progress-tracker.ts
|
|
@@ -3945,15 +4778,15 @@ var InstallStages = {
|
|
|
3945
4778
|
FINALIZE: "finalize"
|
|
3946
4779
|
};
|
|
3947
4780
|
var STAGE_FOR_CATEGORY = {
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
4781
|
+
[DownloadCategories.RUNTIME_FILE]: InstallStages.RUNTIME,
|
|
4782
|
+
[DownloadCategories.CLIENT_JAR]: InstallStages.MINECRAFT,
|
|
4783
|
+
[DownloadCategories.LIBRARY]: InstallStages.MINECRAFT,
|
|
4784
|
+
[DownloadCategories.ASSET_INDEX]: InstallStages.MINECRAFT,
|
|
4785
|
+
[DownloadCategories.ASSET]: InstallStages.MINECRAFT,
|
|
4786
|
+
[DownloadCategories.LOGGING_CONFIG]: InstallStages.MINECRAFT,
|
|
4787
|
+
[DownloadCategories.FABRIC_LIBRARY]: InstallStages.LOADER,
|
|
4788
|
+
[DownloadCategories.FORGE_LIBRARY]: InstallStages.LOADER,
|
|
4789
|
+
[DownloadCategories.FORGE_INSTALLER]: InstallStages.LOADER
|
|
3957
4790
|
};
|
|
3958
4791
|
var STAGE_FOR_PHASE = {
|
|
3959
4792
|
[InstallPhases.PLANNING]: InstallStages.PREPARE,
|
|
@@ -3977,7 +4810,7 @@ var ALL_STAGES = [
|
|
|
3977
4810
|
InstallStages.LOADER,
|
|
3978
4811
|
InstallStages.FINALIZE
|
|
3979
4812
|
];
|
|
3980
|
-
|
|
4813
|
+
var createInstallProgressTracker = (plan, options = {}) => {
|
|
3981
4814
|
const throttleMs = options.throttleMs ?? 100;
|
|
3982
4815
|
const stageOfTarget = /* @__PURE__ */ new Map();
|
|
3983
4816
|
const expectedSizeOf = /* @__PURE__ */ new Map();
|
|
@@ -4061,7 +4894,7 @@ function createInstallProgressTracker(plan, options = {}) {
|
|
|
4061
4894
|
};
|
|
4062
4895
|
const onEvent = (event) => {
|
|
4063
4896
|
switch (event.type) {
|
|
4064
|
-
case
|
|
4897
|
+
case EventTypes.INSTALL_PHASE_CHANGED: {
|
|
4065
4898
|
const next = STAGE_FOR_PHASE[event.phase];
|
|
4066
4899
|
if (next && next !== currentStage) {
|
|
4067
4900
|
currentStage = next;
|
|
@@ -4070,14 +4903,14 @@ function createInstallProgressTracker(plan, options = {}) {
|
|
|
4070
4903
|
}
|
|
4071
4904
|
return;
|
|
4072
4905
|
}
|
|
4073
|
-
case
|
|
4906
|
+
case EventTypes.DOWNLOAD_STARTED: {
|
|
4074
4907
|
const stage = stageOfTarget.get(event.file.target) ?? currentStage;
|
|
4075
4908
|
inFlightByTarget.set(event.file.target, { stage, bytes: 0 });
|
|
4076
4909
|
currentFile = event.file.target;
|
|
4077
4910
|
schedulePush();
|
|
4078
4911
|
return;
|
|
4079
4912
|
}
|
|
4080
|
-
case
|
|
4913
|
+
case EventTypes.DOWNLOAD_PROGRESS: {
|
|
4081
4914
|
const entry = inFlightByTarget.get(event.file.target);
|
|
4082
4915
|
if (entry) {
|
|
4083
4916
|
const delta = event.bytesDownloaded - entry.bytes;
|
|
@@ -4091,7 +4924,7 @@ function createInstallProgressTracker(plan, options = {}) {
|
|
|
4091
4924
|
schedulePush();
|
|
4092
4925
|
return;
|
|
4093
4926
|
}
|
|
4094
|
-
case
|
|
4927
|
+
case EventTypes.DOWNLOAD_SKIPPED: {
|
|
4095
4928
|
const stage = stageOfTarget.get(event.file.target);
|
|
4096
4929
|
if (stage) {
|
|
4097
4930
|
const size = expectedSizeOf.get(event.file.target) ?? 0;
|
|
@@ -4101,7 +4934,7 @@ function createInstallProgressTracker(plan, options = {}) {
|
|
|
4101
4934
|
}
|
|
4102
4935
|
return;
|
|
4103
4936
|
}
|
|
4104
|
-
case
|
|
4937
|
+
case EventTypes.DOWNLOAD_COMPLETED: {
|
|
4105
4938
|
const entry = inFlightByTarget.get(event.file.target);
|
|
4106
4939
|
if (entry) {
|
|
4107
4940
|
const finalBytes = event.bytes ?? entry.bytes;
|
|
@@ -4148,36 +4981,60 @@ function createInstallProgressTracker(plan, options = {}) {
|
|
|
4148
4981
|
for (const listener of listeners) listener(snap);
|
|
4149
4982
|
}
|
|
4150
4983
|
};
|
|
4151
|
-
}
|
|
4152
|
-
|
|
4984
|
+
};
|
|
4985
|
+
var clamp = (value) => {
|
|
4153
4986
|
if (value <= 0) return 0;
|
|
4154
4987
|
if (value >= 100) return 100;
|
|
4155
4988
|
return value;
|
|
4156
|
-
}
|
|
4989
|
+
};
|
|
4157
4990
|
|
|
4158
|
-
// src/
|
|
4159
|
-
var
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4991
|
+
// src/core/pause-controller.ts
|
|
4992
|
+
var PauseController = class {
|
|
4993
|
+
#paused = false;
|
|
4994
|
+
#waiters = [];
|
|
4995
|
+
get paused() {
|
|
4996
|
+
return this.#paused;
|
|
4997
|
+
}
|
|
4998
|
+
pause() {
|
|
4999
|
+
this.#paused = true;
|
|
5000
|
+
}
|
|
5001
|
+
resume() {
|
|
5002
|
+
this.#paused = false;
|
|
5003
|
+
const list = this.#waiters;
|
|
5004
|
+
this.#waiters = [];
|
|
5005
|
+
for (const resolve of list) resolve();
|
|
5006
|
+
}
|
|
5007
|
+
waitWhilePaused() {
|
|
5008
|
+
if (!this.#paused) return Promise.resolve();
|
|
5009
|
+
return new Promise((resolve) => this.#waiters.push(resolve));
|
|
5010
|
+
}
|
|
5011
|
+
};
|
|
5012
|
+
|
|
5013
|
+
// src/constants/placeholders.ts
|
|
5014
|
+
var LAUNCH_PLACEHOLDERS = {
|
|
5015
|
+
"${auth_player_name}": "Player display name.",
|
|
5016
|
+
"${version_name}": "Resolved Minecraft version id.",
|
|
5017
|
+
"${game_directory}": "Per-target directory.",
|
|
5018
|
+
"${assets_root}": "Assets root (`<directory>/assets`).",
|
|
5019
|
+
"${assets_index_name}": "Asset index id from the manifest.",
|
|
5020
|
+
"${auth_uuid}": "Player UUID (no dashes).",
|
|
5021
|
+
"${auth_access_token}": "Yggdrasil/MSA access token.",
|
|
5022
|
+
"${auth_session}": "Legacy session token (`token:<token>:<uuid>`).",
|
|
5023
|
+
"${clientid}": "MSA client id.",
|
|
5024
|
+
"${auth_xuid}": "Xbox user id.",
|
|
5025
|
+
"${user_type}": "`msa` | `mojang` | `legacy`.",
|
|
5026
|
+
"${user_properties}": "User properties JSON (often `{}`).",
|
|
5027
|
+
"${version_type}": "Channel string, e.g. `release`.",
|
|
5028
|
+
"${game_assets}": "Legacy virtual assets directory.",
|
|
5029
|
+
"${resolution_width}": "Window width (feature-gated).",
|
|
5030
|
+
"${resolution_height}": "Window height (feature-gated).",
|
|
5031
|
+
"${natives_directory}": "Extracted natives directory.",
|
|
5032
|
+
"${classpath}": "Joined classpath of libraries + version jar.",
|
|
5033
|
+
"${classpath_separator}": "OS-specific classpath separator (`:` / `;`).",
|
|
5034
|
+
"${library_directory}": "Per-target libraries directory.",
|
|
5035
|
+
"${launcher_name}": "Launcher brand string.",
|
|
5036
|
+
"${launcher_version}": "Launcher version string.",
|
|
5037
|
+
"${path}": "Path to the log4j config file (logging.client.argument only)."
|
|
4181
5038
|
};
|
|
4182
5039
|
|
|
4183
5040
|
// src/types/minecraft.ts
|
|
@@ -4212,33 +5069,6 @@ var Architectures = {
|
|
|
4212
5069
|
ARM64: "arm64"
|
|
4213
5070
|
};
|
|
4214
5071
|
|
|
4215
|
-
|
|
4216
|
-
var LAUNCH_PLACEHOLDERS = {
|
|
4217
|
-
"${auth_player_name}": "Player display name.",
|
|
4218
|
-
"${version_name}": "Resolved Minecraft version id.",
|
|
4219
|
-
"${game_directory}": "Per-target directory.",
|
|
4220
|
-
"${assets_root}": "Assets root (`<directory>/assets`).",
|
|
4221
|
-
"${assets_index_name}": "Asset index id from the manifest.",
|
|
4222
|
-
"${auth_uuid}": "Player UUID (no dashes).",
|
|
4223
|
-
"${auth_access_token}": "Yggdrasil/MSA access token.",
|
|
4224
|
-
"${auth_session}": "Legacy session token (`token:<token>:<uuid>`).",
|
|
4225
|
-
"${clientid}": "MSA client id.",
|
|
4226
|
-
"${auth_xuid}": "Xbox user id.",
|
|
4227
|
-
"${user_type}": "`msa` | `mojang` | `legacy`.",
|
|
4228
|
-
"${user_properties}": "User properties JSON (often `{}`).",
|
|
4229
|
-
"${version_type}": "Channel string, e.g. `release`.",
|
|
4230
|
-
"${game_assets}": "Legacy virtual assets directory.",
|
|
4231
|
-
"${resolution_width}": "Window width (feature-gated).",
|
|
4232
|
-
"${resolution_height}": "Window height (feature-gated).",
|
|
4233
|
-
"${natives_directory}": "Extracted natives directory.",
|
|
4234
|
-
"${classpath}": "Joined classpath of libraries + version jar.",
|
|
4235
|
-
"${classpath_separator}": "OS-specific classpath separator (`:` / `;`).",
|
|
4236
|
-
"${library_directory}": "Per-target libraries directory.",
|
|
4237
|
-
"${launcher_name}": "Launcher brand string.",
|
|
4238
|
-
"${launcher_version}": "Launcher version string.",
|
|
4239
|
-
"${path}": "Path to the log4j config file (logging.client.argument only)."
|
|
4240
|
-
};
|
|
4241
|
-
|
|
4242
|
-
export { ASSETS_DIR, ASSETS_INDEXES_DIR, ASSETS_LEGACY_DIR, ASSETS_LOG_CONFIGS_DIR, ASSETS_OBJECTS_DIR, ASSETS_RESOURCES_DIR, ASSETS_VIRTUAL_DIR, ApiEndpoints, Architectures, AuthModes, BASE_JVM_ARGS, CACHE_MAX_ENTRIES, CACHE_TTL_MS, ChildProcessSpawner, DEFAULT_KILL_GRACE_MS, DEFAULT_LAUNCHER_NAME, DEFAULT_LAUNCHER_VERSION, DEFAULT_LIBRARY_REPOSITORY, DEFAULT_MAX_MB, DEFAULT_MIN_MB, DOWNLOAD_CONCURRENCY, EXTRACTION_MAX_COMPRESSION_RATIO, EXTRACTION_MAX_ENTRY_COUNT, EXTRACTION_MAX_FILE_SIZE, EXTRACTION_MAX_TOTAL_SIZE, EventTypes, FABRIC_MAVEN_BASE, FALLBACK_COMPONENT, FORGE_INSTALLERS_DIR, FORGE_INSTALLER_MAX_SIZE, FORGE_MAVEN_BASE, FabricVersionsApi, FetchHttpClient, ForgeVersionsApi, HTTP_RETRY_BACKOFF_BASE_MS, HTTP_RETRY_BACKOFF_CAP_MS, HTTP_RETRY_MAX, HTTP_TIMEOUT_MS, InstallActionKinds, InstallPhases, InstallStages, JAVA_EXECUTABLE, LAUNCH_PLACEHOLDERS, LEGACY_JVM_ARGS, LIBRARIES_DIR, Loaders, LogLevels, MACOS_JVM_ARGS, MAC_RUNTIME_PREFIX, MAX_PROCESSOR_STDERR_LINES, MinecraftChannels, MinecraftKit, MinecraftKitError, MinecraftVersionsApi, NATIVES_DIR_NAME, NODE_ARCH_TO_MOJANG_ARCH, NODE_PLATFORM_TO_MOJANG_OS, OperatingSystems, PROGRESS_EVENT_INTERVAL_MS, PauseController, RUNTIMES_DIR, RUNTIME_PLATFORM_KEYS, RepairPhases, RuntimeComponents, RuntimePreference, RuntimeVersionsApi, SPAWNER_MAX_LINE_BYTES, TargetsApi, USER_AGENT, VERSIONS_DIR, VerificationKinds, VerifyFileCategories, VerifyFileStatuses, VersionPreference, consoleLogger, createInstallProgressTracker, createMemoryCache, detectSystem, isErrorCode, isMinecraftKitError, offlineUuidFor, parseMajorVersion, pickClientJarVersionId, planFabricRepair, planForgeRepair, planMinecraftRepair, planRuntimeInstall, planRuntimeRepair, planStandaloneRuntimeInstall, repairAll, resolveLaunchVersion, runRepair, silentLogger, stripUuidDashes, targetPaths, verifyFabric, verifyForge, verifyMinecraft, verifyRuntime };
|
|
5072
|
+
export { ASSETS_DIR, ASSETS_INDEXES_DIR, ASSETS_LEGACY_DIR, ASSETS_LOG_CONFIGS_DIR, ASSETS_OBJECTS_DIR, ASSETS_RESOURCES_DIR, ASSETS_VIRTUAL_DIR, ApiEndpoints, Architectures, AuthModes, BASE_JVM_ARGS, CACHE_MAX_ENTRIES, CACHE_TTL_MS, CLIENT_ID_ENV_VAR, ChildProcessSpawner, DEFAULT_KILL_GRACE_MS, DEFAULT_LAUNCHER_NAME, DEFAULT_LAUNCHER_VERSION, DEFAULT_LIBRARY_REPOSITORY, DEFAULT_MAX_MB, DEFAULT_MIN_MB, DOWNLOAD_CONCURRENCY, DownloadCategories, EXTRACTION_MAX_COMPRESSION_RATIO, EXTRACTION_MAX_ENTRY_COUNT, EXTRACTION_MAX_FILE_SIZE, EXTRACTION_MAX_TOTAL_SIZE, EventTypes, FABRIC_MAVEN_BASE, FALLBACK_COMPONENT, FORGE_INSTALLERS_DIR, FORGE_INSTALLER_MAX_SIZE, FORGE_MAVEN_BASE, FabricVersionsApi, FetchHttpClient, ForgeVersionsApi, HTTP_RETRY_BACKOFF_BASE_MS, HTTP_RETRY_BACKOFF_CAP_MS, HTTP_RETRY_MAX, HTTP_TIMEOUT_MS, InstallActionKinds, InstallPhases, InstallStages, JAVA_EXECUTABLE, LAUNCH_PLACEHOLDERS, LEGACY_JVM_ARGS, LIBRARIES_DIR, Loaders, LogLevels, MACOS_JVM_ARGS, MAC_RUNTIME_PREFIX, MAX_PROCESSOR_STDERR_LINES, MinecraftChannels, MinecraftKit, MinecraftKitError, MinecraftKitErrorCodes, MinecraftVersionsApi, MojangAuthApi, NATIVES_DIR_NAME, NODE_ARCH_TO_MOJANG_ARCH, NODE_PLATFORM_TO_MOJANG_OS, OperatingSystems, PROGRESS_EVENT_INTERVAL_MS, PauseController, RUNTIMES_DIR, RUNTIME_PLATFORM_KEYS, RepairPhases, RuntimeComponents, RuntimePreference, RuntimeVersionsApi, SPAWNER_MAX_LINE_BYTES, TargetsApi, USER_AGENT, VERSIONS_DIR, VerificationKinds, VerifyFileCategories, VerifyFileStatuses, VersionPreference, assertNever, consoleLogger, createInstallProgressTracker, createMemoryCache, detectSystem, isErrorCode, isMinecraftKitError, offlineUuidFor, parseMajorVersion, pickClientJarVersionId, planFabricRepair, planForgeRepair, planMinecraftRepair, planRuntimeInstall, planRuntimeRepair, planStandaloneRuntimeInstall, repairAll, resolveLaunchVersion, runRepair, scopedLogger, silentLogger, stripUuidDashes, targetPaths, toOnlineAuth, verifyFabric, verifyForge, verifyMinecraft, verifyRuntime };
|
|
4243
5073
|
//# sourceMappingURL=index.mjs.map
|
|
4244
5074
|
//# sourceMappingURL=index.mjs.map
|