@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.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var process2 = require('process');
|
|
3
4
|
var os = require('os');
|
|
4
5
|
var lruCache = require('lru-cache');
|
|
5
6
|
var path = require('path');
|
|
@@ -15,12 +16,324 @@ var child_process = require('child_process');
|
|
|
15
16
|
|
|
16
17
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
17
18
|
|
|
19
|
+
var process2__default = /*#__PURE__*/_interopDefault(process2);
|
|
18
20
|
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
19
21
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
20
22
|
var yauzl__default = /*#__PURE__*/_interopDefault(yauzl);
|
|
21
23
|
var crypto2__default = /*#__PURE__*/_interopDefault(crypto2);
|
|
22
24
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
23
25
|
|
|
26
|
+
// src/types/errors.ts
|
|
27
|
+
var MinecraftKitErrorCodes = {
|
|
28
|
+
NETWORK_TIMEOUT: "NETWORK_TIMEOUT",
|
|
29
|
+
NETWORK_HTTP_ERROR: "NETWORK_HTTP_ERROR",
|
|
30
|
+
NETWORK_ABORTED: "NETWORK_ABORTED",
|
|
31
|
+
INTEGRITY_HASH_MISMATCH: "INTEGRITY_HASH_MISMATCH",
|
|
32
|
+
INTEGRITY_SIZE_MISMATCH: "INTEGRITY_SIZE_MISMATCH",
|
|
33
|
+
MANIFEST_INVALID: "MANIFEST_INVALID",
|
|
34
|
+
MANIFEST_NOT_FOUND: "MANIFEST_NOT_FOUND",
|
|
35
|
+
METADATA_PARSE_ERROR: "METADATA_PARSE_ERROR",
|
|
36
|
+
FILESYSTEM_PATH_TRAVERSAL: "FILESYSTEM_PATH_TRAVERSAL",
|
|
37
|
+
FILESYSTEM_WRITE_ERROR: "FILESYSTEM_WRITE_ERROR",
|
|
38
|
+
FILESYSTEM_READ_ERROR: "FILESYSTEM_READ_ERROR",
|
|
39
|
+
ARCHIVE_INVALID: "ARCHIVE_INVALID",
|
|
40
|
+
ARCHIVE_TOO_LARGE: "ARCHIVE_TOO_LARGE",
|
|
41
|
+
ARCHIVE_ENTRY_REJECTED: "ARCHIVE_ENTRY_REJECTED",
|
|
42
|
+
RUNTIME_NOT_FOUND: "RUNTIME_NOT_FOUND",
|
|
43
|
+
RUNTIME_UNSUPPORTED_PLATFORM: "RUNTIME_UNSUPPORTED_PLATFORM",
|
|
44
|
+
FORGE_PROCESSOR_FAILED: "FORGE_PROCESSOR_FAILED",
|
|
45
|
+
FORGE_INSTALLER_INVALID: "FORGE_INSTALLER_INVALID",
|
|
46
|
+
LAUNCH_JAVA_NOT_FOUND: "LAUNCH_JAVA_NOT_FOUND",
|
|
47
|
+
LAUNCH_PROCESS_FAILED: "LAUNCH_PROCESS_FAILED",
|
|
48
|
+
LAUNCH_ABORTED: "LAUNCH_ABORTED",
|
|
49
|
+
VERIFICATION_FAILED: "VERIFICATION_FAILED",
|
|
50
|
+
INVALID_INPUT: "INVALID_INPUT",
|
|
51
|
+
NOT_IMPLEMENTED: "NOT_IMPLEMENTED",
|
|
52
|
+
UNSUPPORTED_VERSION: "UNSUPPORTED_VERSION",
|
|
53
|
+
LZMA_DECODE_ERROR: "LZMA_DECODE_ERROR",
|
|
54
|
+
AUTH_DEVICE_CODE_EXPIRED: "AUTH_DEVICE_CODE_EXPIRED",
|
|
55
|
+
AUTH_DEVICE_CODE_DECLINED: "AUTH_DEVICE_CODE_DECLINED",
|
|
56
|
+
AUTH_DEVICE_CODE_FAILED: "AUTH_DEVICE_CODE_FAILED",
|
|
57
|
+
AUTH_REFRESH_FAILED: "AUTH_REFRESH_FAILED",
|
|
58
|
+
AUTH_XBOX_FAILED: "AUTH_XBOX_FAILED",
|
|
59
|
+
AUTH_XSTS_FAILED: "AUTH_XSTS_FAILED",
|
|
60
|
+
AUTH_MINECRAFT_FAILED: "AUTH_MINECRAFT_FAILED",
|
|
61
|
+
AUTH_NO_GAME_OWNERSHIP: "AUTH_NO_GAME_OWNERSHIP",
|
|
62
|
+
AUTH_MISSING_CLIENT_ID: "AUTH_MISSING_CLIENT_ID",
|
|
63
|
+
AUTH_CANCELLED: "AUTH_CANCELLED"
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/core/errors.ts
|
|
67
|
+
var MinecraftKitError = class extends Error {
|
|
68
|
+
name = "MinecraftKitError";
|
|
69
|
+
/** Stable discriminator. */
|
|
70
|
+
code;
|
|
71
|
+
/** Structured context; safe to serialize. */
|
|
72
|
+
context;
|
|
73
|
+
constructor(code, message, options = {}) {
|
|
74
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
75
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
76
|
+
this.code = code;
|
|
77
|
+
this.context = Object.freeze({ ...options.context ?? {} });
|
|
78
|
+
}
|
|
79
|
+
/** JSON-friendly representation. */
|
|
80
|
+
toJSON() {
|
|
81
|
+
return {
|
|
82
|
+
name: this.name,
|
|
83
|
+
code: this.code,
|
|
84
|
+
message: this.message,
|
|
85
|
+
context: this.context,
|
|
86
|
+
cause: this.cause instanceof Error ? { name: this.cause.name, message: this.cause.message } : this.cause
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var isMinecraftKitError = (e) => {
|
|
91
|
+
return e instanceof MinecraftKitError;
|
|
92
|
+
};
|
|
93
|
+
var isErrorCode = (e, code) => {
|
|
94
|
+
return isMinecraftKitError(e) && e.code === code;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/types/auth.ts
|
|
98
|
+
var AuthModes = {
|
|
99
|
+
/** Offline-mode play with a chosen username and synthetic UUID. */
|
|
100
|
+
OFFLINE: "offline",
|
|
101
|
+
/** Pre-authenticated session — caller provides the access token and identity. */
|
|
102
|
+
ONLINE: "online"
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// src/auth/microsoft.ts
|
|
106
|
+
var TENANT = "consumers";
|
|
107
|
+
var DEVICE_CODE_URL = `https://login.microsoftonline.com/${TENANT}/oauth2/v2.0/devicecode`;
|
|
108
|
+
var TOKEN_URL = `https://login.microsoftonline.com/${TENANT}/oauth2/v2.0/token`;
|
|
109
|
+
var SCOPE = "XboxLive.signin offline_access";
|
|
110
|
+
var startDeviceCode = async (input) => {
|
|
111
|
+
const body = new URLSearchParams({ client_id: input.clientId, scope: SCOPE });
|
|
112
|
+
const response = await input.http.request(DEVICE_CODE_URL, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
115
|
+
body: body.toString(),
|
|
116
|
+
// We need to read the body of 400/401 responses to surface Microsoft's actual
|
|
117
|
+
// `error_description` rather than reporting "HTTP 400" — the most common cause is
|
|
118
|
+
// an app registration that doesn't allow personal MSA accounts or hasn't enabled
|
|
119
|
+
// public client flows.
|
|
120
|
+
acceptNonOk: true,
|
|
121
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
122
|
+
});
|
|
123
|
+
if (response.status < 200 || response.status >= 300) {
|
|
124
|
+
const err = await response.json().catch(() => ({}));
|
|
125
|
+
throw new MinecraftKitError(
|
|
126
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_FAILED,
|
|
127
|
+
explainDeviceCodeError(err, input.clientId),
|
|
128
|
+
{
|
|
129
|
+
context: {
|
|
130
|
+
httpStatus: response.status,
|
|
131
|
+
microsoftError: err.error,
|
|
132
|
+
clientId: input.clientId
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const data = await response.json();
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
const state = {
|
|
140
|
+
deviceCode: data.device_code,
|
|
141
|
+
userCode: data.user_code,
|
|
142
|
+
verificationUri: data.verification_uri,
|
|
143
|
+
message: data.message,
|
|
144
|
+
expiresIn: data.expires_in,
|
|
145
|
+
interval: data.interval,
|
|
146
|
+
clientId: input.clientId,
|
|
147
|
+
expiresAt: now + data.expires_in * 1e3
|
|
148
|
+
};
|
|
149
|
+
const prompt = {
|
|
150
|
+
userCode: data.user_code,
|
|
151
|
+
verificationUri: data.verification_uri,
|
|
152
|
+
message: data.message,
|
|
153
|
+
expiresIn: data.expires_in,
|
|
154
|
+
interval: data.interval
|
|
155
|
+
};
|
|
156
|
+
return { prompt, state };
|
|
157
|
+
};
|
|
158
|
+
var pollDeviceCode = async (input) => {
|
|
159
|
+
let intervalSec = input.state.interval;
|
|
160
|
+
while (true) {
|
|
161
|
+
if (input.signal?.aborted) {
|
|
162
|
+
throw new MinecraftKitError(
|
|
163
|
+
MinecraftKitErrorCodes.AUTH_CANCELLED,
|
|
164
|
+
"Device-code polling aborted.",
|
|
165
|
+
{
|
|
166
|
+
context: { reason: input.signal.reason }
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (Date.now() >= input.state.expiresAt) {
|
|
171
|
+
throw new MinecraftKitError(
|
|
172
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_EXPIRED,
|
|
173
|
+
"Device code expired before the user signed in."
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const delayMs = intervalSec * 1e3;
|
|
177
|
+
input.onTick?.({ nextDelayMs: delayMs, expiresAt: input.state.expiresAt });
|
|
178
|
+
await wait(delayMs, input.signal);
|
|
179
|
+
const body = new URLSearchParams({
|
|
180
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
181
|
+
client_id: input.state.clientId,
|
|
182
|
+
device_code: input.state.deviceCode
|
|
183
|
+
});
|
|
184
|
+
const response = await input.http.request(TOKEN_URL, {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
187
|
+
body: body.toString(),
|
|
188
|
+
acceptNonOk: true
|
|
189
|
+
});
|
|
190
|
+
if (response.status >= 200 && response.status < 300) {
|
|
191
|
+
const ok = await response.json();
|
|
192
|
+
if (!ok.refresh_token) {
|
|
193
|
+
throw new MinecraftKitError(
|
|
194
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_FAILED,
|
|
195
|
+
"Microsoft did not return a refresh token. Make sure `offline_access` is in the requested scopes."
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
accessToken: ok.access_token,
|
|
200
|
+
refreshToken: ok.refresh_token,
|
|
201
|
+
expiresIn: ok.expires_in
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const err = await response.json().catch(() => ({}));
|
|
205
|
+
switch (err.error) {
|
|
206
|
+
case "authorization_pending":
|
|
207
|
+
continue;
|
|
208
|
+
case "slow_down":
|
|
209
|
+
intervalSec += 5;
|
|
210
|
+
continue;
|
|
211
|
+
case "authorization_declined":
|
|
212
|
+
throw new MinecraftKitError(
|
|
213
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_DECLINED,
|
|
214
|
+
"The user declined the sign-in request."
|
|
215
|
+
);
|
|
216
|
+
case "expired_token":
|
|
217
|
+
throw new MinecraftKitError(
|
|
218
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_EXPIRED,
|
|
219
|
+
"Device code expired before the user signed in."
|
|
220
|
+
);
|
|
221
|
+
default:
|
|
222
|
+
throw new MinecraftKitError(
|
|
223
|
+
MinecraftKitErrorCodes.AUTH_DEVICE_CODE_FAILED,
|
|
224
|
+
`Microsoft device-code exchange failed: ${err.error ?? "unknown_error"}${err.error_description ? ` \u2014 ${err.error_description}` : ""}`,
|
|
225
|
+
{ context: { httpStatus: response.status, microsoftError: err.error } }
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
var refreshMicrosoftToken = async (input) => {
|
|
231
|
+
const body = new URLSearchParams({
|
|
232
|
+
grant_type: "refresh_token",
|
|
233
|
+
client_id: input.clientId,
|
|
234
|
+
refresh_token: input.refreshToken,
|
|
235
|
+
scope: SCOPE
|
|
236
|
+
});
|
|
237
|
+
const response = await input.http.request(TOKEN_URL, {
|
|
238
|
+
method: "POST",
|
|
239
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
240
|
+
body: body.toString(),
|
|
241
|
+
acceptNonOk: true,
|
|
242
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
243
|
+
});
|
|
244
|
+
if (response.status < 200 || response.status >= 300) {
|
|
245
|
+
const err = await response.json().catch(() => ({}));
|
|
246
|
+
throw new MinecraftKitError(
|
|
247
|
+
MinecraftKitErrorCodes.AUTH_REFRESH_FAILED,
|
|
248
|
+
`Microsoft refused to refresh the token: ${err.error ?? "unknown_error"}${err.error_description ? ` \u2014 ${err.error_description}` : ""}`,
|
|
249
|
+
{ context: { httpStatus: response.status, microsoftError: err.error } }
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const ok = await response.json();
|
|
253
|
+
return {
|
|
254
|
+
accessToken: ok.access_token,
|
|
255
|
+
refreshToken: ok.refresh_token ?? input.refreshToken,
|
|
256
|
+
expiresIn: ok.expires_in
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
var explainDeviceCodeError = (err, clientId) => {
|
|
260
|
+
const desc = err.error_description ?? "";
|
|
261
|
+
const ms = desc ? ` \u2014 ${desc}` : "";
|
|
262
|
+
if (/AADSTS700016/i.test(desc) || /not found in the directory/i.test(desc)) {
|
|
263
|
+
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}`;
|
|
264
|
+
}
|
|
265
|
+
if (/AADSTS7000218/i.test(desc) || /must either be a confidential client/i.test(desc)) {
|
|
266
|
+
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}`;
|
|
267
|
+
}
|
|
268
|
+
if (/AADSTS50059/i.test(desc) || /tenant identifier/i.test(desc)) {
|
|
269
|
+
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}`;
|
|
270
|
+
}
|
|
271
|
+
switch (err.error) {
|
|
272
|
+
case "unauthorized_client":
|
|
273
|
+
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}`;
|
|
274
|
+
case "invalid_client":
|
|
275
|
+
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}`;
|
|
276
|
+
case "invalid_request":
|
|
277
|
+
return `Microsoft rejected the device-code request as malformed.${ms}`;
|
|
278
|
+
case "invalid_scope":
|
|
279
|
+
return `Microsoft refused the requested scope (XboxLive.signin offline_access). Make sure the app is configured for Microsoft account sign-in.${ms}`;
|
|
280
|
+
default:
|
|
281
|
+
return `Microsoft device-code request failed: ${err.error ?? "unknown_error"}${ms}`;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var wait = (ms, signal) => {
|
|
285
|
+
return new Promise((resolve, reject) => {
|
|
286
|
+
if (signal?.aborted) {
|
|
287
|
+
reject(
|
|
288
|
+
new MinecraftKitError(
|
|
289
|
+
MinecraftKitErrorCodes.AUTH_CANCELLED,
|
|
290
|
+
"Device-code polling aborted.",
|
|
291
|
+
{
|
|
292
|
+
context: { reason: signal.reason }
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const timer = setTimeout(() => {
|
|
299
|
+
signal?.removeEventListener("abort", onAbort);
|
|
300
|
+
resolve();
|
|
301
|
+
}, ms);
|
|
302
|
+
const onAbort = () => {
|
|
303
|
+
clearTimeout(timer);
|
|
304
|
+
reject(
|
|
305
|
+
new MinecraftKitError(
|
|
306
|
+
MinecraftKitErrorCodes.AUTH_CANCELLED,
|
|
307
|
+
"Device-code polling aborted.",
|
|
308
|
+
{
|
|
309
|
+
context: { reason: signal?.reason }
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
};
|
|
314
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// src/core/json.ts
|
|
319
|
+
var parseJsonStrict = (text, options) => {
|
|
320
|
+
try {
|
|
321
|
+
return JSON.parse(text);
|
|
322
|
+
} catch (cause) {
|
|
323
|
+
throw new MinecraftKitError(options.code, options.message, {
|
|
324
|
+
cause,
|
|
325
|
+
...options.context !== void 0 ? { context: options.context } : {}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
var parseJsonOrUndefined = (text) => {
|
|
330
|
+
try {
|
|
331
|
+
return JSON.parse(text);
|
|
332
|
+
} catch {
|
|
333
|
+
return void 0;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
24
337
|
// src/types/logger.ts
|
|
25
338
|
var LogLevels = {
|
|
26
339
|
DEBUG: "debug",
|
|
@@ -44,12 +357,361 @@ var consoleLogger = {
|
|
|
44
357
|
}
|
|
45
358
|
}
|
|
46
359
|
};
|
|
47
|
-
|
|
360
|
+
var scopedLogger = (base, scope, baseFields) => {
|
|
361
|
+
if (base === silentLogger) return silentLogger;
|
|
362
|
+
return {
|
|
363
|
+
log(level, message, fields) {
|
|
364
|
+
const merged = baseFields !== void 0 || fields !== void 0 ? { ...baseFields, ...fields } : void 0;
|
|
365
|
+
if (merged !== void 0) {
|
|
366
|
+
base.log(level, `[${scope}] ${message}`, merged);
|
|
367
|
+
} else {
|
|
368
|
+
base.log(level, `[${scope}] ${message}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
var pickConsole = (level) => {
|
|
48
374
|
if (level === LogLevels.ERROR) return console.error.bind(console);
|
|
49
375
|
if (level === LogLevels.WARN) return console.warn.bind(console);
|
|
50
376
|
if (level === LogLevels.INFO) return console.info.bind(console);
|
|
51
377
|
return console.debug.bind(console);
|
|
52
|
-
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// src/auth/debug.ts
|
|
381
|
+
var DEBUG_ENV_VAR = "MINECRAFT_KIT_AUTH_DEBUG";
|
|
382
|
+
var authDebug = (message) => {
|
|
383
|
+
if (process2__default.default.env[DEBUG_ENV_VAR]) {
|
|
384
|
+
process2__default.default.stderr.write(`[auth] ${message}
|
|
385
|
+
`);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// src/auth/minecraft.ts
|
|
390
|
+
var MC_LOGIN_URL = "https://api.minecraftservices.com/authentication/login_with_xbox";
|
|
391
|
+
var MC_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile";
|
|
392
|
+
var loginWithXbox = async (input) => {
|
|
393
|
+
const body = JSON.stringify({
|
|
394
|
+
identityToken: `XBL3.0 x=${input.userHash};${input.xstsToken}`
|
|
395
|
+
});
|
|
396
|
+
authDebug(
|
|
397
|
+
`login_with_xbox POST \u2014 userHashLen=${input.userHash.length}, xstsTokenLen=${input.xstsToken.length}`
|
|
398
|
+
);
|
|
399
|
+
const response = await input.http.request(MC_LOGIN_URL, {
|
|
400
|
+
method: "POST",
|
|
401
|
+
headers: {
|
|
402
|
+
"content-type": "application/json",
|
|
403
|
+
accept: "application/json",
|
|
404
|
+
// Mojang sometimes rejects unknown user-agents on the auth endpoints. Override the
|
|
405
|
+
// library default with a UA that matches what real Minecraft launchers send, so we
|
|
406
|
+
// don't trip an "anomalous client" filter.
|
|
407
|
+
"user-agent": "Minecraft Launcher/2.0 (minecraft-kit)"
|
|
408
|
+
},
|
|
409
|
+
body,
|
|
410
|
+
acceptNonOk: true,
|
|
411
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
412
|
+
});
|
|
413
|
+
if (response.status < 200 || response.status >= 300) {
|
|
414
|
+
const rawBody = await response.text().catch(() => "");
|
|
415
|
+
const detail = rawBody.slice(0, 400);
|
|
416
|
+
authDebug(`login_with_xbox failed status=${response.status} body=${detail}`);
|
|
417
|
+
if (response.status === 403) {
|
|
418
|
+
if (/invalid app registration/i.test(detail)) {
|
|
419
|
+
throw new MinecraftKitError(
|
|
420
|
+
MinecraftKitErrorCodes.AUTH_MINECRAFT_FAILED,
|
|
421
|
+
`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}`,
|
|
422
|
+
{ context: { httpStatus: 403, body: detail, reason: "invalid_app_registration" } }
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
throw new MinecraftKitError(
|
|
426
|
+
MinecraftKitErrorCodes.AUTH_NO_GAME_OWNERSHIP,
|
|
427
|
+
`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>"}`,
|
|
428
|
+
{ context: { httpStatus: 403, body: detail } }
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
throw new MinecraftKitError(
|
|
432
|
+
MinecraftKitErrorCodes.AUTH_MINECRAFT_FAILED,
|
|
433
|
+
`Minecraft sign-in failed with HTTP ${response.status}. Response: ${detail || "<empty>"}`,
|
|
434
|
+
{ context: { httpStatus: response.status, body: detail } }
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
const parsed = await response.json();
|
|
438
|
+
if (!parsed.access_token) {
|
|
439
|
+
throw new MinecraftKitError(
|
|
440
|
+
MinecraftKitErrorCodes.AUTH_MINECRAFT_FAILED,
|
|
441
|
+
"Minecraft sign-in returned no access token."
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
return { accessToken: parsed.access_token, expiresIn: parsed.expires_in };
|
|
445
|
+
};
|
|
446
|
+
var fetchMinecraftProfile = async (input) => {
|
|
447
|
+
const response = await input.http.request(MC_PROFILE_URL, {
|
|
448
|
+
headers: {
|
|
449
|
+
authorization: `Bearer ${input.accessToken}`,
|
|
450
|
+
accept: "application/json",
|
|
451
|
+
"user-agent": "Minecraft Launcher/2.0 (minecraft-kit)"
|
|
452
|
+
},
|
|
453
|
+
acceptNonOk: true,
|
|
454
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
455
|
+
});
|
|
456
|
+
if (response.status === 404) {
|
|
457
|
+
throw new MinecraftKitError(
|
|
458
|
+
MinecraftKitErrorCodes.AUTH_NO_GAME_OWNERSHIP,
|
|
459
|
+
"This Microsoft account does not own Minecraft: Java Edition.",
|
|
460
|
+
{ context: { httpStatus: 404 } }
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
if (response.status < 200 || response.status >= 300) {
|
|
464
|
+
throw new MinecraftKitError(
|
|
465
|
+
MinecraftKitErrorCodes.AUTH_MINECRAFT_FAILED,
|
|
466
|
+
`Failed to load Minecraft profile (HTTP ${response.status}).`,
|
|
467
|
+
{ context: { httpStatus: response.status } }
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
const parsed = await response.json();
|
|
471
|
+
if (parsed.errorMessage || !parsed.id || !parsed.name) {
|
|
472
|
+
throw new MinecraftKitError(
|
|
473
|
+
MinecraftKitErrorCodes.AUTH_MINECRAFT_FAILED,
|
|
474
|
+
parsed.errorMessage ?? "Minecraft profile response was malformed."
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
return { uuid: dashUuid(parsed.id), username: parsed.name };
|
|
478
|
+
};
|
|
479
|
+
var extractXuid = (accessToken) => {
|
|
480
|
+
const parts = accessToken.split(".");
|
|
481
|
+
const payload = parts[1];
|
|
482
|
+
if (typeof payload !== "string") return "";
|
|
483
|
+
const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString(
|
|
484
|
+
"utf8"
|
|
485
|
+
);
|
|
486
|
+
const parsed = parseJsonOrUndefined(json);
|
|
487
|
+
return typeof parsed?.xuid === "string" ? parsed.xuid : "";
|
|
488
|
+
};
|
|
489
|
+
var dashUuid = (raw) => {
|
|
490
|
+
if (raw.includes("-")) return raw;
|
|
491
|
+
if (raw.length !== 32) return raw;
|
|
492
|
+
return `${raw.slice(0, 8)}-${raw.slice(8, 12)}-${raw.slice(12, 16)}-${raw.slice(
|
|
493
|
+
16,
|
|
494
|
+
20
|
|
495
|
+
)}-${raw.slice(20)}`;
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// src/auth/xbox.ts
|
|
499
|
+
var XBL_URL = "https://user.auth.xboxlive.com/user/authenticate";
|
|
500
|
+
var XSTS_URL = "https://xsts.auth.xboxlive.com/xsts/authorize";
|
|
501
|
+
var authenticateXbl = async (input) => {
|
|
502
|
+
const body = JSON.stringify({
|
|
503
|
+
Properties: {
|
|
504
|
+
AuthMethod: "RPS",
|
|
505
|
+
SiteName: "user.auth.xboxlive.com",
|
|
506
|
+
RpsTicket: `d=${input.accessToken}`
|
|
507
|
+
},
|
|
508
|
+
RelyingParty: "http://auth.xboxlive.com",
|
|
509
|
+
TokenType: "JWT"
|
|
510
|
+
});
|
|
511
|
+
let response;
|
|
512
|
+
try {
|
|
513
|
+
response = await input.http.request(XBL_URL, {
|
|
514
|
+
method: "POST",
|
|
515
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
516
|
+
body,
|
|
517
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
518
|
+
});
|
|
519
|
+
} catch (cause) {
|
|
520
|
+
throw new MinecraftKitError(
|
|
521
|
+
MinecraftKitErrorCodes.AUTH_XBOX_FAILED,
|
|
522
|
+
"Xbox Live authentication failed.",
|
|
523
|
+
{
|
|
524
|
+
cause
|
|
525
|
+
}
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
const parsed = await response.json();
|
|
529
|
+
const userHash = parsed.DisplayClaims?.xui?.[0]?.uhs;
|
|
530
|
+
if (!parsed.Token || !userHash) {
|
|
531
|
+
throw new MinecraftKitError(
|
|
532
|
+
MinecraftKitErrorCodes.AUTH_XBOX_FAILED,
|
|
533
|
+
"Xbox Live authentication returned an incomplete response."
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
input.logger?.log("debug", `XBL ok \u2014 tokenLen=${parsed.Token.length}, userHash=${userHash}`);
|
|
537
|
+
return { token: parsed.Token, userHash };
|
|
538
|
+
};
|
|
539
|
+
var authenticateXsts = async (input) => {
|
|
540
|
+
const body = JSON.stringify({
|
|
541
|
+
Properties: { SandboxId: "RETAIL", UserTokens: [input.xblToken] },
|
|
542
|
+
RelyingParty: "rp://api.minecraftservices.com/",
|
|
543
|
+
TokenType: "JWT"
|
|
544
|
+
});
|
|
545
|
+
const response = await input.http.request(XSTS_URL, {
|
|
546
|
+
method: "POST",
|
|
547
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
548
|
+
body,
|
|
549
|
+
acceptNonOk: true,
|
|
550
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
551
|
+
});
|
|
552
|
+
if (response.status === 401) {
|
|
553
|
+
const err = await response.json().catch(() => ({}));
|
|
554
|
+
throw new MinecraftKitError(MinecraftKitErrorCodes.AUTH_XSTS_FAILED, explainXErr(err.XErr), {
|
|
555
|
+
context: { xerr: err.XErr ?? null, message: err.Message ?? null }
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (response.status < 200 || response.status >= 300) {
|
|
559
|
+
throw new MinecraftKitError(
|
|
560
|
+
MinecraftKitErrorCodes.AUTH_XSTS_FAILED,
|
|
561
|
+
`XSTS authorization failed with HTTP ${response.status}.`,
|
|
562
|
+
{ context: { httpStatus: response.status } }
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
const parsed = await response.json();
|
|
566
|
+
const userHash = parsed.DisplayClaims?.xui?.[0]?.uhs;
|
|
567
|
+
if (!parsed.Token || !userHash) {
|
|
568
|
+
throw new MinecraftKitError(
|
|
569
|
+
MinecraftKitErrorCodes.AUTH_XSTS_FAILED,
|
|
570
|
+
"XSTS authorization returned an incomplete response."
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
input.logger?.log("debug", `XSTS ok \u2014 tokenLen=${parsed.Token.length}, userHash=${userHash}`);
|
|
574
|
+
return { token: parsed.Token, userHash };
|
|
575
|
+
};
|
|
576
|
+
var explainXErr = (xerr) => {
|
|
577
|
+
switch (xerr) {
|
|
578
|
+
case 2148916227:
|
|
579
|
+
return "This account has been banned from Xbox.";
|
|
580
|
+
case 2148916233:
|
|
581
|
+
return "This Microsoft account has no Xbox profile yet \u2014 sign in once at https://minecraft.net to create one.";
|
|
582
|
+
case 2148916235:
|
|
583
|
+
return "Xbox Live is not available in this account's country/region.";
|
|
584
|
+
case 2148916236:
|
|
585
|
+
case 2148916237:
|
|
586
|
+
return "This account needs an adult to verify it. Sign in on Xbox to complete the prompt.";
|
|
587
|
+
case 2148916238:
|
|
588
|
+
return "This is a child account. Add it to a Microsoft family group with an adult to continue.";
|
|
589
|
+
default:
|
|
590
|
+
return xerr ? `XSTS authorization failed (XErr ${xerr}).` : "XSTS authorization failed.";
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// src/auth/index.ts
|
|
595
|
+
var CLIENT_ID_ENV_VAR = "MINECRAFT_KIT_MSA_CLIENT_ID";
|
|
596
|
+
var MojangAuthApi = class {
|
|
597
|
+
constructor(http) {
|
|
598
|
+
this.http = http;
|
|
599
|
+
}
|
|
600
|
+
http;
|
|
601
|
+
/**
|
|
602
|
+
* Run the full device-code flow end-to-end. The caller supplies an `onPrompt` callback to
|
|
603
|
+
* render the URL + code to the user; this method polls until they finish.
|
|
604
|
+
*/
|
|
605
|
+
async login(options) {
|
|
606
|
+
const clientId = resolveClientId(options.clientId);
|
|
607
|
+
const { prompt, state } = await startDeviceCode({
|
|
608
|
+
http: this.http,
|
|
609
|
+
clientId,
|
|
610
|
+
...options.signal !== void 0 ? { signal: options.signal } : {}
|
|
611
|
+
});
|
|
612
|
+
await options.onPrompt(prompt);
|
|
613
|
+
const msToken = await pollDeviceCode({
|
|
614
|
+
http: this.http,
|
|
615
|
+
state,
|
|
616
|
+
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
617
|
+
...options.onPoll !== void 0 ? { onTick: options.onPoll } : {}
|
|
618
|
+
});
|
|
619
|
+
return this.completeMicrosoftToken(msToken, clientId, options.signal);
|
|
620
|
+
}
|
|
621
|
+
/** Refresh a previously obtained session. The Microsoft refresh token may be rotated. */
|
|
622
|
+
async refresh(refreshToken, options = {}) {
|
|
623
|
+
const clientId = resolveClientId(options.clientId);
|
|
624
|
+
const msToken = await refreshMicrosoftToken({
|
|
625
|
+
http: this.http,
|
|
626
|
+
refreshToken,
|
|
627
|
+
clientId,
|
|
628
|
+
...options.signal !== void 0 ? { signal: options.signal } : {}
|
|
629
|
+
});
|
|
630
|
+
return this.completeMicrosoftToken(msToken, clientId, options.signal);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Lower-level device-code surface — exposed so callers can decouple "show prompt" from
|
|
634
|
+
* "block on poll" (e.g. a GUI that lets the user dismiss the modal). Most callers should
|
|
635
|
+
* use {@link login} instead.
|
|
636
|
+
*/
|
|
637
|
+
deviceCode = {
|
|
638
|
+
start: (options = {}) => {
|
|
639
|
+
const clientId = resolveClientId(options.clientId);
|
|
640
|
+
return startDeviceCode({
|
|
641
|
+
http: this.http,
|
|
642
|
+
clientId,
|
|
643
|
+
...options.signal !== void 0 ? { signal: options.signal } : {}
|
|
644
|
+
});
|
|
645
|
+
},
|
|
646
|
+
poll: async (state, options = {}) => {
|
|
647
|
+
const msToken = await pollDeviceCode({
|
|
648
|
+
http: this.http,
|
|
649
|
+
state,
|
|
650
|
+
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
651
|
+
...options.onTick !== void 0 ? { onTick: options.onTick } : {}
|
|
652
|
+
});
|
|
653
|
+
return this.completeMicrosoftToken(msToken, state.clientId, options.signal);
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
/** Steps 2 → 5: given a Microsoft access token, finish the flow. */
|
|
657
|
+
async completeMicrosoftToken(msToken, clientId, signal) {
|
|
658
|
+
const signalOpt = signal !== void 0 ? { signal } : {};
|
|
659
|
+
const xbl = await authenticateXbl({
|
|
660
|
+
http: this.http,
|
|
661
|
+
accessToken: msToken.accessToken,
|
|
662
|
+
...signalOpt
|
|
663
|
+
});
|
|
664
|
+
const xsts = await authenticateXsts({
|
|
665
|
+
http: this.http,
|
|
666
|
+
xblToken: xbl.token,
|
|
667
|
+
...signalOpt
|
|
668
|
+
});
|
|
669
|
+
const mc = await loginWithXbox({
|
|
670
|
+
http: this.http,
|
|
671
|
+
xstsToken: xsts.token,
|
|
672
|
+
userHash: xsts.userHash,
|
|
673
|
+
...signalOpt
|
|
674
|
+
});
|
|
675
|
+
const profile = await fetchMinecraftProfile({
|
|
676
|
+
http: this.http,
|
|
677
|
+
accessToken: mc.accessToken,
|
|
678
|
+
...signalOpt
|
|
679
|
+
});
|
|
680
|
+
return {
|
|
681
|
+
minecraft: {
|
|
682
|
+
username: profile.username,
|
|
683
|
+
uuid: profile.uuid,
|
|
684
|
+
accessToken: mc.accessToken,
|
|
685
|
+
expiresAt: Date.now() + mc.expiresIn * 1e3,
|
|
686
|
+
xuid: extractXuid(mc.accessToken)
|
|
687
|
+
},
|
|
688
|
+
microsoft: {
|
|
689
|
+
refreshToken: msToken.refreshToken,
|
|
690
|
+
clientId
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
var toOnlineAuth = (session) => {
|
|
696
|
+
return {
|
|
697
|
+
mode: AuthModes.ONLINE,
|
|
698
|
+
username: session.minecraft.username,
|
|
699
|
+
uuid: session.minecraft.uuid,
|
|
700
|
+
accessToken: session.minecraft.accessToken,
|
|
701
|
+
userType: "msa",
|
|
702
|
+
clientId: session.microsoft.clientId,
|
|
703
|
+
xuid: session.minecraft.xuid
|
|
704
|
+
};
|
|
705
|
+
};
|
|
706
|
+
var resolveClientId = (explicit) => {
|
|
707
|
+
if (typeof explicit === "string" && explicit.trim().length > 0) return explicit.trim();
|
|
708
|
+
const fromEnv = process.env[CLIENT_ID_ENV_VAR];
|
|
709
|
+
if (typeof fromEnv === "string" && fromEnv.trim().length > 0) return fromEnv.trim();
|
|
710
|
+
throw new MinecraftKitError(
|
|
711
|
+
MinecraftKitErrorCodes.AUTH_MISSING_CLIENT_ID,
|
|
712
|
+
`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.`
|
|
713
|
+
);
|
|
714
|
+
};
|
|
53
715
|
|
|
54
716
|
// src/constants/platform.ts
|
|
55
717
|
var NODE_PLATFORM_TO_MOJANG_OS = {
|
|
@@ -68,39 +730,8 @@ var RUNTIME_PLATFORM_KEYS = {
|
|
|
68
730
|
linux: { x64: "linux", x86: "linux-i386", arm64: "linux" }
|
|
69
731
|
};
|
|
70
732
|
|
|
71
|
-
// src/core/errors.ts
|
|
72
|
-
var MinecraftKitError = class extends Error {
|
|
73
|
-
name = "MinecraftKitError";
|
|
74
|
-
/** Stable discriminator. */
|
|
75
|
-
code;
|
|
76
|
-
/** Structured context; safe to serialize. */
|
|
77
|
-
context;
|
|
78
|
-
constructor(code, message, options = {}) {
|
|
79
|
-
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
80
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
81
|
-
this.code = code;
|
|
82
|
-
this.context = Object.freeze({ ...options.context ?? {} });
|
|
83
|
-
}
|
|
84
|
-
/** JSON-friendly representation. */
|
|
85
|
-
toJSON() {
|
|
86
|
-
return {
|
|
87
|
-
name: this.name,
|
|
88
|
-
code: this.code,
|
|
89
|
-
message: this.message,
|
|
90
|
-
context: this.context,
|
|
91
|
-
cause: this.cause instanceof Error ? { name: this.cause.name, message: this.cause.message } : this.cause
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
function isMinecraftKitError(e) {
|
|
96
|
-
return e instanceof MinecraftKitError;
|
|
97
|
-
}
|
|
98
|
-
function isErrorCode(e, code) {
|
|
99
|
-
return isMinecraftKitError(e) && e.code === code;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
733
|
// src/core/system.ts
|
|
103
|
-
|
|
734
|
+
var detectSystem = (input = {}) => {
|
|
104
735
|
const platform = input.platform ?? process.platform;
|
|
105
736
|
const arch = input.arch ?? process.arch;
|
|
106
737
|
const osVersion = input.osVersion ?? os__default.default.release();
|
|
@@ -108,13 +739,13 @@ function detectSystem(input = {}) {
|
|
|
108
739
|
const mojangArch = NODE_ARCH_TO_MOJANG_ARCH[arch];
|
|
109
740
|
if (mojangOs === void 0 || mojangArch === void 0) {
|
|
110
741
|
throw new MinecraftKitError(
|
|
111
|
-
|
|
742
|
+
MinecraftKitErrorCodes.RUNTIME_UNSUPPORTED_PLATFORM,
|
|
112
743
|
`Unsupported platform/arch combination: ${platform}/${arch}`,
|
|
113
744
|
{ context: { platform, arch: String(arch) } }
|
|
114
745
|
);
|
|
115
746
|
}
|
|
116
747
|
return { os: mojangOs, arch: mojangArch, osVersion };
|
|
117
|
-
}
|
|
748
|
+
};
|
|
118
749
|
|
|
119
750
|
// src/constants/defaults.ts
|
|
120
751
|
var HTTP_TIMEOUT_MS = 3e4;
|
|
@@ -135,7 +766,7 @@ var MAX_PROCESSOR_STDERR_LINES = 20;
|
|
|
135
766
|
var SPAWNER_MAX_LINE_BYTES = 64 * 1024;
|
|
136
767
|
|
|
137
768
|
// src/http/cache.ts
|
|
138
|
-
|
|
769
|
+
var createMemoryCache = (options = {}) => {
|
|
139
770
|
const cache = new lruCache.LRUCache({
|
|
140
771
|
max: options.maxEntries ?? CACHE_MAX_ENTRIES,
|
|
141
772
|
ttl: options.ttlMs ?? CACHE_TTL_MS
|
|
@@ -160,7 +791,7 @@ function createMemoryCache(options = {}) {
|
|
|
160
791
|
cache.clear();
|
|
161
792
|
}
|
|
162
793
|
};
|
|
163
|
-
}
|
|
794
|
+
};
|
|
164
795
|
|
|
165
796
|
// src/http/client.ts
|
|
166
797
|
var TIMEOUT_REASON = /* @__PURE__ */ Symbol("http-timeout");
|
|
@@ -178,39 +809,60 @@ var FetchHttpClient = class {
|
|
|
178
809
|
}
|
|
179
810
|
const timer = setTimeout(() => controller.abort(TIMEOUT_REASON), timeoutMs);
|
|
180
811
|
let response;
|
|
812
|
+
const method = options.method ?? "GET";
|
|
181
813
|
try {
|
|
182
|
-
|
|
183
|
-
method
|
|
814
|
+
const init = {
|
|
815
|
+
method,
|
|
184
816
|
headers: { "user-agent": USER_AGENT, ...options.headers ?? {} },
|
|
185
817
|
signal: controller.signal,
|
|
186
818
|
redirect: "follow"
|
|
187
|
-
}
|
|
819
|
+
};
|
|
820
|
+
if (method !== "GET" && options.body !== void 0) {
|
|
821
|
+
init.body = options.body;
|
|
822
|
+
}
|
|
823
|
+
response = await fetch(url, init);
|
|
188
824
|
} catch (cause) {
|
|
189
825
|
clearTimeout(timer);
|
|
190
826
|
options.signal?.removeEventListener("abort", onParentAbort);
|
|
191
827
|
if (controller.signal.reason === TIMEOUT_REASON) {
|
|
192
|
-
throw new MinecraftKitError(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
828
|
+
throw new MinecraftKitError(
|
|
829
|
+
MinecraftKitErrorCodes.NETWORK_TIMEOUT,
|
|
830
|
+
`Request timed out: ${url}`,
|
|
831
|
+
{
|
|
832
|
+
cause,
|
|
833
|
+
context: { url, timeoutMs }
|
|
834
|
+
}
|
|
835
|
+
);
|
|
196
836
|
}
|
|
197
837
|
if (options.signal?.aborted) {
|
|
198
|
-
throw new MinecraftKitError(
|
|
838
|
+
throw new MinecraftKitError(
|
|
839
|
+
MinecraftKitErrorCodes.NETWORK_ABORTED,
|
|
840
|
+
`Request aborted: ${url}`,
|
|
841
|
+
{
|
|
842
|
+
cause,
|
|
843
|
+
context: { url }
|
|
844
|
+
}
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
throw new MinecraftKitError(
|
|
848
|
+
MinecraftKitErrorCodes.NETWORK_HTTP_ERROR,
|
|
849
|
+
`Network request failed: ${url}`,
|
|
850
|
+
{
|
|
199
851
|
cause,
|
|
200
852
|
context: { url }
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
throw new MinecraftKitError("NETWORK_HTTP_ERROR", `Network request failed: ${url}`, {
|
|
204
|
-
cause,
|
|
205
|
-
context: { url }
|
|
206
|
-
});
|
|
853
|
+
}
|
|
854
|
+
);
|
|
207
855
|
}
|
|
208
856
|
clearTimeout(timer);
|
|
209
857
|
options.signal?.removeEventListener("abort", onParentAbort);
|
|
210
|
-
if (!response.ok) {
|
|
211
|
-
throw new MinecraftKitError(
|
|
212
|
-
|
|
213
|
-
|
|
858
|
+
if (!response.ok && options.acceptNonOk !== true) {
|
|
859
|
+
throw new MinecraftKitError(
|
|
860
|
+
MinecraftKitErrorCodes.NETWORK_HTTP_ERROR,
|
|
861
|
+
`HTTP ${response.status} for ${url}`,
|
|
862
|
+
{
|
|
863
|
+
context: { url, httpStatus: response.status }
|
|
864
|
+
}
|
|
865
|
+
);
|
|
214
866
|
}
|
|
215
867
|
return new FetchHttpResponse(response, url);
|
|
216
868
|
}
|
|
@@ -334,6 +986,17 @@ var InstallActionKinds = {
|
|
|
334
986
|
WRITE_VERSION_JSON: "write-version-json",
|
|
335
987
|
WRITE_LOGGING_CONFIG: "write-logging-config"
|
|
336
988
|
};
|
|
989
|
+
var DownloadCategories = {
|
|
990
|
+
CLIENT_JAR: "client-jar",
|
|
991
|
+
LIBRARY: "library",
|
|
992
|
+
ASSET_INDEX: "asset-index",
|
|
993
|
+
ASSET: "asset",
|
|
994
|
+
LOGGING_CONFIG: "logging-config",
|
|
995
|
+
FABRIC_LIBRARY: "fabric-library",
|
|
996
|
+
FORGE_LIBRARY: "forge-library",
|
|
997
|
+
RUNTIME_FILE: "runtime-file",
|
|
998
|
+
FORGE_INSTALLER: "forge-installer"
|
|
999
|
+
};
|
|
337
1000
|
|
|
338
1001
|
// src/types/loader.ts
|
|
339
1002
|
var Loaders = {
|
|
@@ -387,7 +1050,7 @@ var ApiEndpoints = {
|
|
|
387
1050
|
};
|
|
388
1051
|
|
|
389
1052
|
// src/http/metadata.ts
|
|
390
|
-
async
|
|
1053
|
+
var fetchJson = async (http, cache, input) => {
|
|
391
1054
|
const key = input.cacheKey ?? `json:${input.url}`;
|
|
392
1055
|
const cached = cache.get(key);
|
|
393
1056
|
if (cached !== void 0) {
|
|
@@ -399,8 +1062,8 @@ async function fetchJson(http, cache, input) {
|
|
|
399
1062
|
const value = await response.json();
|
|
400
1063
|
cache.set(key, value, input.ttlMs ?? CACHE_TTL_MS);
|
|
401
1064
|
return value;
|
|
402
|
-
}
|
|
403
|
-
async
|
|
1065
|
+
};
|
|
1066
|
+
var fetchText = async (http, cache, input) => {
|
|
404
1067
|
const key = input.cacheKey ?? `text:${input.url}`;
|
|
405
1068
|
const cached = cache.get(key);
|
|
406
1069
|
if (cached !== void 0) {
|
|
@@ -412,10 +1075,10 @@ async function fetchText(http, cache, input) {
|
|
|
412
1075
|
const text = await response.text();
|
|
413
1076
|
cache.set(key, text, input.ttlMs ?? CACHE_TTL_MS);
|
|
414
1077
|
return text;
|
|
415
|
-
}
|
|
1078
|
+
};
|
|
416
1079
|
|
|
417
1080
|
// src/install/assets.ts
|
|
418
|
-
async
|
|
1081
|
+
var planAssetDownloads = async (input) => {
|
|
419
1082
|
const indexUrl = input.assetIndex.url;
|
|
420
1083
|
const indexPath = targetPaths.assetIndex(input.directory, input.assetIndex.id);
|
|
421
1084
|
const indexDocument = await fetchJson(input.http, input.cache, {
|
|
@@ -430,7 +1093,7 @@ async function planAssetDownloads(input) {
|
|
|
430
1093
|
target: indexPath,
|
|
431
1094
|
expectedSha1: input.assetIndex.sha1,
|
|
432
1095
|
expectedSize: input.assetIndex.size,
|
|
433
|
-
category:
|
|
1096
|
+
category: DownloadCategories.ASSET_INDEX
|
|
434
1097
|
}
|
|
435
1098
|
];
|
|
436
1099
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -443,11 +1106,11 @@ async function planAssetDownloads(input) {
|
|
|
443
1106
|
target: targetPaths.assetObject(input.directory, entry.hash),
|
|
444
1107
|
expectedSha1: entry.hash,
|
|
445
1108
|
expectedSize: entry.size,
|
|
446
|
-
category:
|
|
1109
|
+
category: DownloadCategories.ASSET
|
|
447
1110
|
});
|
|
448
1111
|
}
|
|
449
1112
|
return { actions, indexDocument };
|
|
450
|
-
}
|
|
1113
|
+
};
|
|
451
1114
|
|
|
452
1115
|
// src/constants/maven.ts
|
|
453
1116
|
var DEFAULT_LIBRARY_REPOSITORY = "https://libraries.minecraft.net/";
|
|
@@ -455,21 +1118,25 @@ var FABRIC_MAVEN_BASE = "https://maven.fabricmc.net/";
|
|
|
455
1118
|
var FORGE_MAVEN_BASE = "https://maven.minecraftforge.net/";
|
|
456
1119
|
|
|
457
1120
|
// src/core/maven.ts
|
|
458
|
-
|
|
1121
|
+
var parseMavenCoordinate = (input) => {
|
|
459
1122
|
const trimmed = input.startsWith("[") && input.endsWith("]") ? input.slice(1, -1) : input;
|
|
460
1123
|
const atIndex = trimmed.indexOf("@");
|
|
461
1124
|
const extension = atIndex === -1 ? "jar" : trimmed.slice(atIndex + 1);
|
|
462
1125
|
const body = atIndex === -1 ? trimmed : trimmed.slice(0, atIndex);
|
|
463
1126
|
const parts = body.split(":");
|
|
464
1127
|
if (parts.length < 3 || parts.length > 4) {
|
|
465
|
-
throw new MinecraftKitError(
|
|
466
|
-
|
|
467
|
-
|
|
1128
|
+
throw new MinecraftKitError(
|
|
1129
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
1130
|
+
`Invalid Maven coordinate: ${input}`,
|
|
1131
|
+
{
|
|
1132
|
+
context: { input }
|
|
1133
|
+
}
|
|
1134
|
+
);
|
|
468
1135
|
}
|
|
469
1136
|
const [group, artifact, version, classifier] = parts;
|
|
470
1137
|
if (!group || !artifact || !version) {
|
|
471
1138
|
throw new MinecraftKitError(
|
|
472
|
-
|
|
1139
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
473
1140
|
`Invalid Maven coordinate (missing component): ${input}`,
|
|
474
1141
|
{ context: { input } }
|
|
475
1142
|
);
|
|
@@ -478,19 +1145,19 @@ function parseMavenCoordinate(input) {
|
|
|
478
1145
|
return { group, artifact, version, extension };
|
|
479
1146
|
}
|
|
480
1147
|
return { group, artifact, version, classifier, extension };
|
|
481
|
-
}
|
|
482
|
-
|
|
1148
|
+
};
|
|
1149
|
+
var mavenRelativePath = (coord) => {
|
|
483
1150
|
const groupPath = coord.group.replaceAll(".", "/");
|
|
484
1151
|
const classifierSegment = coord.classifier === void 0 ? "" : `-${coord.classifier}`;
|
|
485
1152
|
const filename = `${coord.artifact}-${coord.version}${classifierSegment}.${coord.extension}`;
|
|
486
1153
|
return `${groupPath}/${coord.artifact}/${coord.version}/${filename}`;
|
|
487
|
-
}
|
|
488
|
-
|
|
1154
|
+
};
|
|
1155
|
+
var mavenRelativePathFor = (input) => {
|
|
489
1156
|
return mavenRelativePath(parseMavenCoordinate(input));
|
|
490
|
-
}
|
|
1157
|
+
};
|
|
491
1158
|
|
|
492
1159
|
// src/core/rules.ts
|
|
493
|
-
|
|
1160
|
+
var evaluateRules = (rules, context) => {
|
|
494
1161
|
if (!rules || rules.length === 0) {
|
|
495
1162
|
return true;
|
|
496
1163
|
}
|
|
@@ -501,8 +1168,8 @@ function evaluateRules(rules, context) {
|
|
|
501
1168
|
}
|
|
502
1169
|
}
|
|
503
1170
|
return allowed;
|
|
504
|
-
}
|
|
505
|
-
|
|
1171
|
+
};
|
|
1172
|
+
var matchesRule = (rule, context) => {
|
|
506
1173
|
if (rule.os !== void 0) {
|
|
507
1174
|
if (rule.os.name !== void 0 && rule.os.name !== context.system.os) {
|
|
508
1175
|
return false;
|
|
@@ -530,21 +1197,21 @@ function matchesRule(rule, context) {
|
|
|
530
1197
|
}
|
|
531
1198
|
}
|
|
532
1199
|
return true;
|
|
533
|
-
}
|
|
534
|
-
|
|
1200
|
+
};
|
|
1201
|
+
var normalizeArch = (arch) => {
|
|
535
1202
|
return arch === "ia32" ? "x86" : arch;
|
|
536
|
-
}
|
|
537
|
-
|
|
1203
|
+
};
|
|
1204
|
+
var resolveArchPlaceholder = (template, archDigit2) => {
|
|
538
1205
|
return template.replaceAll("${arch}", archDigit2);
|
|
539
|
-
}
|
|
540
|
-
|
|
1206
|
+
};
|
|
1207
|
+
var archDigit = (arch) => {
|
|
541
1208
|
if (arch === "x86") return "32";
|
|
542
1209
|
if (arch === "x64") return "64";
|
|
543
1210
|
return "64";
|
|
544
|
-
}
|
|
1211
|
+
};
|
|
545
1212
|
|
|
546
1213
|
// src/install/libraries.ts
|
|
547
|
-
|
|
1214
|
+
var planLibraryDownloads = (input) => {
|
|
548
1215
|
const downloads = [];
|
|
549
1216
|
const nativeExtractions = [];
|
|
550
1217
|
const classpathFiles = [];
|
|
@@ -598,8 +1265,8 @@ function planLibraryDownloads(input) {
|
|
|
598
1265
|
}
|
|
599
1266
|
}
|
|
600
1267
|
return { downloads, nativeExtractions, classpathFiles };
|
|
601
|
-
}
|
|
602
|
-
|
|
1268
|
+
};
|
|
1269
|
+
var pickPrimaryArtifact = (library) => {
|
|
603
1270
|
if (library.downloads?.artifact) {
|
|
604
1271
|
return artifactFromDownload(library.downloads.artifact);
|
|
605
1272
|
}
|
|
@@ -610,8 +1277,8 @@ function pickPrimaryArtifact(library) {
|
|
|
610
1277
|
return mavenArtifactFromCoord(library.name, DEFAULT_LIBRARY_REPOSITORY);
|
|
611
1278
|
}
|
|
612
1279
|
return null;
|
|
613
|
-
}
|
|
614
|
-
|
|
1280
|
+
};
|
|
1281
|
+
var pickNative = (library, system) => {
|
|
615
1282
|
if (!library.natives) return null;
|
|
616
1283
|
const classifierTemplate = library.natives[system.os];
|
|
617
1284
|
if (!classifierTemplate) return null;
|
|
@@ -626,22 +1293,26 @@ function pickNative(library, system) {
|
|
|
626
1293
|
return mavenArtifactFromCoord(withClassifier, library.url ?? DEFAULT_LIBRARY_REPOSITORY);
|
|
627
1294
|
}
|
|
628
1295
|
return null;
|
|
629
|
-
}
|
|
630
|
-
|
|
1296
|
+
};
|
|
1297
|
+
var artifactFromDownload = (artifact) => {
|
|
631
1298
|
return {
|
|
632
1299
|
relativePath: artifact.path,
|
|
633
1300
|
url: artifact.url,
|
|
634
1301
|
sha1: artifact.sha1,
|
|
635
1302
|
size: artifact.size
|
|
636
1303
|
};
|
|
637
|
-
}
|
|
638
|
-
|
|
1304
|
+
};
|
|
1305
|
+
var mavenArtifactFromCoord = (coord, baseUrl) => {
|
|
639
1306
|
const relativePath = mavenRelativePathFor(coord);
|
|
640
1307
|
const normalizedBase = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
641
1308
|
if (!relativePath) {
|
|
642
|
-
throw new MinecraftKitError(
|
|
643
|
-
|
|
644
|
-
|
|
1309
|
+
throw new MinecraftKitError(
|
|
1310
|
+
MinecraftKitErrorCodes.MANIFEST_INVALID,
|
|
1311
|
+
`Invalid library coordinate: ${coord}`,
|
|
1312
|
+
{
|
|
1313
|
+
context: { input: coord }
|
|
1314
|
+
}
|
|
1315
|
+
);
|
|
645
1316
|
}
|
|
646
1317
|
return {
|
|
647
1318
|
relativePath,
|
|
@@ -649,10 +1320,10 @@ function mavenArtifactFromCoord(coord, baseUrl) {
|
|
|
649
1320
|
sha1: void 0,
|
|
650
1321
|
size: void 0
|
|
651
1322
|
};
|
|
652
|
-
}
|
|
1323
|
+
};
|
|
653
1324
|
|
|
654
1325
|
// src/install/fabric-install.ts
|
|
655
|
-
|
|
1326
|
+
var planFabricInstall = (input) => {
|
|
656
1327
|
const versionId = input.loader.profile.id;
|
|
657
1328
|
const versionJsonPath = targetPaths.versionJson(input.directory, versionId);
|
|
658
1329
|
const versionJson = {
|
|
@@ -666,7 +1337,7 @@ function planFabricInstall(input) {
|
|
|
666
1337
|
directory: input.directory,
|
|
667
1338
|
system: input.system,
|
|
668
1339
|
versionId: input.minecraft.version,
|
|
669
|
-
category:
|
|
1340
|
+
category: DownloadCategories.FABRIC_LIBRARY
|
|
670
1341
|
});
|
|
671
1342
|
return {
|
|
672
1343
|
versionJson,
|
|
@@ -674,7 +1345,7 @@ function planFabricInstall(input) {
|
|
|
674
1345
|
classpathFiles: plan.classpathFiles,
|
|
675
1346
|
versionId
|
|
676
1347
|
};
|
|
677
|
-
}
|
|
1348
|
+
};
|
|
678
1349
|
|
|
679
1350
|
// src/constants/limits.ts
|
|
680
1351
|
var EXTRACTION_MAX_FILE_SIZE = 256 * 1024 * 1024;
|
|
@@ -682,42 +1353,42 @@ var EXTRACTION_MAX_TOTAL_SIZE = 2 * 1024 * 1024 * 1024;
|
|
|
682
1353
|
var EXTRACTION_MAX_COMPRESSION_RATIO = 200;
|
|
683
1354
|
var EXTRACTION_MAX_ENTRY_COUNT = 1e5;
|
|
684
1355
|
var FORGE_INSTALLER_MAX_SIZE = 256 * 1024 * 1024;
|
|
685
|
-
async
|
|
1356
|
+
var ensureDir = async (directory) => {
|
|
686
1357
|
try {
|
|
687
1358
|
await fs__default.default.mkdir(directory, { recursive: true });
|
|
688
1359
|
} catch (cause) {
|
|
689
1360
|
throw new MinecraftKitError(
|
|
690
|
-
|
|
1361
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
691
1362
|
`Failed to create directory: ${directory}`,
|
|
692
1363
|
{ cause, context: { filePath: directory } }
|
|
693
1364
|
);
|
|
694
1365
|
}
|
|
695
|
-
}
|
|
696
|
-
async
|
|
1366
|
+
};
|
|
1367
|
+
var fileExists = async (filePath) => {
|
|
697
1368
|
try {
|
|
698
1369
|
const stat = await fs__default.default.stat(filePath);
|
|
699
1370
|
return stat.isFile();
|
|
700
1371
|
} catch {
|
|
701
1372
|
return false;
|
|
702
1373
|
}
|
|
703
|
-
}
|
|
704
|
-
async
|
|
1374
|
+
};
|
|
1375
|
+
var dirExists = async (filePath) => {
|
|
705
1376
|
try {
|
|
706
1377
|
const stat = await fs__default.default.stat(filePath);
|
|
707
1378
|
return stat.isDirectory();
|
|
708
1379
|
} catch {
|
|
709
1380
|
return false;
|
|
710
1381
|
}
|
|
711
|
-
}
|
|
712
|
-
async
|
|
1382
|
+
};
|
|
1383
|
+
var fileSize = async (filePath) => {
|
|
713
1384
|
try {
|
|
714
1385
|
const stat = await fs__default.default.stat(filePath);
|
|
715
1386
|
return stat.size;
|
|
716
1387
|
} catch {
|
|
717
1388
|
return -1;
|
|
718
1389
|
}
|
|
719
|
-
}
|
|
720
|
-
async
|
|
1390
|
+
};
|
|
1391
|
+
var atomicWrite = async (target, data) => {
|
|
721
1392
|
await ensureDir(path__default.default.dirname(target));
|
|
722
1393
|
const tmp = `${target}.${crypto2__default.default.randomBytes(4).toString("hex")}.tmp`;
|
|
723
1394
|
try {
|
|
@@ -732,31 +1403,39 @@ async function atomicWrite(target, data) {
|
|
|
732
1403
|
await fs__default.default.unlink(tmp);
|
|
733
1404
|
} catch {
|
|
734
1405
|
}
|
|
735
|
-
throw new MinecraftKitError(
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1406
|
+
throw new MinecraftKitError(
|
|
1407
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
1408
|
+
`Failed to write file: ${target}`,
|
|
1409
|
+
{
|
|
1410
|
+
cause,
|
|
1411
|
+
context: { filePath: target }
|
|
1412
|
+
}
|
|
1413
|
+
);
|
|
739
1414
|
}
|
|
740
|
-
}
|
|
741
|
-
async
|
|
1415
|
+
};
|
|
1416
|
+
var readText = async (filePath) => {
|
|
742
1417
|
try {
|
|
743
1418
|
return await fs__default.default.readFile(filePath, "utf8");
|
|
744
1419
|
} catch (cause) {
|
|
745
|
-
throw new MinecraftKitError(
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1420
|
+
throw new MinecraftKitError(
|
|
1421
|
+
MinecraftKitErrorCodes.FILESYSTEM_READ_ERROR,
|
|
1422
|
+
`Failed to read file: ${filePath}`,
|
|
1423
|
+
{
|
|
1424
|
+
cause,
|
|
1425
|
+
context: { filePath }
|
|
1426
|
+
}
|
|
1427
|
+
);
|
|
749
1428
|
}
|
|
750
|
-
}
|
|
751
|
-
async
|
|
1429
|
+
};
|
|
1430
|
+
var listChildDirectories = async (directory) => {
|
|
752
1431
|
try {
|
|
753
1432
|
const entries = await fs__default.default.readdir(directory, { withFileTypes: true });
|
|
754
1433
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
755
1434
|
} catch {
|
|
756
1435
|
return [];
|
|
757
1436
|
}
|
|
758
|
-
}
|
|
759
|
-
async
|
|
1437
|
+
};
|
|
1438
|
+
var chmodExecutable = async (filePath) => {
|
|
760
1439
|
if (process.platform === "win32") {
|
|
761
1440
|
return;
|
|
762
1441
|
}
|
|
@@ -764,35 +1443,43 @@ async function chmodExecutable(filePath) {
|
|
|
764
1443
|
await fs__default.default.chmod(filePath, 493);
|
|
765
1444
|
} catch {
|
|
766
1445
|
}
|
|
767
|
-
}
|
|
768
|
-
|
|
1446
|
+
};
|
|
1447
|
+
var assertWithinRoot = (root, child) => {
|
|
769
1448
|
const normalizedRoot = path__default.default.resolve(root);
|
|
770
1449
|
const normalizedChild = path__default.default.resolve(root, child);
|
|
771
1450
|
const sep = path__default.default.sep;
|
|
772
1451
|
if (normalizedChild !== normalizedRoot && !normalizedChild.startsWith(normalizedRoot + sep)) {
|
|
773
|
-
throw new MinecraftKitError(
|
|
774
|
-
|
|
775
|
-
|
|
1452
|
+
throw new MinecraftKitError(
|
|
1453
|
+
MinecraftKitErrorCodes.FILESYSTEM_PATH_TRAVERSAL,
|
|
1454
|
+
`Path escapes root: ${child}`,
|
|
1455
|
+
{
|
|
1456
|
+
context: { filePath: child, rootDirectory: root }
|
|
1457
|
+
}
|
|
1458
|
+
);
|
|
776
1459
|
}
|
|
777
|
-
}
|
|
1460
|
+
};
|
|
778
1461
|
|
|
779
1462
|
// src/core/archive.ts
|
|
780
|
-
|
|
1463
|
+
var openZip = (filePath) => {
|
|
781
1464
|
return new Promise((resolve, reject) => {
|
|
782
1465
|
yauzl__default.default.open(filePath, { lazyEntries: true, autoClose: false }, (err, zipFile) => {
|
|
783
1466
|
if (err || !zipFile) {
|
|
784
1467
|
reject(
|
|
785
|
-
new MinecraftKitError(
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1468
|
+
new MinecraftKitError(
|
|
1469
|
+
MinecraftKitErrorCodes.ARCHIVE_INVALID,
|
|
1470
|
+
`Failed to open archive: ${filePath}`,
|
|
1471
|
+
{
|
|
1472
|
+
cause: err,
|
|
1473
|
+
context: { filePath }
|
|
1474
|
+
}
|
|
1475
|
+
)
|
|
789
1476
|
);
|
|
790
1477
|
return;
|
|
791
1478
|
}
|
|
792
1479
|
resolve(new ZipReader(zipFile, filePath));
|
|
793
1480
|
});
|
|
794
1481
|
});
|
|
795
|
-
}
|
|
1482
|
+
};
|
|
796
1483
|
var ZipReader = class {
|
|
797
1484
|
constructor(file, filePath) {
|
|
798
1485
|
this.file = file;
|
|
@@ -810,7 +1497,7 @@ var ZipReader = class {
|
|
|
810
1497
|
count++;
|
|
811
1498
|
if (count > EXTRACTION_MAX_ENTRY_COUNT) {
|
|
812
1499
|
throw new MinecraftKitError(
|
|
813
|
-
|
|
1500
|
+
MinecraftKitErrorCodes.ARCHIVE_TOO_LARGE,
|
|
814
1501
|
`Archive contains too many entries: ${this.filePath}`,
|
|
815
1502
|
{ context: { filePath: this.filePath } }
|
|
816
1503
|
);
|
|
@@ -842,10 +1529,14 @@ var ZipReader = class {
|
|
|
842
1529
|
const onError = (err) => {
|
|
843
1530
|
cleanup();
|
|
844
1531
|
reject(
|
|
845
|
-
new MinecraftKitError(
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1532
|
+
new MinecraftKitError(
|
|
1533
|
+
MinecraftKitErrorCodes.ARCHIVE_INVALID,
|
|
1534
|
+
"Failed to read archive entry",
|
|
1535
|
+
{
|
|
1536
|
+
cause: err,
|
|
1537
|
+
context: { filePath: this.filePath }
|
|
1538
|
+
}
|
|
1539
|
+
)
|
|
849
1540
|
);
|
|
850
1541
|
};
|
|
851
1542
|
const cleanup = () => {
|
|
@@ -870,14 +1561,14 @@ var ZipReader = class {
|
|
|
870
1561
|
readBuffer: async () => {
|
|
871
1562
|
if (entry.uncompressedSize > EXTRACTION_MAX_FILE_SIZE) {
|
|
872
1563
|
throw new MinecraftKitError(
|
|
873
|
-
|
|
1564
|
+
MinecraftKitErrorCodes.ARCHIVE_TOO_LARGE,
|
|
874
1565
|
`Archive entry exceeds size cap: ${name}`,
|
|
875
1566
|
{ context: { filePath: this.filePath, entryName: name, size: entry.uncompressedSize } }
|
|
876
1567
|
);
|
|
877
1568
|
}
|
|
878
1569
|
if (entry.compressedSize > 0 && entry.uncompressedSize / entry.compressedSize > EXTRACTION_MAX_COMPRESSION_RATIO) {
|
|
879
1570
|
throw new MinecraftKitError(
|
|
880
|
-
|
|
1571
|
+
MinecraftKitErrorCodes.ARCHIVE_TOO_LARGE,
|
|
881
1572
|
`Archive entry exceeds compression-ratio cap: ${name}`,
|
|
882
1573
|
{ context: { filePath: this.filePath, entryName: name } }
|
|
883
1574
|
);
|
|
@@ -893,13 +1584,13 @@ var ZipReader = class {
|
|
|
893
1584
|
};
|
|
894
1585
|
}
|
|
895
1586
|
};
|
|
896
|
-
|
|
1587
|
+
var openStream = (file, entry, archivePath) => {
|
|
897
1588
|
return new Promise((resolve, reject) => {
|
|
898
1589
|
file.openReadStream(entry, (err, stream) => {
|
|
899
1590
|
if (err || !stream) {
|
|
900
1591
|
reject(
|
|
901
1592
|
new MinecraftKitError(
|
|
902
|
-
|
|
1593
|
+
MinecraftKitErrorCodes.ARCHIVE_INVALID,
|
|
903
1594
|
`Failed to open archive entry: ${entry.fileName}`,
|
|
904
1595
|
{ cause: err, context: { filePath: archivePath, entryName: entry.fileName } }
|
|
905
1596
|
)
|
|
@@ -909,8 +1600,8 @@ function openStream(file, entry, archivePath) {
|
|
|
909
1600
|
resolve(stream);
|
|
910
1601
|
});
|
|
911
1602
|
});
|
|
912
|
-
}
|
|
913
|
-
async
|
|
1603
|
+
};
|
|
1604
|
+
var extractAllToDir = async (zipPath, targetDir, options = {}) => {
|
|
914
1605
|
const exclude = options.excludePrefixes ?? ["META-INF/"];
|
|
915
1606
|
let fileCount = 0;
|
|
916
1607
|
let totalSize = 0;
|
|
@@ -926,14 +1617,14 @@ async function extractAllToDir(zipPath, targetDir, options = {}) {
|
|
|
926
1617
|
totalSize += entry.uncompressedSize;
|
|
927
1618
|
if (totalSize > EXTRACTION_MAX_TOTAL_SIZE) {
|
|
928
1619
|
throw new MinecraftKitError(
|
|
929
|
-
|
|
1620
|
+
MinecraftKitErrorCodes.ARCHIVE_TOO_LARGE,
|
|
930
1621
|
`Archive total size cap exceeded: ${zipPath}`,
|
|
931
1622
|
{ context: { filePath: zipPath } }
|
|
932
1623
|
);
|
|
933
1624
|
}
|
|
934
1625
|
if (entry.uncompressedSize > EXTRACTION_MAX_FILE_SIZE) {
|
|
935
1626
|
throw new MinecraftKitError(
|
|
936
|
-
|
|
1627
|
+
MinecraftKitErrorCodes.ARCHIVE_TOO_LARGE,
|
|
937
1628
|
`Archive entry exceeds size cap: ${entry.name}`,
|
|
938
1629
|
{ context: { filePath: zipPath, entryName: entry.name } }
|
|
939
1630
|
);
|
|
@@ -950,9 +1641,9 @@ async function extractAllToDir(zipPath, targetDir, options = {}) {
|
|
|
950
1641
|
reader.close();
|
|
951
1642
|
}
|
|
952
1643
|
return { fileCount };
|
|
953
|
-
}
|
|
1644
|
+
};
|
|
954
1645
|
var RESERVED_NAME = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\..*)?$/i;
|
|
955
|
-
|
|
1646
|
+
var assertSafeEntryName = (name) => {
|
|
956
1647
|
if (!name) {
|
|
957
1648
|
throw rejectEntry(name, "empty entry name");
|
|
958
1649
|
}
|
|
@@ -974,15 +1665,15 @@ function assertSafeEntryName(name) {
|
|
|
974
1665
|
throw rejectEntry(name, "trailing dot or whitespace");
|
|
975
1666
|
}
|
|
976
1667
|
}
|
|
977
|
-
}
|
|
978
|
-
|
|
1668
|
+
};
|
|
1669
|
+
var rejectEntry = (name, reason) => {
|
|
979
1670
|
return new MinecraftKitError(
|
|
980
|
-
|
|
1671
|
+
MinecraftKitErrorCodes.ARCHIVE_ENTRY_REJECTED,
|
|
981
1672
|
`Archive entry rejected (${reason}): ${name}`,
|
|
982
1673
|
{ context: { entryName: name, reason } }
|
|
983
1674
|
);
|
|
984
|
-
}
|
|
985
|
-
async
|
|
1675
|
+
};
|
|
1676
|
+
var readEntryBuffer = async (zipPath, entryName) => {
|
|
986
1677
|
const reader = await openZip(zipPath);
|
|
987
1678
|
try {
|
|
988
1679
|
const entry = await reader.findEntry(entryName);
|
|
@@ -991,19 +1682,23 @@ async function readEntryBuffer(zipPath, entryName) {
|
|
|
991
1682
|
} finally {
|
|
992
1683
|
reader.close();
|
|
993
1684
|
}
|
|
994
|
-
}
|
|
995
|
-
async
|
|
1685
|
+
};
|
|
1686
|
+
var extractSingleEntry = async (zipPath, entryName, destination) => {
|
|
996
1687
|
const buffer = await readEntryBuffer(zipPath, entryName);
|
|
997
1688
|
if (!buffer) {
|
|
998
|
-
throw new MinecraftKitError(
|
|
999
|
-
|
|
1000
|
-
|
|
1689
|
+
throw new MinecraftKitError(
|
|
1690
|
+
MinecraftKitErrorCodes.ARCHIVE_INVALID,
|
|
1691
|
+
`Archive entry not found: ${entryName}`,
|
|
1692
|
+
{
|
|
1693
|
+
context: { filePath: zipPath, entryName }
|
|
1694
|
+
}
|
|
1695
|
+
);
|
|
1001
1696
|
}
|
|
1002
1697
|
await atomicWrite(destination, buffer);
|
|
1003
|
-
}
|
|
1698
|
+
};
|
|
1004
1699
|
var MANIFEST_LINE_CONTINUATION = /\r?\n[ \t]/g;
|
|
1005
1700
|
var MANIFEST_MAIN_CLASS = /^Main-Class:\s*(.+)$/i;
|
|
1006
|
-
async
|
|
1701
|
+
var readJarMainClass = async (zipPath) => {
|
|
1007
1702
|
const buf = await readEntryBuffer(zipPath, "META-INF/MANIFEST.MF");
|
|
1008
1703
|
if (!buf) return void 0;
|
|
1009
1704
|
const text = buf.toString("utf8").replaceAll(MANIFEST_LINE_CONTINUATION, "");
|
|
@@ -1012,10 +1707,10 @@ async function readJarMainClass(zipPath) {
|
|
|
1012
1707
|
if (match?.[1]) return match[1].trim();
|
|
1013
1708
|
}
|
|
1014
1709
|
return void 0;
|
|
1015
|
-
}
|
|
1710
|
+
};
|
|
1016
1711
|
|
|
1017
1712
|
// src/core/collections.ts
|
|
1018
|
-
|
|
1713
|
+
var dedupeBy = (values, key) => {
|
|
1019
1714
|
const seen = /* @__PURE__ */ new Set();
|
|
1020
1715
|
const result = [];
|
|
1021
1716
|
for (const value of values) {
|
|
@@ -1025,13 +1720,25 @@ function dedupeBy(values, key) {
|
|
|
1025
1720
|
result.push(value);
|
|
1026
1721
|
}
|
|
1027
1722
|
return result;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1723
|
+
};
|
|
1724
|
+
var dedupe = (values) => {
|
|
1030
1725
|
return dedupeBy(values, (v) => v);
|
|
1031
|
-
}
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
// src/core/abort.ts
|
|
1729
|
+
var assertNotAborted = (signal, message) => {
|
|
1730
|
+
if (signal?.aborted) {
|
|
1731
|
+
throw new MinecraftKitError(MinecraftKitErrorCodes.LAUNCH_ABORTED, message);
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
var checkpoint = async (sources, message = "Operation aborted by signal") => {
|
|
1735
|
+
assertNotAborted(sources.signal, message);
|
|
1736
|
+
await sources.pauseController?.waitWhilePaused();
|
|
1737
|
+
assertNotAborted(sources.signal, message);
|
|
1738
|
+
};
|
|
1032
1739
|
|
|
1033
1740
|
// src/core/retry.ts
|
|
1034
|
-
|
|
1741
|
+
var abortableSleep = (ms, signal) => {
|
|
1035
1742
|
return new Promise((resolve, reject) => {
|
|
1036
1743
|
if (signal?.aborted) {
|
|
1037
1744
|
reject(toAbortError(signal));
|
|
@@ -1047,13 +1754,13 @@ function abortableSleep(ms, signal) {
|
|
|
1047
1754
|
};
|
|
1048
1755
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1049
1756
|
});
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
return new MinecraftKitError(
|
|
1757
|
+
};
|
|
1758
|
+
var toAbortError = (signal) => {
|
|
1759
|
+
return new MinecraftKitError(MinecraftKitErrorCodes.NETWORK_ABORTED, "Operation aborted", {
|
|
1053
1760
|
context: { reason: signal?.reason }
|
|
1054
1761
|
});
|
|
1055
|
-
}
|
|
1056
|
-
async
|
|
1762
|
+
};
|
|
1763
|
+
var withRetry = async (op, isRetryable, options = {}) => {
|
|
1057
1764
|
const max = options.maxAttempts ?? HTTP_RETRY_MAX;
|
|
1058
1765
|
const base = options.baseMs ?? HTTP_RETRY_BACKOFF_BASE_MS;
|
|
1059
1766
|
const cap = options.capMs ?? HTTP_RETRY_BACKOFF_CAP_MS;
|
|
@@ -1078,8 +1785,8 @@ async function withRetry(op, isRetryable, options = {}) {
|
|
|
1078
1785
|
}
|
|
1079
1786
|
}
|
|
1080
1787
|
throw lastError ?? new Error("withRetry exhausted attempts");
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1788
|
+
};
|
|
1789
|
+
var isHttpRetryable = (error) => {
|
|
1083
1790
|
if (!isMinecraftKitError(error)) {
|
|
1084
1791
|
return false;
|
|
1085
1792
|
}
|
|
@@ -1092,15 +1799,45 @@ function isHttpRetryable(error) {
|
|
|
1092
1799
|
return status === 0;
|
|
1093
1800
|
}
|
|
1094
1801
|
return false;
|
|
1095
|
-
}
|
|
1802
|
+
};
|
|
1803
|
+
|
|
1804
|
+
// src/types/events.ts
|
|
1805
|
+
var EventTypes = {
|
|
1806
|
+
INSTALL_PHASE_CHANGED: "install:phase-changed",
|
|
1807
|
+
DOWNLOAD_STARTED: "download:started",
|
|
1808
|
+
DOWNLOAD_PROGRESS: "download:progress",
|
|
1809
|
+
DOWNLOAD_SKIPPED: "download:skipped",
|
|
1810
|
+
DOWNLOAD_COMPLETED: "download:completed",
|
|
1811
|
+
DOWNLOAD_FAILED: "download:failed",
|
|
1812
|
+
INTEGRITY_VERIFIED: "integrity:verified",
|
|
1813
|
+
INTEGRITY_MISMATCH: "integrity:mismatch",
|
|
1814
|
+
ARCHIVE_EXTRACTED: "archive:extracted",
|
|
1815
|
+
FORGE_PROCESSOR_STARTED: "forge:processor-started",
|
|
1816
|
+
FORGE_PROCESSOR_COMPLETED: "forge:processor-completed",
|
|
1817
|
+
FORGE_PROCESSOR_OUTPUT_VERIFIED: "forge:processor-output-verified",
|
|
1818
|
+
VERIFY_FILE_CHECKED: "verify:file-checked",
|
|
1819
|
+
VERIFY_COMPLETED: "verify:completed",
|
|
1820
|
+
REPAIR_PHASE_CHANGED: "repair:phase-changed",
|
|
1821
|
+
LAUNCH_STARTING: "launch:starting",
|
|
1822
|
+
LAUNCH_STARTED: "launch:started",
|
|
1823
|
+
LAUNCH_STDOUT: "launch:stdout",
|
|
1824
|
+
LAUNCH_STDERR: "launch:stderr",
|
|
1825
|
+
LAUNCH_EXITED: "launch:exited",
|
|
1826
|
+
LAUNCH_ABORTED: "launch:aborted"
|
|
1827
|
+
};
|
|
1096
1828
|
|
|
1097
1829
|
// src/http/download.ts
|
|
1098
|
-
async
|
|
1099
|
-
|
|
1830
|
+
var downloadFile = async (http, input) => {
|
|
1831
|
+
assertSafeDownloadUrl(input.url, input.hostAllowList);
|
|
1832
|
+
const fileRef = {
|
|
1833
|
+
url: input.url,
|
|
1834
|
+
target: input.target,
|
|
1835
|
+
...input.category !== void 0 ? { category: input.category } : {}
|
|
1836
|
+
};
|
|
1100
1837
|
if (input.expectedSha1 !== void 0) {
|
|
1101
1838
|
const existing = await checkExistingFile(input.target, input.expectedSha1, input.expectedSize);
|
|
1102
1839
|
if (existing.matches) {
|
|
1103
|
-
input.onEvent?.({ type:
|
|
1840
|
+
input.onEvent?.({ type: EventTypes.DOWNLOAD_SKIPPED, file: fileRef });
|
|
1104
1841
|
return { bytesDownloaded: 0, sha1: existing.sha1, skipped: true };
|
|
1105
1842
|
}
|
|
1106
1843
|
}
|
|
@@ -1109,29 +1846,32 @@ async function downloadFile(http, input) {
|
|
|
1109
1846
|
return withRetry(
|
|
1110
1847
|
async () => {
|
|
1111
1848
|
input.onEvent?.({
|
|
1112
|
-
type:
|
|
1849
|
+
type: EventTypes.DOWNLOAD_STARTED,
|
|
1113
1850
|
file: fileRef,
|
|
1114
1851
|
expectedSize: input.expectedSize ?? 0
|
|
1115
1852
|
});
|
|
1116
1853
|
const startedAt = Date.now();
|
|
1117
1854
|
let bytesDownloaded = 0;
|
|
1118
1855
|
const hash = crypto2__default.default.createHash("sha1");
|
|
1119
|
-
const response = await http.request(input.url, {
|
|
1856
|
+
const response = await http.request(input.url, {
|
|
1857
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
1858
|
+
});
|
|
1120
1859
|
const contentLength = Number(response.headers["content-length"] ?? "0");
|
|
1121
1860
|
const total = input.expectedSize ?? (Number.isFinite(contentLength) ? contentLength : 0);
|
|
1122
1861
|
const sourceIterable = response.stream();
|
|
1123
1862
|
const counting = (async function* () {
|
|
1124
1863
|
for await (const chunk of sourceIterable) {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1864
|
+
await checkpoint(
|
|
1865
|
+
{
|
|
1866
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1867
|
+
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1868
|
+
},
|
|
1869
|
+
"Download aborted by signal"
|
|
1870
|
+
);
|
|
1131
1871
|
bytesDownloaded += chunk.byteLength;
|
|
1132
1872
|
hash.update(chunk);
|
|
1133
1873
|
input.onEvent?.({
|
|
1134
|
-
type:
|
|
1874
|
+
type: EventTypes.DOWNLOAD_PROGRESS,
|
|
1135
1875
|
file: fileRef,
|
|
1136
1876
|
bytesDownloaded,
|
|
1137
1877
|
totalBytes: total
|
|
@@ -1144,7 +1884,7 @@ async function downloadFile(http, input) {
|
|
|
1144
1884
|
} catch (cause) {
|
|
1145
1885
|
await safeUnlink(tmp);
|
|
1146
1886
|
throw new MinecraftKitError(
|
|
1147
|
-
|
|
1887
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
1148
1888
|
`Failed to write download: ${input.target}`,
|
|
1149
1889
|
{ cause, context: { filePath: input.target, url: input.url } }
|
|
1150
1890
|
);
|
|
@@ -1152,50 +1892,58 @@ async function downloadFile(http, input) {
|
|
|
1152
1892
|
const computedSha1 = hash.digest("hex");
|
|
1153
1893
|
if (input.expectedSize !== void 0 && bytesDownloaded !== input.expectedSize) {
|
|
1154
1894
|
await safeUnlink(tmp);
|
|
1155
|
-
throw new MinecraftKitError(
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1895
|
+
throw new MinecraftKitError(
|
|
1896
|
+
MinecraftKitErrorCodes.INTEGRITY_SIZE_MISMATCH,
|
|
1897
|
+
`Size mismatch for ${input.url}`,
|
|
1898
|
+
{
|
|
1899
|
+
context: {
|
|
1900
|
+
url: input.url,
|
|
1901
|
+
expectedSize: input.expectedSize,
|
|
1902
|
+
actualSize: bytesDownloaded
|
|
1903
|
+
}
|
|
1160
1904
|
}
|
|
1161
|
-
|
|
1905
|
+
);
|
|
1162
1906
|
}
|
|
1163
1907
|
if (input.expectedSha1 !== void 0 && computedSha1 !== input.expectedSha1) {
|
|
1164
1908
|
await safeUnlink(tmp);
|
|
1165
1909
|
input.onEvent?.({
|
|
1166
|
-
type:
|
|
1910
|
+
type: EventTypes.INTEGRITY_MISMATCH,
|
|
1167
1911
|
file: fileRef,
|
|
1168
1912
|
algorithm: "sha1",
|
|
1169
1913
|
expected: input.expectedSha1,
|
|
1170
1914
|
actual: computedSha1
|
|
1171
1915
|
});
|
|
1172
|
-
throw new MinecraftKitError(
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1916
|
+
throw new MinecraftKitError(
|
|
1917
|
+
MinecraftKitErrorCodes.INTEGRITY_HASH_MISMATCH,
|
|
1918
|
+
`SHA-1 mismatch for ${input.url}`,
|
|
1919
|
+
{
|
|
1920
|
+
context: {
|
|
1921
|
+
url: input.url,
|
|
1922
|
+
expectedHash: input.expectedSha1,
|
|
1923
|
+
actualHash: computedSha1
|
|
1924
|
+
}
|
|
1177
1925
|
}
|
|
1178
|
-
|
|
1926
|
+
);
|
|
1179
1927
|
}
|
|
1180
1928
|
try {
|
|
1181
1929
|
await fs__default.default.rename(tmp, input.target);
|
|
1182
1930
|
} catch (cause) {
|
|
1183
1931
|
await safeUnlink(tmp);
|
|
1184
1932
|
throw new MinecraftKitError(
|
|
1185
|
-
|
|
1933
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
1186
1934
|
`Failed to finalize download: ${input.target}`,
|
|
1187
1935
|
{ cause, context: { filePath: input.target } }
|
|
1188
1936
|
);
|
|
1189
1937
|
}
|
|
1190
1938
|
input.onEvent?.({
|
|
1191
|
-
type:
|
|
1939
|
+
type: EventTypes.DOWNLOAD_COMPLETED,
|
|
1192
1940
|
file: fileRef,
|
|
1193
1941
|
durationMs: Date.now() - startedAt,
|
|
1194
1942
|
bytes: bytesDownloaded
|
|
1195
1943
|
});
|
|
1196
1944
|
if (input.expectedSha1 !== void 0) {
|
|
1197
1945
|
input.onEvent?.({
|
|
1198
|
-
type:
|
|
1946
|
+
type: EventTypes.INTEGRITY_VERIFIED,
|
|
1199
1947
|
file: fileRef,
|
|
1200
1948
|
algorithm: "sha1",
|
|
1201
1949
|
hash: computedSha1
|
|
@@ -1208,7 +1956,7 @@ async function downloadFile(http, input) {
|
|
|
1208
1956
|
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1209
1957
|
onAttemptFailed: (error, attempt) => {
|
|
1210
1958
|
input.onEvent?.({
|
|
1211
|
-
type:
|
|
1959
|
+
type: EventTypes.DOWNLOAD_FAILED,
|
|
1212
1960
|
file: fileRef,
|
|
1213
1961
|
error: error instanceof Error ? error : new Error(String(error)),
|
|
1214
1962
|
willRetry: isHttpRetryable(error) && attempt < HTTP_RETRY_MAX - 1
|
|
@@ -1216,8 +1964,8 @@ async function downloadFile(http, input) {
|
|
|
1216
1964
|
}
|
|
1217
1965
|
}
|
|
1218
1966
|
);
|
|
1219
|
-
}
|
|
1220
|
-
async
|
|
1967
|
+
};
|
|
1968
|
+
var checkExistingFile = async (target, expectedSha1, expectedSize) => {
|
|
1221
1969
|
let stat;
|
|
1222
1970
|
try {
|
|
1223
1971
|
stat = await fs__default.default.stat(target);
|
|
@@ -1233,21 +1981,60 @@ async function checkExistingFile(target, expectedSha1, expectedSize) {
|
|
|
1233
1981
|
const buf = await fs__default.default.readFile(target);
|
|
1234
1982
|
const sha1 = crypto2__default.default.createHash("sha1").update(buf).digest("hex");
|
|
1235
1983
|
return { matches: sha1 === expectedSha1, sha1 };
|
|
1236
|
-
}
|
|
1237
|
-
async
|
|
1984
|
+
};
|
|
1985
|
+
var safeUnlink = async (filePath) => {
|
|
1238
1986
|
try {
|
|
1239
1987
|
await fs__default.default.unlink(filePath);
|
|
1240
1988
|
} catch {
|
|
1241
1989
|
}
|
|
1242
|
-
}
|
|
1990
|
+
};
|
|
1991
|
+
var assertSafeDownloadUrl = (url, allowList) => {
|
|
1992
|
+
let parsed;
|
|
1993
|
+
try {
|
|
1994
|
+
parsed = new URL(url);
|
|
1995
|
+
} catch {
|
|
1996
|
+
throw new MinecraftKitError(
|
|
1997
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
1998
|
+
`Download URL is not parseable: ${url}`,
|
|
1999
|
+
{
|
|
2000
|
+
context: { url }
|
|
2001
|
+
}
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
2005
|
+
throw new MinecraftKitError(
|
|
2006
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2007
|
+
`Download URL must use http(s); refusing scheme ${parsed.protocol}`,
|
|
2008
|
+
{ context: { url, scheme: parsed.protocol } }
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
if (allowList !== void 0 && !matchesHostAllowList(parsed.hostname, allowList)) {
|
|
2012
|
+
throw new MinecraftKitError(
|
|
2013
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2014
|
+
`Download URL host is not in the allow-list: ${parsed.hostname}`,
|
|
2015
|
+
{ context: { url, host: parsed.hostname } }
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
var matchesHostAllowList = (hostname, allowList) => {
|
|
2020
|
+
const host = hostname.toLowerCase();
|
|
2021
|
+
return allowList.some((entry) => matchesHostEntry(host, entry.toLowerCase()));
|
|
2022
|
+
};
|
|
2023
|
+
var matchesHostEntry = (host, entry) => {
|
|
2024
|
+
if (entry.startsWith("*.")) {
|
|
2025
|
+
const suffix = entry.slice(1);
|
|
2026
|
+
return host === entry.slice(2) || host.endsWith(suffix);
|
|
2027
|
+
}
|
|
2028
|
+
return host === entry;
|
|
2029
|
+
};
|
|
1243
2030
|
|
|
1244
2031
|
// src/install/forge-install.ts
|
|
1245
|
-
async
|
|
2032
|
+
var planForgeInstall = async (input) => {
|
|
1246
2033
|
const installerPath = targetPaths.forgeInstaller(input.directory, input.loader.fullVersion);
|
|
1247
2034
|
await downloadFile(input.http, {
|
|
1248
2035
|
url: input.loader.installerUrl,
|
|
1249
2036
|
target: installerPath,
|
|
1250
|
-
category:
|
|
2037
|
+
category: DownloadCategories.FORGE_INSTALLER,
|
|
1251
2038
|
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1252
2039
|
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
1253
2040
|
});
|
|
@@ -1255,7 +2042,7 @@ async function planForgeInstall(input) {
|
|
|
1255
2042
|
kind: InstallActionKinds.DOWNLOAD_FILE,
|
|
1256
2043
|
url: input.loader.installerUrl,
|
|
1257
2044
|
target: installerPath,
|
|
1258
|
-
category:
|
|
2045
|
+
category: DownloadCategories.FORGE_INSTALLER
|
|
1259
2046
|
};
|
|
1260
2047
|
const profile = await readJsonEntry(installerPath, "install_profile.json");
|
|
1261
2048
|
const versionRelative = profile.json.startsWith("/") ? profile.json.slice(1) : profile.json;
|
|
@@ -1271,14 +2058,14 @@ async function planForgeInstall(input) {
|
|
|
1271
2058
|
directory: input.directory,
|
|
1272
2059
|
system: input.system,
|
|
1273
2060
|
versionId: input.minecraft.version,
|
|
1274
|
-
category:
|
|
2061
|
+
category: DownloadCategories.FORGE_LIBRARY
|
|
1275
2062
|
});
|
|
1276
2063
|
const versionLibraries = planLibraryDownloads({
|
|
1277
2064
|
libraries: version.libraries,
|
|
1278
2065
|
directory: input.directory,
|
|
1279
2066
|
system: input.system,
|
|
1280
2067
|
versionId: version.id,
|
|
1281
|
-
category:
|
|
2068
|
+
category: DownloadCategories.FORGE_LIBRARY
|
|
1282
2069
|
});
|
|
1283
2070
|
const dedupedDownloads = dedupeBy(
|
|
1284
2071
|
[...installerLibraries.downloads, ...versionLibraries.downloads],
|
|
@@ -1313,27 +2100,23 @@ async function planForgeInstall(input) {
|
|
|
1313
2100
|
profile,
|
|
1314
2101
|
version
|
|
1315
2102
|
};
|
|
1316
|
-
}
|
|
1317
|
-
async
|
|
2103
|
+
};
|
|
2104
|
+
var readJsonEntry = async (zipPath, entryName) => {
|
|
1318
2105
|
const buffer = await readEntryBuffer(zipPath, entryName);
|
|
1319
2106
|
if (!buffer) {
|
|
1320
2107
|
throw new MinecraftKitError(
|
|
1321
|
-
|
|
2108
|
+
MinecraftKitErrorCodes.FORGE_INSTALLER_INVALID,
|
|
1322
2109
|
`Forge installer is missing required entry: ${entryName}`,
|
|
1323
2110
|
{ context: { filePath: zipPath, entryName } }
|
|
1324
2111
|
);
|
|
1325
2112
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
);
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
async function extractInstallerMavenEntries(installerPath, directory) {
|
|
2113
|
+
return parseJsonStrict(buffer.toString("utf8"), {
|
|
2114
|
+
code: MinecraftKitErrorCodes.FORGE_INSTALLER_INVALID,
|
|
2115
|
+
message: `Forge installer entry is not valid JSON: ${entryName}`,
|
|
2116
|
+
context: { filePath: zipPath, entryName }
|
|
2117
|
+
});
|
|
2118
|
+
};
|
|
2119
|
+
var extractInstallerMavenEntries = async (installerPath, directory) => {
|
|
1337
2120
|
const reader = await openZip(installerPath);
|
|
1338
2121
|
try {
|
|
1339
2122
|
for await (const entry of reader.entries()) {
|
|
@@ -1346,16 +2129,16 @@ async function extractInstallerMavenEntries(installerPath, directory) {
|
|
|
1346
2129
|
} finally {
|
|
1347
2130
|
reader.close();
|
|
1348
2131
|
}
|
|
1349
|
-
}
|
|
1350
|
-
async
|
|
2132
|
+
};
|
|
2133
|
+
var resolveProfileData = async (input) => {
|
|
1351
2134
|
const tokens = {};
|
|
1352
2135
|
for (const [key, sided] of Object.entries(input.profile.data)) {
|
|
1353
2136
|
const raw = sided.client;
|
|
1354
2137
|
tokens[key] = await resolveDataValue(raw, input.installerPath, input.directory);
|
|
1355
2138
|
}
|
|
1356
2139
|
return { tokens };
|
|
1357
|
-
}
|
|
1358
|
-
async
|
|
2140
|
+
};
|
|
2141
|
+
var resolveDataValue = async (raw, installerPath, directory) => {
|
|
1359
2142
|
if (raw.startsWith("[") && raw.endsWith("]")) {
|
|
1360
2143
|
const coord = raw.slice(1, -1);
|
|
1361
2144
|
const relativePath = mavenRelativePathFor(coord);
|
|
@@ -1374,8 +2157,8 @@ async function resolveDataValue(raw, installerPath, directory) {
|
|
|
1374
2157
|
return { value: destination, isPath: true };
|
|
1375
2158
|
}
|
|
1376
2159
|
return { value: raw, isPath: false };
|
|
1377
|
-
}
|
|
1378
|
-
async
|
|
2160
|
+
};
|
|
2161
|
+
var buildProcessorActions = async (input) => {
|
|
1379
2162
|
const builtIns = {
|
|
1380
2163
|
SIDE: { value: "client", isPath: false },
|
|
1381
2164
|
MINECRAFT_JAR: {
|
|
@@ -1408,12 +2191,12 @@ async function buildProcessorActions(input) {
|
|
|
1408
2191
|
index++;
|
|
1409
2192
|
}
|
|
1410
2193
|
return actions;
|
|
1411
|
-
}
|
|
1412
|
-
|
|
2194
|
+
};
|
|
2195
|
+
var processorAppliesToClient = (processor) => {
|
|
1413
2196
|
if (!processor.sides || processor.sides.length === 0) return true;
|
|
1414
2197
|
return processor.sides.includes("client");
|
|
1415
|
-
}
|
|
1416
|
-
|
|
2198
|
+
};
|
|
2199
|
+
var buildProcessorAction = (input) => {
|
|
1417
2200
|
const jarPath = path__default.default.join(
|
|
1418
2201
|
targetPaths.librariesDir(input.directory),
|
|
1419
2202
|
mavenRelativePathFor(input.processor.jar)
|
|
@@ -1440,26 +2223,30 @@ function buildProcessorAction(input) {
|
|
|
1440
2223
|
args,
|
|
1441
2224
|
outputs
|
|
1442
2225
|
};
|
|
1443
|
-
}
|
|
1444
|
-
|
|
2226
|
+
};
|
|
2227
|
+
var substituteToken = (raw, tokens) => {
|
|
1445
2228
|
if (raw.startsWith("[") && raw.endsWith("]")) {
|
|
1446
2229
|
return path__default.default.join(...mavenRelativePathFor(raw.slice(1, -1)).split("/"));
|
|
1447
2230
|
}
|
|
1448
2231
|
return raw.replaceAll(/\{([A-Z0-9_]+)\}/g, (match, key) => {
|
|
1449
2232
|
const token = tokens[key];
|
|
1450
2233
|
if (token === void 0) {
|
|
1451
|
-
throw new MinecraftKitError(
|
|
1452
|
-
|
|
1453
|
-
|
|
2234
|
+
throw new MinecraftKitError(
|
|
2235
|
+
MinecraftKitErrorCodes.FORGE_INSTALLER_INVALID,
|
|
2236
|
+
`Unknown processor token: ${match}`,
|
|
2237
|
+
{
|
|
2238
|
+
context: { token: key }
|
|
2239
|
+
}
|
|
2240
|
+
);
|
|
1454
2241
|
}
|
|
1455
2242
|
return token.value;
|
|
1456
2243
|
});
|
|
1457
|
-
}
|
|
1458
|
-
|
|
2244
|
+
};
|
|
2245
|
+
var stripLiteralPrefix = (value) => {
|
|
1459
2246
|
const stripped = value.startsWith("'") ? value.slice(1) : value;
|
|
1460
2247
|
return stripped.endsWith("'") ? stripped.slice(0, -1) : stripped;
|
|
1461
|
-
}
|
|
1462
|
-
async
|
|
2248
|
+
};
|
|
2249
|
+
var planRuntimeDownloads = async (input) => {
|
|
1463
2250
|
const manifest = await fetchJson(input.http, input.cache, {
|
|
1464
2251
|
url: input.runtime.manifestUrl,
|
|
1465
2252
|
cacheKey: `runtime-manifest:${input.runtime.component}:${input.runtime.platformKey}:${input.runtime.manifestSha1}`,
|
|
@@ -1480,14 +2267,14 @@ async function planRuntimeDownloads(input) {
|
|
|
1480
2267
|
target,
|
|
1481
2268
|
expectedSha1: entry.downloads.raw.sha1,
|
|
1482
2269
|
expectedSize: entry.downloads.raw.size,
|
|
1483
|
-
category:
|
|
2270
|
+
category: DownloadCategories.RUNTIME_FILE
|
|
1484
2271
|
});
|
|
1485
2272
|
}
|
|
1486
2273
|
return { actions, manifest };
|
|
1487
|
-
}
|
|
2274
|
+
};
|
|
1488
2275
|
|
|
1489
2276
|
// src/install/planner.ts
|
|
1490
|
-
async
|
|
2277
|
+
var planInstall = async (input) => {
|
|
1491
2278
|
const { target } = input;
|
|
1492
2279
|
const actions = [];
|
|
1493
2280
|
actions.push({
|
|
@@ -1496,7 +2283,7 @@ async function planInstall(input) {
|
|
|
1496
2283
|
target: targetPaths.versionJar(target.directory, target.minecraft.version),
|
|
1497
2284
|
expectedSha1: target.minecraft.manifest.downloads.client.sha1,
|
|
1498
2285
|
expectedSize: target.minecraft.manifest.downloads.client.size,
|
|
1499
|
-
category:
|
|
2286
|
+
category: DownloadCategories.CLIENT_JAR
|
|
1500
2287
|
});
|
|
1501
2288
|
actions.push({
|
|
1502
2289
|
kind: InstallActionKinds.WRITE_VERSION_JSON,
|
|
@@ -1509,7 +2296,7 @@ async function planInstall(input) {
|
|
|
1509
2296
|
directory: target.directory,
|
|
1510
2297
|
system: target.runtime.system,
|
|
1511
2298
|
versionId: target.minecraft.version,
|
|
1512
|
-
category:
|
|
2299
|
+
category: DownloadCategories.LIBRARY
|
|
1513
2300
|
});
|
|
1514
2301
|
actions.push(...vanillaLibraries.downloads);
|
|
1515
2302
|
actions.push(...vanillaLibraries.nativeExtractions);
|
|
@@ -1529,7 +2316,7 @@ async function planInstall(input) {
|
|
|
1529
2316
|
target: targetPaths.loggingConfig(target.directory, logging.file.id),
|
|
1530
2317
|
expectedSha1: logging.file.sha1,
|
|
1531
2318
|
expectedSize: logging.file.size,
|
|
1532
|
-
category:
|
|
2319
|
+
category: DownloadCategories.LOGGING_CONFIG
|
|
1533
2320
|
});
|
|
1534
2321
|
}
|
|
1535
2322
|
const runtimePlan = await planRuntimeDownloads({
|
|
@@ -1579,7 +2366,7 @@ async function planInstall(input) {
|
|
|
1579
2366
|
totalActions: actions.length,
|
|
1580
2367
|
totalBytes
|
|
1581
2368
|
};
|
|
1582
|
-
}
|
|
2369
|
+
};
|
|
1583
2370
|
|
|
1584
2371
|
// node_modules/yocto-queue/index.js
|
|
1585
2372
|
var Node = class {
|
|
@@ -1682,22 +2469,126 @@ function pLimit(concurrency) {
|
|
|
1682
2469
|
const generator = (function_, ...arguments_) => new Promise((resolve) => {
|
|
1683
2470
|
enqueue(function_, resolve, arguments_);
|
|
1684
2471
|
});
|
|
1685
|
-
Object.defineProperties(generator, {
|
|
1686
|
-
activeCount: {
|
|
1687
|
-
get: () => activeCount
|
|
1688
|
-
},
|
|
1689
|
-
pendingCount: {
|
|
1690
|
-
get: () => queue.size
|
|
1691
|
-
},
|
|
1692
|
-
clearQueue: {
|
|
1693
|
-
value() {
|
|
1694
|
-
queue.clear();
|
|
1695
|
-
}
|
|
2472
|
+
Object.defineProperties(generator, {
|
|
2473
|
+
activeCount: {
|
|
2474
|
+
get: () => activeCount
|
|
2475
|
+
},
|
|
2476
|
+
pendingCount: {
|
|
2477
|
+
get: () => queue.size
|
|
2478
|
+
},
|
|
2479
|
+
clearQueue: {
|
|
2480
|
+
value() {
|
|
2481
|
+
queue.clear();
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
return generator;
|
|
2486
|
+
}
|
|
2487
|
+
var sha1OfFile = async (filePath) => {
|
|
2488
|
+
const hash = crypto2__default.default.createHash("sha1");
|
|
2489
|
+
const stream = fs$1.createReadStream(filePath);
|
|
2490
|
+
try {
|
|
2491
|
+
await new Promise((resolve, reject) => {
|
|
2492
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
2493
|
+
stream.on("end", resolve);
|
|
2494
|
+
stream.on("error", reject);
|
|
2495
|
+
});
|
|
2496
|
+
} finally {
|
|
2497
|
+
if (!stream.destroyed) stream.destroy();
|
|
2498
|
+
}
|
|
2499
|
+
return hash.digest("hex");
|
|
2500
|
+
};
|
|
2501
|
+
|
|
2502
|
+
// src/install/processor.ts
|
|
2503
|
+
var runProcessor = async (input) => {
|
|
2504
|
+
const startedAt = Date.now();
|
|
2505
|
+
const mainClass = await resolveProcessorMainClass(input.action);
|
|
2506
|
+
emit(input, { type: EventTypes.FORGE_PROCESSOR_STARTED, index: input.action.index, mainClass });
|
|
2507
|
+
const exit = await spawnProcessor(input, mainClass);
|
|
2508
|
+
if (exit.code !== 0) {
|
|
2509
|
+
throw new MinecraftKitError(
|
|
2510
|
+
MinecraftKitErrorCodes.FORGE_PROCESSOR_FAILED,
|
|
2511
|
+
`Forge processor exited with code ${exit.code ?? "(signal)"}: ${mainClass}`,
|
|
2512
|
+
{
|
|
2513
|
+
context: {
|
|
2514
|
+
mainClass,
|
|
2515
|
+
stderr: exit.stderr,
|
|
2516
|
+
...exit.code !== null ? { exitCode: exit.code } : {}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
);
|
|
2520
|
+
}
|
|
2521
|
+
input.onEvent?.({
|
|
2522
|
+
type: EventTypes.FORGE_PROCESSOR_COMPLETED,
|
|
2523
|
+
processor: { index: input.action.index, mainClass },
|
|
2524
|
+
exitCode: exit.code ?? 0,
|
|
2525
|
+
durationMs: Date.now() - startedAt
|
|
2526
|
+
});
|
|
2527
|
+
await verifyProcessorOutputs(input, mainClass);
|
|
2528
|
+
};
|
|
2529
|
+
var resolveProcessorMainClass = async (action) => {
|
|
2530
|
+
const processorJar = action.classpath[0];
|
|
2531
|
+
if (processorJar === void 0) {
|
|
2532
|
+
throw new MinecraftKitError(
|
|
2533
|
+
MinecraftKitErrorCodes.FORGE_INSTALLER_INVALID,
|
|
2534
|
+
"Forge processor has an empty classpath",
|
|
2535
|
+
{ context: { processorIndex: action.index } }
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2538
|
+
const mainClass = await readJarMainClass(processorJar);
|
|
2539
|
+
if (!mainClass) {
|
|
2540
|
+
throw new MinecraftKitError(
|
|
2541
|
+
MinecraftKitErrorCodes.FORGE_INSTALLER_INVALID,
|
|
2542
|
+
`Forge processor jar has no Main-Class: ${processorJar}`,
|
|
2543
|
+
{ context: { filePath: processorJar } }
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
return mainClass;
|
|
2547
|
+
};
|
|
2548
|
+
var spawnProcessor = async (input, mainClass) => {
|
|
2549
|
+
const classpathSeparator = process.platform === "win32" ? ";" : ":";
|
|
2550
|
+
const args = [
|
|
2551
|
+
"-cp",
|
|
2552
|
+
input.action.classpath.join(classpathSeparator),
|
|
2553
|
+
mainClass,
|
|
2554
|
+
...input.action.args
|
|
2555
|
+
];
|
|
2556
|
+
const stderrTail = [];
|
|
2557
|
+
const child = input.spawner.spawn(input.javaPath, args, { cwd: process.cwd() });
|
|
2558
|
+
child.stdout.on("data", () => {
|
|
2559
|
+
});
|
|
2560
|
+
child.stderr.on("data", (line) => {
|
|
2561
|
+
if (stderrTail.length >= MAX_PROCESSOR_STDERR_LINES) stderrTail.shift();
|
|
2562
|
+
stderrTail.push(line);
|
|
2563
|
+
});
|
|
2564
|
+
const exit = await child.exited;
|
|
2565
|
+
return { code: exit.code, stderr: stderrTail.join("\n") };
|
|
2566
|
+
};
|
|
2567
|
+
var verifyProcessorOutputs = async (input, mainClass) => {
|
|
2568
|
+
for (const [outputPath, expectedSha1] of Object.entries(input.action.outputs)) {
|
|
2569
|
+
const sha1 = await sha1OfFile(outputPath);
|
|
2570
|
+
if (sha1 !== expectedSha1) {
|
|
2571
|
+
throw new MinecraftKitError(
|
|
2572
|
+
MinecraftKitErrorCodes.FORGE_PROCESSOR_FAILED,
|
|
2573
|
+
`Processor output hash mismatch: ${outputPath}`,
|
|
2574
|
+
{ context: { filePath: outputPath, expectedHash: expectedSha1, actualHash: sha1 } }
|
|
2575
|
+
);
|
|
1696
2576
|
}
|
|
2577
|
+
input.onEvent?.({
|
|
2578
|
+
type: EventTypes.FORGE_PROCESSOR_OUTPUT_VERIFIED,
|
|
2579
|
+
processor: { index: input.action.index, mainClass },
|
|
2580
|
+
path: outputPath
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
};
|
|
2584
|
+
var emit = (input, event) => {
|
|
2585
|
+
input.onEvent?.({
|
|
2586
|
+
type: event.type,
|
|
2587
|
+
processor: { index: event.index, mainClass: event.mainClass },
|
|
2588
|
+
total: input.total
|
|
1697
2589
|
});
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
async function materializeRuntimeExtras(input) {
|
|
2590
|
+
};
|
|
2591
|
+
var materializeRuntimeExtras = async (input) => {
|
|
1701
2592
|
const root = targetPaths.runtimeRoot(
|
|
1702
2593
|
input.directory,
|
|
1703
2594
|
input.runtime.component,
|
|
@@ -1716,20 +2607,20 @@ async function materializeRuntimeExtras(input) {
|
|
|
1716
2607
|
});
|
|
1717
2608
|
}
|
|
1718
2609
|
}
|
|
1719
|
-
}
|
|
1720
|
-
async
|
|
2610
|
+
};
|
|
2611
|
+
var unlinkIfPresent = async (target) => {
|
|
1721
2612
|
try {
|
|
1722
2613
|
await fs__default.default.unlink(target);
|
|
1723
2614
|
} catch (cause) {
|
|
1724
2615
|
if (isNotFound(cause)) return;
|
|
1725
2616
|
throw new MinecraftKitError(
|
|
1726
|
-
|
|
2617
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
1727
2618
|
`Failed to remove stale runtime entry: ${target}`,
|
|
1728
2619
|
{ cause, context: { filePath: target } }
|
|
1729
2620
|
);
|
|
1730
2621
|
}
|
|
1731
|
-
}
|
|
1732
|
-
async
|
|
2622
|
+
};
|
|
2623
|
+
var createLinkOrCopy = async (root, relativePath, linkTarget, destination) => {
|
|
1733
2624
|
try {
|
|
1734
2625
|
await fs__default.default.symlink(linkTarget, destination);
|
|
1735
2626
|
return;
|
|
@@ -1739,7 +2630,7 @@ async function createLinkOrCopy(root, relativePath, linkTarget, destination) {
|
|
|
1739
2630
|
await fs__default.default.copyFile(absoluteSource, destination);
|
|
1740
2631
|
} catch (copyError) {
|
|
1741
2632
|
throw new MinecraftKitError(
|
|
1742
|
-
|
|
2633
|
+
MinecraftKitErrorCodes.FILESYSTEM_WRITE_ERROR,
|
|
1743
2634
|
`Failed to materialize runtime entry: ${destination}`,
|
|
1744
2635
|
{
|
|
1745
2636
|
cause: copyError,
|
|
@@ -1752,283 +2643,209 @@ async function createLinkOrCopy(root, relativePath, linkTarget, destination) {
|
|
|
1752
2643
|
);
|
|
1753
2644
|
}
|
|
1754
2645
|
}
|
|
1755
|
-
}
|
|
1756
|
-
|
|
2646
|
+
};
|
|
2647
|
+
var isNotFound = (error) => {
|
|
1757
2648
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1758
|
-
}
|
|
1759
|
-
|
|
2649
|
+
};
|
|
2650
|
+
var errorMessage = (error) => {
|
|
1760
2651
|
return error instanceof Error ? error.message : String(error);
|
|
1761
|
-
}
|
|
2652
|
+
};
|
|
1762
2653
|
|
|
1763
2654
|
// src/install/runner.ts
|
|
1764
2655
|
var DOWNLOAD_GROUPS = [
|
|
1765
|
-
{ categories: [
|
|
1766
|
-
{ categories: [
|
|
1767
|
-
{ categories: [
|
|
1768
|
-
{ categories: [
|
|
1769
|
-
{ categories: [
|
|
1770
|
-
{ categories: [
|
|
1771
|
-
{ categories: [
|
|
1772
|
-
{
|
|
2656
|
+
{ categories: [DownloadCategories.RUNTIME_FILE], phase: InstallPhases.INSTALLING_RUNTIME },
|
|
2657
|
+
{ categories: [DownloadCategories.CLIENT_JAR], phase: InstallPhases.DOWNLOADING_CLIENT_JAR },
|
|
2658
|
+
{ categories: [DownloadCategories.LIBRARY], phase: InstallPhases.DOWNLOADING_LIBRARIES },
|
|
2659
|
+
{ categories: [DownloadCategories.ASSET_INDEX], phase: InstallPhases.DOWNLOADING_ASSET_INDEX },
|
|
2660
|
+
{ categories: [DownloadCategories.ASSET], phase: InstallPhases.DOWNLOADING_ASSETS },
|
|
2661
|
+
{ categories: [DownloadCategories.LOGGING_CONFIG], phase: InstallPhases.WRITING_FILES },
|
|
2662
|
+
{ categories: [DownloadCategories.FABRIC_LIBRARY], phase: InstallPhases.INSTALLING_FABRIC },
|
|
2663
|
+
{
|
|
2664
|
+
categories: [DownloadCategories.FORGE_INSTALLER, DownloadCategories.FORGE_LIBRARY],
|
|
2665
|
+
phase: InstallPhases.INSTALLING_FORGE
|
|
2666
|
+
}
|
|
1773
2667
|
];
|
|
1774
|
-
async
|
|
2668
|
+
var runInstall = async (input) => {
|
|
1775
2669
|
const startedAt = Date.now();
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
const
|
|
2670
|
+
const counters = { bytesDownloaded: 0, actionsCompleted: 0, actionsSkipped: 0 };
|
|
2671
|
+
const ctx = createContext(input, counters);
|
|
2672
|
+
ctx.enterPhase(InstallPhases.PLANNING);
|
|
2673
|
+
const plannedActions = partitionActions(input);
|
|
2674
|
+
await runDownloadsStage(ctx, plannedActions.downloads);
|
|
2675
|
+
await runWritesStage(ctx, plannedActions.writes);
|
|
2676
|
+
await runNativesStage(ctx, plannedActions.natives);
|
|
2677
|
+
await runRuntimeStage(ctx);
|
|
2678
|
+
await runProcessorsStage(ctx, plannedActions.processors);
|
|
2679
|
+
ctx.enterPhase(InstallPhases.COMPLETED);
|
|
2680
|
+
return {
|
|
2681
|
+
targetId: input.plan.targetId,
|
|
2682
|
+
bytesDownloaded: counters.bytesDownloaded,
|
|
2683
|
+
actionsCompleted: counters.actionsCompleted,
|
|
2684
|
+
actionsSkipped: counters.actionsSkipped,
|
|
2685
|
+
durationMs: Date.now() - startedAt
|
|
2686
|
+
};
|
|
2687
|
+
};
|
|
2688
|
+
var createContext = (input, counters) => {
|
|
1780
2689
|
let currentPhase = null;
|
|
1781
2690
|
const enterPhase = (phase) => {
|
|
1782
2691
|
if (phase === currentPhase) return;
|
|
1783
|
-
onEvent?.({ type:
|
|
2692
|
+
input.onEvent?.({ type: EventTypes.INSTALL_PHASE_CHANGED, phase, previous: currentPhase });
|
|
1784
2693
|
currentPhase = phase;
|
|
1785
2694
|
};
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
2695
|
+
const checkpoint2 = () => checkpoint(
|
|
2696
|
+
{
|
|
2697
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
2698
|
+
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
2699
|
+
},
|
|
2700
|
+
"Install aborted by signal"
|
|
2701
|
+
);
|
|
2702
|
+
return {
|
|
2703
|
+
input,
|
|
2704
|
+
counters,
|
|
2705
|
+
checkpoint: checkpoint2,
|
|
2706
|
+
enterPhase,
|
|
2707
|
+
limit: pLimit(input.concurrency ?? DOWNLOAD_CONCURRENCY)
|
|
2708
|
+
};
|
|
2709
|
+
};
|
|
2710
|
+
var partitionActions = (input) => {
|
|
2711
|
+
const filter = input.actionCategories;
|
|
2712
|
+
return {
|
|
2713
|
+
downloads: input.plan.actions.filter(isDownload).filter((a) => filter ? filter.has(a.category) : true),
|
|
2714
|
+
natives: input.plan.actions.filter(isNative),
|
|
2715
|
+
writes: input.plan.actions.filter(isWrite),
|
|
2716
|
+
processors: input.plan.actions.filter(isProcessor)
|
|
1794
2717
|
};
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
const writeActions = input.plan.actions.filter(isWrite);
|
|
1799
|
-
const processors = input.plan.actions.filter(isProcessor);
|
|
1800
|
-
enterPhase(InstallPhases.PLANNING);
|
|
1801
|
-
const limit = pLimit(input.concurrency ?? DOWNLOAD_CONCURRENCY);
|
|
2718
|
+
};
|
|
2719
|
+
var runDownloadsStage = async (ctx, downloads) => {
|
|
2720
|
+
if (downloads.length === 0) return;
|
|
1802
2721
|
for (const group of DOWNLOAD_GROUPS) {
|
|
1803
2722
|
const groupActions = downloads.filter((action) => group.categories.includes(action.category));
|
|
1804
2723
|
if (groupActions.length === 0) continue;
|
|
1805
|
-
await checkpoint();
|
|
1806
|
-
enterPhase(group.phase);
|
|
1807
|
-
await
|
|
1808
|
-
groupActions.map(
|
|
1809
|
-
(action) => limit(async () => {
|
|
1810
|
-
await checkpoint();
|
|
1811
|
-
const result = await downloadFile(input.http, {
|
|
1812
|
-
url: action.url,
|
|
1813
|
-
target: action.target,
|
|
1814
|
-
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
1815
|
-
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
1816
|
-
...action.category !== void 0 ? { category: action.category } : {},
|
|
1817
|
-
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1818
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1819
|
-
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1820
|
-
});
|
|
1821
|
-
bytesDownloaded += result.bytesDownloaded;
|
|
1822
|
-
if (result.skipped) actionsSkipped++;
|
|
1823
|
-
actionsCompleted++;
|
|
1824
|
-
})
|
|
1825
|
-
)
|
|
1826
|
-
);
|
|
2724
|
+
await ctx.checkpoint();
|
|
2725
|
+
ctx.enterPhase(group.phase);
|
|
2726
|
+
await runDownloadGroup(ctx, groupActions);
|
|
1827
2727
|
}
|
|
1828
2728
|
const ungrouped = downloads.filter(
|
|
1829
2729
|
(action) => !DOWNLOAD_GROUPS.some((g) => g.categories.includes(action.category))
|
|
1830
2730
|
);
|
|
1831
2731
|
if (ungrouped.length > 0) {
|
|
1832
|
-
await checkpoint();
|
|
1833
|
-
enterPhase(InstallPhases.DOWNLOADING_LIBRARIES);
|
|
1834
|
-
await
|
|
1835
|
-
ungrouped.map(
|
|
1836
|
-
(action) => limit(async () => {
|
|
1837
|
-
await checkpoint();
|
|
1838
|
-
const result = await downloadFile(input.http, {
|
|
1839
|
-
url: action.url,
|
|
1840
|
-
target: action.target,
|
|
1841
|
-
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
1842
|
-
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
1843
|
-
...action.category !== void 0 ? { category: action.category } : {},
|
|
1844
|
-
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1845
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1846
|
-
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1847
|
-
});
|
|
1848
|
-
bytesDownloaded += result.bytesDownloaded;
|
|
1849
|
-
if (result.skipped) actionsSkipped++;
|
|
1850
|
-
actionsCompleted++;
|
|
1851
|
-
})
|
|
1852
|
-
)
|
|
1853
|
-
);
|
|
1854
|
-
}
|
|
1855
|
-
if (writeActions.length > 0) {
|
|
1856
|
-
await checkpoint();
|
|
1857
|
-
enterPhase(InstallPhases.WRITING_FILES);
|
|
1858
|
-
for (const action of writeActions) {
|
|
1859
|
-
await checkpoint();
|
|
1860
|
-
await atomicWrite(action.path, action.content);
|
|
1861
|
-
actionsCompleted++;
|
|
1862
|
-
}
|
|
2732
|
+
await ctx.checkpoint();
|
|
2733
|
+
ctx.enterPhase(InstallPhases.DOWNLOADING_LIBRARIES);
|
|
2734
|
+
await runDownloadGroup(ctx, ungrouped);
|
|
1863
2735
|
}
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2736
|
+
};
|
|
2737
|
+
var runDownloadGroup = async (ctx, groupActions) => {
|
|
2738
|
+
await Promise.all(
|
|
2739
|
+
groupActions.map(
|
|
2740
|
+
(action) => ctx.limit(async () => {
|
|
2741
|
+
await ctx.checkpoint();
|
|
2742
|
+
const result = await downloadFile(ctx.input.http, {
|
|
2743
|
+
url: action.url,
|
|
2744
|
+
target: action.target,
|
|
2745
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
2746
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
2747
|
+
...action.category !== void 0 ? { category: action.category } : {},
|
|
2748
|
+
...ctx.input.signal !== void 0 ? { signal: ctx.input.signal } : {},
|
|
2749
|
+
...ctx.input.onEvent !== void 0 ? { onEvent: ctx.input.onEvent } : {},
|
|
2750
|
+
...ctx.input.pauseController !== void 0 ? { pauseController: ctx.input.pauseController } : {}
|
|
2751
|
+
});
|
|
2752
|
+
ctx.counters.bytesDownloaded += result.bytesDownloaded;
|
|
2753
|
+
if (result.skipped) ctx.counters.actionsSkipped++;
|
|
2754
|
+
ctx.counters.actionsCompleted++;
|
|
2755
|
+
})
|
|
2756
|
+
)
|
|
2757
|
+
);
|
|
2758
|
+
};
|
|
2759
|
+
var runWritesStage = async (ctx, writes) => {
|
|
2760
|
+
if (writes.length === 0) return;
|
|
2761
|
+
await ctx.checkpoint();
|
|
2762
|
+
ctx.enterPhase(InstallPhases.WRITING_FILES);
|
|
2763
|
+
for (const action of writes) {
|
|
2764
|
+
await ctx.checkpoint();
|
|
2765
|
+
await atomicWrite(action.path, action.content);
|
|
2766
|
+
ctx.counters.actionsCompleted++;
|
|
1880
2767
|
}
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
2768
|
+
};
|
|
2769
|
+
var runNativesStage = async (ctx, natives) => {
|
|
2770
|
+
if (natives.length === 0) return;
|
|
2771
|
+
await ctx.checkpoint();
|
|
2772
|
+
ctx.enterPhase(InstallPhases.EXTRACTING_NATIVES);
|
|
2773
|
+
for (const action of natives) {
|
|
2774
|
+
await ctx.checkpoint();
|
|
2775
|
+
const { fileCount } = await extractAllToDir(action.source, action.destination, {
|
|
2776
|
+
excludePrefixes: action.exclude
|
|
1890
2777
|
});
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
2778
|
+
ctx.input.onEvent?.({
|
|
2779
|
+
type: EventTypes.ARCHIVE_EXTRACTED,
|
|
2780
|
+
archive: action.source,
|
|
2781
|
+
target: action.destination,
|
|
2782
|
+
fileCount
|
|
1895
2783
|
});
|
|
2784
|
+
ctx.counters.actionsCompleted++;
|
|
1896
2785
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
input.plan.target.runtime.system.os,
|
|
1910
|
-
input.plan.target.runtime.installRoot
|
|
1911
|
-
);
|
|
1912
|
-
for (const action of processors) {
|
|
1913
|
-
await checkpoint();
|
|
1914
|
-
await runProcessor({
|
|
1915
|
-
action,
|
|
1916
|
-
javaPath,
|
|
1917
|
-
spawner: input.spawner,
|
|
1918
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1919
|
-
total: processors.length
|
|
1920
|
-
});
|
|
1921
|
-
actionsCompleted++;
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
enterPhase(InstallPhases.COMPLETED);
|
|
1925
|
-
return {
|
|
1926
|
-
targetId: input.plan.targetId,
|
|
1927
|
-
bytesDownloaded,
|
|
1928
|
-
actionsCompleted,
|
|
1929
|
-
actionsSkipped,
|
|
1930
|
-
durationMs: Date.now() - startedAt
|
|
1931
|
-
};
|
|
1932
|
-
}
|
|
1933
|
-
function isDownload(action) {
|
|
1934
|
-
return action.kind === InstallActionKinds.DOWNLOAD_FILE;
|
|
1935
|
-
}
|
|
1936
|
-
function isNative(action) {
|
|
1937
|
-
return action.kind === InstallActionKinds.EXTRACT_NATIVE;
|
|
1938
|
-
}
|
|
1939
|
-
function isProcessor(action) {
|
|
1940
|
-
return action.kind === InstallActionKinds.RUN_FORGE_PROCESSOR;
|
|
1941
|
-
}
|
|
1942
|
-
function isWrite(action) {
|
|
1943
|
-
return action.kind === InstallActionKinds.WRITE_VERSION_JSON || action.kind === InstallActionKinds.WRITE_LOGGING_CONFIG;
|
|
1944
|
-
}
|
|
1945
|
-
async function runProcessor(input) {
|
|
1946
|
-
const startedAt = Date.now();
|
|
1947
|
-
const processorJar = input.action.classpath[0];
|
|
1948
|
-
if (processorJar === void 0) {
|
|
1949
|
-
throw new MinecraftKitError(
|
|
1950
|
-
"FORGE_INSTALLER_INVALID",
|
|
1951
|
-
"Forge processor has an empty classpath",
|
|
1952
|
-
{ context: { processorIndex: input.action.index } }
|
|
1953
|
-
);
|
|
1954
|
-
}
|
|
1955
|
-
const mainClass = await readJarMainClass(processorJar);
|
|
1956
|
-
if (!mainClass) {
|
|
1957
|
-
throw new MinecraftKitError(
|
|
1958
|
-
"FORGE_INSTALLER_INVALID",
|
|
1959
|
-
`Forge processor jar has no Main-Class: ${processorJar}`,
|
|
1960
|
-
{ context: { filePath: processorJar } }
|
|
1961
|
-
);
|
|
1962
|
-
}
|
|
1963
|
-
const classpathSeparator = process.platform === "win32" ? ";" : ":";
|
|
1964
|
-
const args = [
|
|
1965
|
-
"-cp",
|
|
1966
|
-
input.action.classpath.join(classpathSeparator),
|
|
1967
|
-
mainClass,
|
|
1968
|
-
...input.action.args
|
|
1969
|
-
];
|
|
1970
|
-
input.onEvent?.({
|
|
1971
|
-
type: "forge:processor-started",
|
|
1972
|
-
processor: { index: input.action.index, mainClass },
|
|
1973
|
-
total: input.total
|
|
1974
|
-
});
|
|
1975
|
-
const stderrTail = [];
|
|
1976
|
-
const child = input.spawner.spawn(input.javaPath, args, { cwd: process.cwd() });
|
|
1977
|
-
child.stdout.on("data", () => {
|
|
2786
|
+
};
|
|
2787
|
+
var runRuntimeStage = async (ctx) => {
|
|
2788
|
+
const runtime = ctx.input.plan.target.runtime;
|
|
2789
|
+
if (runtime === void 0) return;
|
|
2790
|
+
await ctx.checkpoint();
|
|
2791
|
+
ctx.enterPhase(InstallPhases.INSTALLING_RUNTIME);
|
|
2792
|
+
const runtimePlan = await planRuntimeDownloads({
|
|
2793
|
+
runtime,
|
|
2794
|
+
directory: ctx.input.plan.directory,
|
|
2795
|
+
http: ctx.input.http,
|
|
2796
|
+
cache: ctx.input.cache,
|
|
2797
|
+
...ctx.input.signal !== void 0 ? { signal: ctx.input.signal } : {}
|
|
1978
2798
|
});
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
2799
|
+
await materializeRuntimeExtras({
|
|
2800
|
+
runtime,
|
|
2801
|
+
directory: ctx.input.plan.directory,
|
|
2802
|
+
manifest: runtimePlan.manifest
|
|
1982
2803
|
});
|
|
1983
|
-
|
|
1984
|
-
|
|
2804
|
+
};
|
|
2805
|
+
var runProcessorsStage = async (ctx, processors) => {
|
|
2806
|
+
if (processors.length === 0) return;
|
|
2807
|
+
await ctx.checkpoint();
|
|
2808
|
+
ctx.enterPhase(InstallPhases.RUNNING_FORGE_PROCESSORS);
|
|
2809
|
+
const target = ctx.input.plan.target;
|
|
2810
|
+
if (target.loader?.type !== Loaders.FORGE) {
|
|
1985
2811
|
throw new MinecraftKitError(
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
{
|
|
1989
|
-
context: {
|
|
1990
|
-
exitCode: exit.code ?? void 0,
|
|
1991
|
-
mainClass,
|
|
1992
|
-
stderr: stderrTail.join("\n")
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
2812
|
+
MinecraftKitErrorCodes.FORGE_PROCESSOR_FAILED,
|
|
2813
|
+
"Forge processors planned for a non-Forge target"
|
|
1995
2814
|
);
|
|
1996
2815
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
for (const
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
}
|
|
2012
|
-
input.onEvent?.({
|
|
2013
|
-
type: "forge:processor-output-verified",
|
|
2014
|
-
processor: { index: input.action.index, mainClass },
|
|
2015
|
-
path: outputPath
|
|
2816
|
+
const javaPath = targetPaths.runtimeJavaExecutable(
|
|
2817
|
+
ctx.input.plan.directory,
|
|
2818
|
+
target.runtime.component,
|
|
2819
|
+
target.runtime.system.os,
|
|
2820
|
+
target.runtime.installRoot
|
|
2821
|
+
);
|
|
2822
|
+
for (const action of processors) {
|
|
2823
|
+
await ctx.checkpoint();
|
|
2824
|
+
await runProcessor({
|
|
2825
|
+
action,
|
|
2826
|
+
javaPath,
|
|
2827
|
+
spawner: ctx.input.spawner,
|
|
2828
|
+
...ctx.input.onEvent !== void 0 ? { onEvent: ctx.input.onEvent } : {},
|
|
2829
|
+
total: processors.length
|
|
2016
2830
|
});
|
|
2831
|
+
ctx.counters.actionsCompleted++;
|
|
2017
2832
|
}
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2833
|
+
};
|
|
2834
|
+
var isDownload = (action) => {
|
|
2835
|
+
return action.kind === InstallActionKinds.DOWNLOAD_FILE;
|
|
2836
|
+
};
|
|
2837
|
+
var isNative = (action) => {
|
|
2838
|
+
return action.kind === InstallActionKinds.EXTRACT_NATIVE;
|
|
2839
|
+
};
|
|
2840
|
+
var isProcessor = (action) => {
|
|
2841
|
+
return action.kind === InstallActionKinds.RUN_FORGE_PROCESSOR;
|
|
2842
|
+
};
|
|
2843
|
+
var isWrite = (action) => {
|
|
2844
|
+
return action.kind === InstallActionKinds.WRITE_VERSION_JSON || action.kind === InstallActionKinds.WRITE_LOGGING_CONFIG;
|
|
2845
|
+
};
|
|
2029
2846
|
|
|
2030
2847
|
// src/install/runtime-install.ts
|
|
2031
|
-
async
|
|
2848
|
+
var planRuntimeInstall = async (input) => {
|
|
2032
2849
|
const runtimePlan = await planRuntimeDownloads({
|
|
2033
2850
|
runtime: input.target.runtime,
|
|
2034
2851
|
directory: input.target.directory,
|
|
@@ -2049,8 +2866,8 @@ async function planRuntimeInstall(input) {
|
|
|
2049
2866
|
totalActions: actions.length,
|
|
2050
2867
|
totalBytes
|
|
2051
2868
|
};
|
|
2052
|
-
}
|
|
2053
|
-
async
|
|
2869
|
+
};
|
|
2870
|
+
var planStandaloneRuntimeInstall = async (input) => {
|
|
2054
2871
|
const runtimePlan = await planRuntimeDownloads({
|
|
2055
2872
|
runtime: input.runtime,
|
|
2056
2873
|
directory: input.directory,
|
|
@@ -2066,9 +2883,7 @@ async function planStandaloneRuntimeInstall(input) {
|
|
|
2066
2883
|
const target = {
|
|
2067
2884
|
id: input.id,
|
|
2068
2885
|
directory: input.directory,
|
|
2069
|
-
runtime: input.runtime
|
|
2070
|
-
minecraft: void 0,
|
|
2071
|
-
loader: void 0
|
|
2886
|
+
runtime: input.runtime
|
|
2072
2887
|
};
|
|
2073
2888
|
return {
|
|
2074
2889
|
targetId: input.id,
|
|
@@ -2078,7 +2893,7 @@ async function planStandaloneRuntimeInstall(input) {
|
|
|
2078
2893
|
totalActions: actions.length,
|
|
2079
2894
|
totalBytes
|
|
2080
2895
|
};
|
|
2081
|
-
}
|
|
2896
|
+
};
|
|
2082
2897
|
|
|
2083
2898
|
// src/constants/launch.ts
|
|
2084
2899
|
var BASE_JVM_ARGS = [
|
|
@@ -2099,7 +2914,7 @@ var LEGACY_JVM_ARGS = [
|
|
|
2099
2914
|
var MACOS_JVM_ARGS = ["-Xdock:name=Minecraft"];
|
|
2100
2915
|
|
|
2101
2916
|
// src/launch/arguments.ts
|
|
2102
|
-
|
|
2917
|
+
var flattenArguments = (entries, context) => {
|
|
2103
2918
|
const result = [];
|
|
2104
2919
|
for (const entry of entries) {
|
|
2105
2920
|
if (typeof entry === "string") {
|
|
@@ -2114,16 +2929,16 @@ function flattenArguments(entries, context) {
|
|
|
2114
2929
|
}
|
|
2115
2930
|
}
|
|
2116
2931
|
return result;
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2932
|
+
};
|
|
2933
|
+
var splitLegacyArguments = (raw) => {
|
|
2119
2934
|
return raw.trim().length === 0 ? [] : raw.trim().split(/\s+/);
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2935
|
+
};
|
|
2936
|
+
var pickArguments = (args, context) => {
|
|
2122
2937
|
return {
|
|
2123
2938
|
game: flattenArguments(args?.game ?? [], context),
|
|
2124
2939
|
jvm: flattenArguments(args?.jvm ?? [], context)
|
|
2125
2940
|
};
|
|
2126
|
-
}
|
|
2941
|
+
};
|
|
2127
2942
|
|
|
2128
2943
|
// src/launch/jvm-compat.ts
|
|
2129
2944
|
var FLAG_MIN_JAVA = [
|
|
@@ -2132,7 +2947,7 @@ var FLAG_MIN_JAVA = [
|
|
|
2132
2947
|
{ prefix: "-XX:+UseCompactObjectHeaders", minJava: 24 },
|
|
2133
2948
|
{ prefix: "-XX:+UseZGC", minJava: 15 }
|
|
2134
2949
|
];
|
|
2135
|
-
|
|
2950
|
+
var filterArgsForJava = (input) => {
|
|
2136
2951
|
if (!Number.isFinite(input.javaMajor) || input.javaMajor <= 0) return input.args;
|
|
2137
2952
|
const out = [];
|
|
2138
2953
|
for (const arg of input.args) {
|
|
@@ -2150,26 +2965,30 @@ function filterArgsForJava(input) {
|
|
|
2150
2965
|
out.push(arg);
|
|
2151
2966
|
}
|
|
2152
2967
|
return out;
|
|
2153
|
-
}
|
|
2968
|
+
};
|
|
2154
2969
|
|
|
2155
2970
|
// src/launch/placeholders.ts
|
|
2156
|
-
|
|
2971
|
+
var substituteArg = (raw, values) => {
|
|
2157
2972
|
return raw.replaceAll(/\$\{([a-zA-Z0-9_]+)\}/g, (match, key) => {
|
|
2158
2973
|
const value = values[key];
|
|
2159
2974
|
if (value === void 0) {
|
|
2160
|
-
throw new MinecraftKitError(
|
|
2161
|
-
|
|
2162
|
-
|
|
2975
|
+
throw new MinecraftKitError(
|
|
2976
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2977
|
+
`Unknown launch placeholder: ${match}`,
|
|
2978
|
+
{
|
|
2979
|
+
context: { placeholder: key }
|
|
2980
|
+
}
|
|
2981
|
+
);
|
|
2163
2982
|
}
|
|
2164
2983
|
return value;
|
|
2165
2984
|
});
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2985
|
+
};
|
|
2986
|
+
var substituteArgs = (args, values) => {
|
|
2168
2987
|
return args.map((arg) => substituteArg(arg, values));
|
|
2169
|
-
}
|
|
2988
|
+
};
|
|
2170
2989
|
|
|
2171
2990
|
// src/launch/args-composition.ts
|
|
2172
|
-
|
|
2991
|
+
var composeArgs = (input) => {
|
|
2173
2992
|
const minMb = input.options.memory?.minMb ?? DEFAULT_MIN_MB;
|
|
2174
2993
|
const maxMb = input.options.memory?.maxMb ?? DEFAULT_MAX_MB;
|
|
2175
2994
|
const memoryArgs = [`-Xms${minMb}M`, `-Xmx${maxMb}M`];
|
|
@@ -2219,8 +3038,8 @@ function composeArgs(input) {
|
|
|
2219
3038
|
);
|
|
2220
3039
|
}
|
|
2221
3040
|
return { jvmArgs: [...jvmArgs, ...extraJvm], gameArgs };
|
|
2222
|
-
}
|
|
2223
|
-
|
|
3041
|
+
};
|
|
3042
|
+
var buildClasspath = (input) => {
|
|
2224
3043
|
const seen = /* @__PURE__ */ new Set();
|
|
2225
3044
|
const entries = [];
|
|
2226
3045
|
for (const library of input.merged.libraries) {
|
|
@@ -2236,41 +3055,33 @@ function buildClasspath(input) {
|
|
|
2236
3055
|
const versionJar = targetPaths.versionJar(input.directory, input.versionId);
|
|
2237
3056
|
if (!seen.has(versionJar)) entries.push(versionJar);
|
|
2238
3057
|
return entries;
|
|
2239
|
-
}
|
|
2240
|
-
|
|
3058
|
+
};
|
|
3059
|
+
var relativeFor = (library) => {
|
|
2241
3060
|
if (library.downloads?.artifact?.path) return library.downloads.artifact.path;
|
|
2242
3061
|
if (library.name) {
|
|
2243
3062
|
const coord = parseMavenCoordinate(library.name);
|
|
2244
3063
|
return mavenRelativePath(coord);
|
|
2245
3064
|
}
|
|
2246
3065
|
return null;
|
|
2247
|
-
}
|
|
2248
|
-
|
|
3066
|
+
};
|
|
3067
|
+
var offlineUuidFor = (username) => {
|
|
2249
3068
|
const md5 = crypto2__default.default.createHash("md5");
|
|
2250
3069
|
md5.update(`OfflinePlayer:${username}`, "utf8");
|
|
2251
3070
|
const bytes = md5.digest();
|
|
2252
3071
|
bytes[6] = (bytes[6] ?? 0) & 15 | 48;
|
|
2253
3072
|
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
2254
3073
|
return formatUuid(bytes);
|
|
2255
|
-
}
|
|
2256
|
-
|
|
3074
|
+
};
|
|
3075
|
+
var formatUuid = (bytes) => {
|
|
2257
3076
|
const hex = bytes.toString("hex");
|
|
2258
3077
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
2259
|
-
}
|
|
2260
|
-
|
|
3078
|
+
};
|
|
3079
|
+
var stripUuidDashes = (uuid) => {
|
|
2261
3080
|
return uuid.replaceAll("-", "");
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
|
-
// src/types/auth.ts
|
|
2265
|
-
var AuthModes = {
|
|
2266
|
-
/** Offline-mode play with a chosen username and synthetic UUID. */
|
|
2267
|
-
OFFLINE: "offline",
|
|
2268
|
-
/** Pre-authenticated session — caller provides the access token and identity. */
|
|
2269
|
-
ONLINE: "online"
|
|
2270
3081
|
};
|
|
2271
3082
|
|
|
2272
3083
|
// src/launch/placeholder-values.ts
|
|
2273
|
-
|
|
3084
|
+
var buildPlaceholderValues = (input) => {
|
|
2274
3085
|
const cpSeparator = process.platform === "win32" ? ";" : ":";
|
|
2275
3086
|
const directory = input.target.directory;
|
|
2276
3087
|
const username = input.auth.username;
|
|
@@ -2303,11 +3114,20 @@ function buildPlaceholderValues(input) {
|
|
|
2303
3114
|
resolution_width: input.options.resolution?.width.toString() ?? "",
|
|
2304
3115
|
resolution_height: input.options.resolution?.height.toString() ?? ""
|
|
2305
3116
|
};
|
|
2306
|
-
}
|
|
3117
|
+
};
|
|
2307
3118
|
|
|
2308
3119
|
// src/core/manifest-merge.ts
|
|
2309
|
-
|
|
2310
|
-
const
|
|
3120
|
+
var mergeManifest = (parent, child) => {
|
|
3121
|
+
const args = mergeArguments(parent.arguments, child.arguments);
|
|
3122
|
+
const minecraftArguments = child.minecraftArguments ?? parent.minecraftArguments;
|
|
3123
|
+
const javaVersion = child.javaVersion ?? parent.javaVersion;
|
|
3124
|
+
const logging = child.logging ?? parent.logging;
|
|
3125
|
+
const inheritsFrom = child.inheritsFrom ?? parent.inheritsFrom;
|
|
3126
|
+
const releaseTime = child.releaseTime ?? parent.releaseTime;
|
|
3127
|
+
const time = child.time ?? parent.time;
|
|
3128
|
+
const minimumLauncherVersion = child.minimumLauncherVersion ?? parent.minimumLauncherVersion;
|
|
3129
|
+
const complianceLevel = child.complianceLevel ?? parent.complianceLevel;
|
|
3130
|
+
return {
|
|
2311
3131
|
id: child.id || parent.id,
|
|
2312
3132
|
type: child.type ?? parent.type,
|
|
2313
3133
|
mainClass: child.mainClass ?? parent.mainClass,
|
|
@@ -2315,19 +3135,18 @@ function mergeManifest(parent, child) {
|
|
|
2315
3135
|
assets: child.assets ?? parent.assets,
|
|
2316
3136
|
downloads: { ...parent.downloads, ...child.downloads },
|
|
2317
3137
|
libraries: mergeLibraries(parent.libraries, child.libraries),
|
|
2318
|
-
arguments:
|
|
2319
|
-
minecraftArguments
|
|
2320
|
-
javaVersion
|
|
2321
|
-
logging
|
|
2322
|
-
inheritsFrom
|
|
2323
|
-
releaseTime
|
|
2324
|
-
time
|
|
2325
|
-
minimumLauncherVersion
|
|
2326
|
-
complianceLevel
|
|
3138
|
+
...args !== void 0 ? { arguments: args } : {},
|
|
3139
|
+
...minecraftArguments !== void 0 ? { minecraftArguments } : {},
|
|
3140
|
+
...javaVersion !== void 0 ? { javaVersion } : {},
|
|
3141
|
+
...logging !== void 0 ? { logging } : {},
|
|
3142
|
+
...inheritsFrom !== void 0 ? { inheritsFrom } : {},
|
|
3143
|
+
...releaseTime !== void 0 ? { releaseTime } : {},
|
|
3144
|
+
...time !== void 0 ? { time } : {},
|
|
3145
|
+
...minimumLauncherVersion !== void 0 ? { minimumLauncherVersion } : {},
|
|
3146
|
+
...complianceLevel !== void 0 ? { complianceLevel } : {}
|
|
2327
3147
|
};
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
function libraryDedupeKey(library) {
|
|
3148
|
+
};
|
|
3149
|
+
var libraryDedupeKey = (library) => {
|
|
2331
3150
|
if (!library.name) return null;
|
|
2332
3151
|
try {
|
|
2333
3152
|
const coord = parseMavenCoordinate(library.name);
|
|
@@ -2336,8 +3155,8 @@ function libraryDedupeKey(library) {
|
|
|
2336
3155
|
} catch {
|
|
2337
3156
|
return null;
|
|
2338
3157
|
}
|
|
2339
|
-
}
|
|
2340
|
-
|
|
3158
|
+
};
|
|
3159
|
+
var mergeLibraries = (parent, child) => {
|
|
2341
3160
|
const byKey = /* @__PURE__ */ new Map();
|
|
2342
3161
|
const unkeyed = [];
|
|
2343
3162
|
for (const lib of [...parent, ...child]) {
|
|
@@ -2349,8 +3168,8 @@ function mergeLibraries(parent, child) {
|
|
|
2349
3168
|
byKey.set(key, lib);
|
|
2350
3169
|
}
|
|
2351
3170
|
return [...byKey.values(), ...unkeyed];
|
|
2352
|
-
}
|
|
2353
|
-
|
|
3171
|
+
};
|
|
3172
|
+
var mergeArguments = (parent, child) => {
|
|
2354
3173
|
if (!parent && !child) return void 0;
|
|
2355
3174
|
const parentGame = parent?.game ?? [];
|
|
2356
3175
|
const parentJvm = parent?.jvm ?? [];
|
|
@@ -2360,10 +3179,10 @@ function mergeArguments(parent, child) {
|
|
|
2360
3179
|
game: [...parentGame, ...childGame],
|
|
2361
3180
|
jvm: [...parentJvm, ...childJvm]
|
|
2362
3181
|
};
|
|
2363
|
-
}
|
|
3182
|
+
};
|
|
2364
3183
|
|
|
2365
3184
|
// src/launch/version-resolution.ts
|
|
2366
|
-
async
|
|
3185
|
+
var resolveLaunchVersion = async (target) => {
|
|
2367
3186
|
if (target.loader.type === Loaders.VANILLA) {
|
|
2368
3187
|
return {
|
|
2369
3188
|
versionId: target.minecraft.version,
|
|
@@ -2374,8 +3193,8 @@ async function resolveLaunchVersion(target) {
|
|
|
2374
3193
|
const versionId = await pickInstalledVersionId(target);
|
|
2375
3194
|
const merged = await loadAndMerge(target.directory, versionId, target.minecraft.manifest);
|
|
2376
3195
|
return { versionId, merged, chain: [versionId, target.minecraft.version] };
|
|
2377
|
-
}
|
|
2378
|
-
async
|
|
3196
|
+
};
|
|
3197
|
+
var pickClientJarVersionId = async (directory, chain) => {
|
|
2379
3198
|
for (const id of chain) {
|
|
2380
3199
|
const jar = targetPaths.versionJar(directory, id);
|
|
2381
3200
|
if (await fileExists(jar)) return id;
|
|
@@ -2383,13 +3202,13 @@ async function pickClientJarVersionId(directory, chain) {
|
|
|
2383
3202
|
const fallback = chain.at(-1);
|
|
2384
3203
|
if (fallback === void 0) {
|
|
2385
3204
|
throw new MinecraftKitError(
|
|
2386
|
-
|
|
3205
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
2387
3206
|
"Cannot resolve a client jar version id from an empty inheritsFrom chain"
|
|
2388
3207
|
);
|
|
2389
3208
|
}
|
|
2390
3209
|
return fallback;
|
|
2391
|
-
}
|
|
2392
|
-
async
|
|
3210
|
+
};
|
|
3211
|
+
var pickInstalledVersionId = async (target) => {
|
|
2393
3212
|
if (target.loader.type === Loaders.FABRIC) {
|
|
2394
3213
|
const candidate = target.loader.profile.id;
|
|
2395
3214
|
const versionJsonPath = targetPaths.versionJson(target.directory, candidate);
|
|
@@ -2401,46 +3220,38 @@ async function pickInstalledVersionId(target) {
|
|
|
2401
3220
|
const versionJsonPath = targetPaths.versionJson(target.directory, id);
|
|
2402
3221
|
if (!await fileExists(versionJsonPath)) continue;
|
|
2403
3222
|
const text = await readText(versionJsonPath);
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
return id;
|
|
2408
|
-
}
|
|
2409
|
-
} catch {
|
|
3223
|
+
const parsed = parseJsonOrUndefined(text);
|
|
3224
|
+
if (parsed?.inheritsFrom === target.minecraft.version && (id.includes("forge") || (parsed.id ?? "").includes("forge"))) {
|
|
3225
|
+
return id;
|
|
2410
3226
|
}
|
|
2411
3227
|
}
|
|
2412
3228
|
}
|
|
2413
3229
|
throw new MinecraftKitError(
|
|
2414
|
-
|
|
3230
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
2415
3231
|
`Could not find an installed version JSON for target ${target.id}`,
|
|
2416
3232
|
{ context: { targetId: target.id, loaderType: target.loader.type } }
|
|
2417
3233
|
);
|
|
2418
|
-
}
|
|
2419
|
-
async
|
|
3234
|
+
};
|
|
3235
|
+
var loadAndMerge = async (directory, versionId, parentManifest) => {
|
|
2420
3236
|
const versionJsonPath = targetPaths.versionJson(directory, versionId);
|
|
2421
3237
|
const text = await readText(versionJsonPath);
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
"MANIFEST_INVALID",
|
|
2428
|
-
`Version JSON is not valid JSON: ${versionJsonPath}`,
|
|
2429
|
-
{ cause, context: { filePath: versionJsonPath } }
|
|
2430
|
-
);
|
|
2431
|
-
}
|
|
3238
|
+
const child = parseJsonStrict(text, {
|
|
3239
|
+
code: MinecraftKitErrorCodes.MANIFEST_INVALID,
|
|
3240
|
+
message: `Version JSON is not valid JSON: ${versionJsonPath}`,
|
|
3241
|
+
context: { filePath: versionJsonPath }
|
|
3242
|
+
});
|
|
2432
3243
|
if (child.inheritsFrom !== void 0 && child.inheritsFrom !== parentManifest.id) {
|
|
2433
3244
|
return mergeManifest(parentManifest, child);
|
|
2434
3245
|
}
|
|
2435
3246
|
return mergeManifest(parentManifest, child);
|
|
2436
|
-
}
|
|
3247
|
+
};
|
|
2437
3248
|
|
|
2438
3249
|
// src/launch/compose.ts
|
|
2439
|
-
async
|
|
3250
|
+
var composeLaunch = async (input) => {
|
|
2440
3251
|
const { target, options } = input;
|
|
2441
3252
|
if (!options.auth.username || options.auth.username.length === 0) {
|
|
2442
3253
|
throw new MinecraftKitError(
|
|
2443
|
-
|
|
3254
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2444
3255
|
`Auth username must be non-empty (target ${target.id})`,
|
|
2445
3256
|
{ context: { targetId: target.id } }
|
|
2446
3257
|
);
|
|
@@ -2487,22 +3298,22 @@ async function composeLaunch(input) {
|
|
|
2487
3298
|
auth: options.auth,
|
|
2488
3299
|
workingDirectory: target.directory
|
|
2489
3300
|
};
|
|
2490
|
-
}
|
|
2491
|
-
|
|
3301
|
+
};
|
|
3302
|
+
var buildFeatures = (options) => {
|
|
2492
3303
|
const features = { ...options.features ?? {} };
|
|
2493
3304
|
if (options.resolution !== void 0) {
|
|
2494
3305
|
features.has_custom_resolution = true;
|
|
2495
3306
|
}
|
|
2496
3307
|
return features;
|
|
2497
|
-
}
|
|
3308
|
+
};
|
|
2498
3309
|
|
|
2499
3310
|
// src/launch/runner.ts
|
|
2500
|
-
|
|
3311
|
+
var runLaunch = (input) => {
|
|
2501
3312
|
const composition = input.composition;
|
|
2502
3313
|
const options = input.options ?? {};
|
|
2503
3314
|
const args = [...composition.jvmArgs, composition.mainClass, ...composition.gameArgs];
|
|
2504
3315
|
options.onEvent?.({
|
|
2505
|
-
type:
|
|
3316
|
+
type: EventTypes.LAUNCH_STARTING,
|
|
2506
3317
|
command: composition.javaPath,
|
|
2507
3318
|
args,
|
|
2508
3319
|
cwd: composition.workingDirectory
|
|
@@ -2511,19 +3322,19 @@ function runLaunch(input) {
|
|
|
2511
3322
|
cwd: composition.workingDirectory,
|
|
2512
3323
|
...composition.env !== void 0 ? { env: composition.env } : {}
|
|
2513
3324
|
});
|
|
2514
|
-
options.onEvent?.({ type:
|
|
3325
|
+
options.onEvent?.({ type: EventTypes.LAUNCH_STARTED, pid: child.pid });
|
|
2515
3326
|
child.stdout.on("data", (line) => {
|
|
2516
|
-
options.onEvent?.({ type:
|
|
3327
|
+
options.onEvent?.({ type: EventTypes.LAUNCH_STDOUT, line });
|
|
2517
3328
|
});
|
|
2518
3329
|
child.stderr.on("data", (line) => {
|
|
2519
|
-
options.onEvent?.({ type:
|
|
3330
|
+
options.onEvent?.({ type: EventTypes.LAUNCH_STDERR, line });
|
|
2520
3331
|
});
|
|
2521
3332
|
const grace = options.killGracePeriodMs ?? DEFAULT_KILL_GRACE_MS;
|
|
2522
3333
|
let aborted = false;
|
|
2523
3334
|
const doAbort = (reason) => {
|
|
2524
3335
|
if (aborted) return;
|
|
2525
3336
|
aborted = true;
|
|
2526
|
-
options.onEvent?.({ type:
|
|
3337
|
+
options.onEvent?.({ type: EventTypes.LAUNCH_ABORTED, reason });
|
|
2527
3338
|
child.kill("SIGTERM");
|
|
2528
3339
|
setTimeout(() => child.kill("SIGKILL"), grace).unref();
|
|
2529
3340
|
};
|
|
@@ -2538,10 +3349,10 @@ function runLaunch(input) {
|
|
|
2538
3349
|
}
|
|
2539
3350
|
const exited = (async () => {
|
|
2540
3351
|
const { code, signal } = await child.exited;
|
|
2541
|
-
options.onEvent?.({ type:
|
|
3352
|
+
options.onEvent?.({ type: EventTypes.LAUNCH_EXITED, code, signal });
|
|
2542
3353
|
if (!aborted && code !== 0 && code !== null) {
|
|
2543
3354
|
throw new MinecraftKitError(
|
|
2544
|
-
|
|
3355
|
+
MinecraftKitErrorCodes.LAUNCH_PROCESS_FAILED,
|
|
2545
3356
|
`Minecraft process exited with code ${code}`,
|
|
2546
3357
|
{ context: { exitCode: code } }
|
|
2547
3358
|
);
|
|
@@ -2555,13 +3366,13 @@ function runLaunch(input) {
|
|
|
2555
3366
|
doAbort(reason ?? "user");
|
|
2556
3367
|
}
|
|
2557
3368
|
};
|
|
2558
|
-
}
|
|
2559
|
-
|
|
3369
|
+
};
|
|
3370
|
+
var reasonFrom = (value) => {
|
|
2560
3371
|
if (value === void 0) return "aborted";
|
|
2561
3372
|
if (typeof value === "string") return value;
|
|
2562
3373
|
if (value instanceof Error) return value.message;
|
|
2563
3374
|
return String(value);
|
|
2564
|
-
}
|
|
3375
|
+
};
|
|
2565
3376
|
var ChildProcessSpawner = class {
|
|
2566
3377
|
spawn(command, args, options) {
|
|
2567
3378
|
const child = child_process.spawn(command, [...args], {
|
|
@@ -2585,14 +3396,14 @@ var ChildProcessSpawner = class {
|
|
|
2585
3396
|
};
|
|
2586
3397
|
}
|
|
2587
3398
|
};
|
|
2588
|
-
|
|
3399
|
+
var streamFromBuffer = (stream) => {
|
|
2589
3400
|
if (!stream) {
|
|
2590
3401
|
return { on() {
|
|
2591
3402
|
} };
|
|
2592
3403
|
}
|
|
2593
3404
|
let buffer$1 = "";
|
|
2594
3405
|
const listeners = /* @__PURE__ */ new Set();
|
|
2595
|
-
const
|
|
3406
|
+
const emit2 = (line) => {
|
|
2596
3407
|
for (const listener of listeners) listener(line);
|
|
2597
3408
|
};
|
|
2598
3409
|
stream.on("data", (chunk) => {
|
|
@@ -2602,35 +3413,36 @@ function streamFromBuffer(stream) {
|
|
|
2602
3413
|
while (index !== -1) {
|
|
2603
3414
|
const line = buffer$1.slice(0, index).replace(/\r$/, "");
|
|
2604
3415
|
buffer$1 = buffer$1.slice(index + 1);
|
|
2605
|
-
emitBounded(
|
|
3416
|
+
emitBounded(emit2, line);
|
|
2606
3417
|
index = buffer$1.indexOf("\n");
|
|
2607
3418
|
}
|
|
2608
3419
|
while (buffer$1.length > SPAWNER_MAX_LINE_BYTES) {
|
|
2609
|
-
|
|
3420
|
+
emit2(buffer$1.slice(0, SPAWNER_MAX_LINE_BYTES));
|
|
2610
3421
|
buffer$1 = buffer$1.slice(SPAWNER_MAX_LINE_BYTES);
|
|
2611
3422
|
}
|
|
2612
3423
|
});
|
|
2613
3424
|
stream.on("end", () => {
|
|
2614
3425
|
if (buffer$1.length > 0) {
|
|
2615
|
-
emitBounded(
|
|
3426
|
+
emitBounded(emit2, buffer$1);
|
|
2616
3427
|
buffer$1 = "";
|
|
2617
3428
|
}
|
|
3429
|
+
listeners.clear();
|
|
2618
3430
|
});
|
|
2619
3431
|
return {
|
|
2620
3432
|
on(_event, listener) {
|
|
2621
3433
|
listeners.add(listener);
|
|
2622
3434
|
}
|
|
2623
3435
|
};
|
|
2624
|
-
}
|
|
2625
|
-
|
|
3436
|
+
};
|
|
3437
|
+
var emitBounded = (emit2, line) => {
|
|
2626
3438
|
if (line.length <= SPAWNER_MAX_LINE_BYTES) {
|
|
2627
|
-
|
|
3439
|
+
emit2(line);
|
|
2628
3440
|
return;
|
|
2629
3441
|
}
|
|
2630
3442
|
for (let i = 0; i < line.length; i += SPAWNER_MAX_LINE_BYTES) {
|
|
2631
|
-
|
|
3443
|
+
emit2(line.slice(i, i + SPAWNER_MAX_LINE_BYTES));
|
|
2632
3444
|
}
|
|
2633
|
-
}
|
|
3445
|
+
};
|
|
2634
3446
|
|
|
2635
3447
|
// src/types/verify.ts
|
|
2636
3448
|
var VerificationKinds = {
|
|
@@ -2655,24 +3467,14 @@ var VerifyFileCategories = {
|
|
|
2655
3467
|
RUNTIME_FILE: "runtime-file",
|
|
2656
3468
|
LOGGING_CONFIG: "logging-config"
|
|
2657
3469
|
};
|
|
2658
|
-
async function sha1OfFile(filePath) {
|
|
2659
|
-
const hash = crypto2__default.default.createHash("sha1");
|
|
2660
|
-
await new Promise((resolve, reject) => {
|
|
2661
|
-
const stream = fs$1.createReadStream(filePath);
|
|
2662
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
2663
|
-
stream.on("end", resolve);
|
|
2664
|
-
stream.on("error", reject);
|
|
2665
|
-
});
|
|
2666
|
-
return hash.digest("hex");
|
|
2667
|
-
}
|
|
2668
3470
|
|
|
2669
3471
|
// src/verify/helpers.ts
|
|
2670
|
-
async
|
|
3472
|
+
var runVerification = async (input, check) => {
|
|
2671
3473
|
const startedAt = Date.now();
|
|
2672
3474
|
const results = [];
|
|
2673
3475
|
const record = (result) => {
|
|
2674
3476
|
results.push(result);
|
|
2675
|
-
input.onEvent?.({ type:
|
|
3477
|
+
input.onEvent?.({ type: EventTypes.VERIFY_FILE_CHECKED, file: result });
|
|
2676
3478
|
};
|
|
2677
3479
|
await check(record);
|
|
2678
3480
|
return {
|
|
@@ -2683,8 +3485,8 @@ async function runVerification(input, check) {
|
|
|
2683
3485
|
checkedFiles: results.length,
|
|
2684
3486
|
durationMs: Date.now() - startedAt
|
|
2685
3487
|
};
|
|
2686
|
-
}
|
|
2687
|
-
async
|
|
3488
|
+
};
|
|
3489
|
+
var verifyHashedFile = async (input) => {
|
|
2688
3490
|
if (!await fileExists(input.path)) {
|
|
2689
3491
|
return {
|
|
2690
3492
|
path: input.path,
|
|
@@ -2731,8 +3533,8 @@ async function verifyHashedFile(input) {
|
|
|
2731
3533
|
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2732
3534
|
...input.url !== void 0 ? { url: input.url } : {}
|
|
2733
3535
|
};
|
|
2734
|
-
}
|
|
2735
|
-
async
|
|
3536
|
+
};
|
|
3537
|
+
var verifyExistence = async (input) => {
|
|
2736
3538
|
if (await fileExists(input.path)) {
|
|
2737
3539
|
return {
|
|
2738
3540
|
path: input.path,
|
|
@@ -2747,8 +3549,8 @@ async function verifyExistence(input) {
|
|
|
2747
3549
|
status: VerifyFileStatuses.MISSING,
|
|
2748
3550
|
...input.url !== void 0 ? { url: input.url } : {}
|
|
2749
3551
|
};
|
|
2750
|
-
}
|
|
2751
|
-
async
|
|
3552
|
+
};
|
|
3553
|
+
var findForgeVersionJsonPath = async (directory, minecraftVersion) => {
|
|
2752
3554
|
const versionsDir = targetPaths.versionsDir(directory);
|
|
2753
3555
|
const dirs = await listChildDirectories(versionsDir);
|
|
2754
3556
|
for (const id of dirs) {
|
|
@@ -2761,21 +3563,17 @@ async function findForgeVersionJsonPath(directory, minecraftVersion) {
|
|
|
2761
3563
|
if (parsed === minecraftVersion) return jsonPath;
|
|
2762
3564
|
}
|
|
2763
3565
|
return null;
|
|
2764
|
-
}
|
|
2765
|
-
async
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
} catch {
|
|
2770
|
-
return void 0;
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
3566
|
+
};
|
|
3567
|
+
var tryParseInheritsFrom = async (jsonPath) => {
|
|
3568
|
+
const parsed = parseJsonOrUndefined(await readText(jsonPath));
|
|
3569
|
+
return parsed?.inheritsFrom;
|
|
3570
|
+
};
|
|
2773
3571
|
|
|
2774
3572
|
// src/verify/fabric.ts
|
|
2775
|
-
async
|
|
3573
|
+
var verifyFabric = async (input) => {
|
|
2776
3574
|
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
2777
3575
|
throw new MinecraftKitError(
|
|
2778
|
-
|
|
3576
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2779
3577
|
`verify.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
2780
3578
|
);
|
|
2781
3579
|
}
|
|
@@ -2798,14 +3596,14 @@ async function verifyFabric(input) {
|
|
|
2798
3596
|
directory: input.target.directory,
|
|
2799
3597
|
system: input.target.runtime.system,
|
|
2800
3598
|
versionId: input.target.minecraft.version,
|
|
2801
|
-
category:
|
|
3599
|
+
category: DownloadCategories.FABRIC_LIBRARY
|
|
2802
3600
|
});
|
|
2803
3601
|
for (const action of fabricLibraries.downloads) {
|
|
2804
3602
|
record(
|
|
2805
3603
|
await verifyHashedFile({
|
|
2806
3604
|
path: action.target,
|
|
2807
|
-
expectedSha1: action.expectedSha1,
|
|
2808
|
-
expectedSize: action.expectedSize,
|
|
3605
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
3606
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
2809
3607
|
...action.url ? { url: action.url } : {},
|
|
2810
3608
|
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2811
3609
|
})
|
|
@@ -2813,13 +3611,13 @@ async function verifyFabric(input) {
|
|
|
2813
3611
|
}
|
|
2814
3612
|
}
|
|
2815
3613
|
);
|
|
2816
|
-
}
|
|
3614
|
+
};
|
|
2817
3615
|
|
|
2818
3616
|
// src/verify/forge.ts
|
|
2819
|
-
async
|
|
3617
|
+
var verifyForge = async (input) => {
|
|
2820
3618
|
if (input.target.loader.type !== Loaders.FORGE) {
|
|
2821
3619
|
throw new MinecraftKitError(
|
|
2822
|
-
|
|
3620
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
2823
3621
|
`verify.forge requires a Forge target (got ${input.target.loader.type})`
|
|
2824
3622
|
);
|
|
2825
3623
|
}
|
|
@@ -2842,10 +3640,8 @@ async function verifyForge(input) {
|
|
|
2842
3640
|
})
|
|
2843
3641
|
);
|
|
2844
3642
|
if (!await fileExists(forgeVersionJsonPath)) return;
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
parsed = JSON.parse(await readText(forgeVersionJsonPath));
|
|
2848
|
-
} catch {
|
|
3643
|
+
const parsed = parseJsonOrUndefined(await readText(forgeVersionJsonPath));
|
|
3644
|
+
if (parsed === void 0) {
|
|
2849
3645
|
record({
|
|
2850
3646
|
path: forgeVersionJsonPath,
|
|
2851
3647
|
category: VerifyFileCategories.LOADER_LIBRARY,
|
|
@@ -2858,14 +3654,14 @@ async function verifyForge(input) {
|
|
|
2858
3654
|
directory: input.target.directory,
|
|
2859
3655
|
system: input.target.runtime.system,
|
|
2860
3656
|
versionId: input.target.minecraft.version,
|
|
2861
|
-
category:
|
|
3657
|
+
category: DownloadCategories.FORGE_LIBRARY
|
|
2862
3658
|
});
|
|
2863
3659
|
for (const action of forgeLibraries.downloads) {
|
|
2864
3660
|
record(
|
|
2865
3661
|
await verifyHashedFile({
|
|
2866
3662
|
path: action.target,
|
|
2867
|
-
expectedSha1: action.expectedSha1,
|
|
2868
|
-
expectedSize: action.expectedSize,
|
|
3663
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
3664
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
2869
3665
|
...action.url ? { url: action.url } : {},
|
|
2870
3666
|
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2871
3667
|
})
|
|
@@ -2873,10 +3669,10 @@ async function verifyForge(input) {
|
|
|
2873
3669
|
}
|
|
2874
3670
|
}
|
|
2875
3671
|
);
|
|
2876
|
-
}
|
|
3672
|
+
};
|
|
2877
3673
|
|
|
2878
3674
|
// src/verify/minecraft.ts
|
|
2879
|
-
async
|
|
3675
|
+
var verifyMinecraft = async (input) => {
|
|
2880
3676
|
return runVerification(
|
|
2881
3677
|
{
|
|
2882
3678
|
targetId: input.target.id,
|
|
@@ -2917,14 +3713,14 @@ async function verifyMinecraft(input) {
|
|
|
2917
3713
|
directory,
|
|
2918
3714
|
system: runtime.system,
|
|
2919
3715
|
versionId: minecraft.version,
|
|
2920
|
-
category:
|
|
3716
|
+
category: DownloadCategories.LIBRARY
|
|
2921
3717
|
});
|
|
2922
3718
|
for (const action of libraryPlan.downloads) {
|
|
2923
3719
|
record(
|
|
2924
3720
|
await verifyHashedFile({
|
|
2925
3721
|
path: action.target,
|
|
2926
|
-
expectedSha1: action.expectedSha1,
|
|
2927
|
-
expectedSize: action.expectedSize,
|
|
3722
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
3723
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
2928
3724
|
url: action.url,
|
|
2929
3725
|
category: VerifyFileCategories.LIBRARY
|
|
2930
3726
|
})
|
|
@@ -2971,8 +3767,8 @@ async function verifyMinecraft(input) {
|
|
|
2971
3767
|
}
|
|
2972
3768
|
}
|
|
2973
3769
|
);
|
|
2974
|
-
}
|
|
2975
|
-
async
|
|
3770
|
+
};
|
|
3771
|
+
var verifyRuntime = async (input) => {
|
|
2976
3772
|
return runVerification(
|
|
2977
3773
|
{
|
|
2978
3774
|
targetId: input.target.id,
|
|
@@ -3014,13 +3810,13 @@ async function verifyRuntime(input) {
|
|
|
3014
3810
|
}
|
|
3015
3811
|
}
|
|
3016
3812
|
);
|
|
3017
|
-
}
|
|
3813
|
+
};
|
|
3018
3814
|
|
|
3019
3815
|
// src/repair/helpers.ts
|
|
3020
|
-
|
|
3816
|
+
var asResultArray = (from) => {
|
|
3021
3817
|
return Array.isArray(from) ? from : [from];
|
|
3022
|
-
}
|
|
3023
|
-
|
|
3818
|
+
};
|
|
3819
|
+
var buildIssueIndex = (from) => {
|
|
3024
3820
|
const map = /* @__PURE__ */ new Map();
|
|
3025
3821
|
for (const v of asResultArray(from)) {
|
|
3026
3822
|
for (const issue of v.issues) {
|
|
@@ -3041,16 +3837,16 @@ function buildIssueIndex(from) {
|
|
|
3041
3837
|
},
|
|
3042
3838
|
categoriesAt: (path13) => map.get(path13) ?? /* @__PURE__ */ new Set()
|
|
3043
3839
|
};
|
|
3044
|
-
}
|
|
3045
|
-
|
|
3840
|
+
};
|
|
3841
|
+
var sumDownloadBytes = (actions) => {
|
|
3046
3842
|
return actions.reduce((sum, action) => {
|
|
3047
3843
|
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3048
3844
|
return sum + (action.expectedSize ?? 0);
|
|
3049
3845
|
}
|
|
3050
3846
|
return sum;
|
|
3051
3847
|
}, 0);
|
|
3052
|
-
}
|
|
3053
|
-
|
|
3848
|
+
};
|
|
3849
|
+
var buildRepairPlan = (target, actions) => {
|
|
3054
3850
|
return {
|
|
3055
3851
|
targetId: target.id,
|
|
3056
3852
|
directory: target.directory,
|
|
@@ -3059,8 +3855,8 @@ function buildRepairPlan(target, actions) {
|
|
|
3059
3855
|
totalActions: actions.length,
|
|
3060
3856
|
totalBytes: sumDownloadBytes(actions)
|
|
3061
3857
|
};
|
|
3062
|
-
}
|
|
3063
|
-
async
|
|
3858
|
+
};
|
|
3859
|
+
var planAspectRepair = async (input, aspectFilter, postprocess) => {
|
|
3064
3860
|
const installPlan = await planInstall({
|
|
3065
3861
|
target: input.target,
|
|
3066
3862
|
http: input.http,
|
|
@@ -3076,8 +3872,8 @@ async function planAspectRepair(input, aspectFilter, postprocess) {
|
|
|
3076
3872
|
});
|
|
3077
3873
|
postprocess?.({ actions, installPlan, issues });
|
|
3078
3874
|
return buildRepairPlan(input.target, actions);
|
|
3079
|
-
}
|
|
3080
|
-
|
|
3875
|
+
};
|
|
3876
|
+
var selectRepairActions = (input) => {
|
|
3081
3877
|
const matching = [];
|
|
3082
3878
|
for (const action of input.installPlan.actions) {
|
|
3083
3879
|
if (!input.aspectFilter(action)) continue;
|
|
@@ -3098,13 +3894,13 @@ function selectRepairActions(input) {
|
|
|
3098
3894
|
}
|
|
3099
3895
|
}
|
|
3100
3896
|
return matching;
|
|
3101
|
-
}
|
|
3897
|
+
};
|
|
3102
3898
|
|
|
3103
3899
|
// src/repair/fabric.ts
|
|
3104
|
-
async
|
|
3900
|
+
var planFabricRepair = async (input) => {
|
|
3105
3901
|
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
3106
3902
|
throw new MinecraftKitError(
|
|
3107
|
-
|
|
3903
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
3108
3904
|
`repair.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
3109
3905
|
);
|
|
3110
3906
|
}
|
|
@@ -3114,24 +3910,24 @@ async function planFabricRepair(input) {
|
|
|
3114
3910
|
);
|
|
3115
3911
|
return planAspectRepair(input, (action) => {
|
|
3116
3912
|
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3117
|
-
return action.category ===
|
|
3913
|
+
return action.category === DownloadCategories.FABRIC_LIBRARY;
|
|
3118
3914
|
}
|
|
3119
3915
|
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3120
3916
|
return action.path === fabricJsonPath;
|
|
3121
3917
|
}
|
|
3122
3918
|
return false;
|
|
3123
3919
|
});
|
|
3124
|
-
}
|
|
3920
|
+
};
|
|
3125
3921
|
|
|
3126
3922
|
// src/repair/forge.ts
|
|
3127
3923
|
var FORGE_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3128
|
-
|
|
3129
|
-
|
|
3924
|
+
DownloadCategories.FORGE_LIBRARY,
|
|
3925
|
+
DownloadCategories.FORGE_INSTALLER
|
|
3130
3926
|
]);
|
|
3131
|
-
async
|
|
3927
|
+
var planForgeRepair = async (input) => {
|
|
3132
3928
|
if (input.target.loader.type !== Loaders.FORGE) {
|
|
3133
3929
|
throw new MinecraftKitError(
|
|
3134
|
-
|
|
3930
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
3135
3931
|
`repair.forge requires a Forge target (got ${input.target.loader.type})`
|
|
3136
3932
|
);
|
|
3137
3933
|
}
|
|
@@ -3156,7 +3952,7 @@ async function planForgeRepair(input) {
|
|
|
3156
3952
|
actions.filter((a) => a.kind === InstallActionKinds.DOWNLOAD_FILE).map((a) => a.target)
|
|
3157
3953
|
);
|
|
3158
3954
|
for (const action of installPlan.actions) {
|
|
3159
|
-
if (action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category ===
|
|
3955
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category === DownloadCategories.FORGE_LIBRARY && !alreadyIncluded.has(action.target)) {
|
|
3160
3956
|
actions.push(action);
|
|
3161
3957
|
} else if (action.kind === InstallActionKinds.RUN_FORGE_PROCESSOR) {
|
|
3162
3958
|
actions.push(action);
|
|
@@ -3164,17 +3960,17 @@ async function planForgeRepair(input) {
|
|
|
3164
3960
|
}
|
|
3165
3961
|
}
|
|
3166
3962
|
);
|
|
3167
|
-
}
|
|
3963
|
+
};
|
|
3168
3964
|
|
|
3169
3965
|
// src/repair/minecraft.ts
|
|
3170
3966
|
var MINECRAFT_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3967
|
+
DownloadCategories.CLIENT_JAR,
|
|
3968
|
+
DownloadCategories.LIBRARY,
|
|
3969
|
+
DownloadCategories.ASSET_INDEX,
|
|
3970
|
+
DownloadCategories.ASSET,
|
|
3971
|
+
DownloadCategories.LOGGING_CONFIG
|
|
3176
3972
|
]);
|
|
3177
|
-
async
|
|
3973
|
+
var planMinecraftRepair = async (input) => {
|
|
3178
3974
|
const vanillaJsonPath = targetPaths.versionJson(
|
|
3179
3975
|
input.target.directory,
|
|
3180
3976
|
input.target.minecraft.version
|
|
@@ -3191,10 +3987,10 @@ async function planMinecraftRepair(input) {
|
|
|
3191
3987
|
}
|
|
3192
3988
|
return false;
|
|
3193
3989
|
});
|
|
3194
|
-
}
|
|
3990
|
+
};
|
|
3195
3991
|
|
|
3196
3992
|
// src/repair/runner.ts
|
|
3197
|
-
async
|
|
3993
|
+
var runRepair = async (input) => {
|
|
3198
3994
|
const report = await runInstall({
|
|
3199
3995
|
plan: {
|
|
3200
3996
|
...input.plan,
|
|
@@ -3213,18 +4009,18 @@ async function runRepair(input) {
|
|
|
3213
4009
|
actionsCompleted: report.actionsCompleted,
|
|
3214
4010
|
durationMs: report.durationMs
|
|
3215
4011
|
};
|
|
3216
|
-
}
|
|
4012
|
+
};
|
|
3217
4013
|
|
|
3218
4014
|
// src/repair/runtime.ts
|
|
3219
|
-
async
|
|
4015
|
+
var planRuntimeRepair = async (input) => {
|
|
3220
4016
|
return planAspectRepair(
|
|
3221
4017
|
input,
|
|
3222
|
-
(action) => action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category ===
|
|
4018
|
+
(action) => action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category === DownloadCategories.RUNTIME_FILE
|
|
3223
4019
|
);
|
|
3224
|
-
}
|
|
4020
|
+
};
|
|
3225
4021
|
|
|
3226
4022
|
// src/repair/all.ts
|
|
3227
|
-
async
|
|
4023
|
+
var repairAll = async (input) => {
|
|
3228
4024
|
const startedAt = Date.now();
|
|
3229
4025
|
const ctx = {
|
|
3230
4026
|
target: input.target,
|
|
@@ -3267,7 +4063,7 @@ async function repairAll(input) {
|
|
|
3267
4063
|
bytesDownloaded,
|
|
3268
4064
|
durationMs: Date.now() - startedAt
|
|
3269
4065
|
};
|
|
3270
|
-
}
|
|
4066
|
+
};
|
|
3271
4067
|
var PLANNERS = {
|
|
3272
4068
|
minecraft: planMinecraftRepair,
|
|
3273
4069
|
runtime: planRuntimeRepair,
|
|
@@ -3306,14 +4102,27 @@ var TargetsApi = class {
|
|
|
3306
4102
|
/** Build a {@link Target} from already-resolved components. */
|
|
3307
4103
|
create(input) {
|
|
3308
4104
|
if (!input.id) {
|
|
3309
|
-
throw new MinecraftKitError(
|
|
4105
|
+
throw new MinecraftKitError(
|
|
4106
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
4107
|
+
"Target id must be non-empty"
|
|
4108
|
+
);
|
|
3310
4109
|
}
|
|
3311
4110
|
if (!input.directory) {
|
|
3312
|
-
throw new MinecraftKitError(
|
|
4111
|
+
throw new MinecraftKitError(
|
|
4112
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
4113
|
+
"Target directory must be non-empty"
|
|
4114
|
+
);
|
|
4115
|
+
}
|
|
4116
|
+
if (!path__default.default.isAbsolute(input.directory)) {
|
|
4117
|
+
throw new MinecraftKitError(
|
|
4118
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
4119
|
+
`Target directory must be an absolute path, got: ${input.directory}`,
|
|
4120
|
+
{ context: { directory: input.directory } }
|
|
4121
|
+
);
|
|
3313
4122
|
}
|
|
3314
4123
|
if (input.loader.minecraftVersion !== input.minecraft.version) {
|
|
3315
4124
|
throw new MinecraftKitError(
|
|
3316
|
-
|
|
4125
|
+
MinecraftKitErrorCodes.INVALID_INPUT,
|
|
3317
4126
|
`Loader Minecraft version (${input.loader.minecraftVersion}) does not match resolved Minecraft (${input.minecraft.version})`,
|
|
3318
4127
|
{
|
|
3319
4128
|
context: {
|
|
@@ -3390,7 +4199,7 @@ var TargetsApi = class {
|
|
|
3390
4199
|
return results;
|
|
3391
4200
|
}
|
|
3392
4201
|
};
|
|
3393
|
-
async
|
|
4202
|
+
var discoverInstallation = async (id, directory) => {
|
|
3394
4203
|
const versionsDir = path__default.default.join(directory, VERSIONS_DIR);
|
|
3395
4204
|
const librariesDir = path__default.default.join(directory, LIBRARIES_DIR);
|
|
3396
4205
|
const assetsDir = path__default.default.join(directory, ASSETS_DIR);
|
|
@@ -3412,8 +4221,8 @@ async function discoverInstallation(id, directory) {
|
|
|
3412
4221
|
}
|
|
3413
4222
|
const runtime = await discoverRuntime(directory);
|
|
3414
4223
|
return { id, directory, minecraftVersions, loaders, ...runtime ? { runtime } : {} };
|
|
3415
|
-
}
|
|
3416
|
-
async
|
|
4224
|
+
};
|
|
4225
|
+
var discoverRuntime = async (directory) => {
|
|
3417
4226
|
const runtimeDir = path__default.default.join(directory, RUNTIMES_DIR);
|
|
3418
4227
|
if (!await dirExists(runtimeDir)) return void 0;
|
|
3419
4228
|
let components;
|
|
@@ -3424,14 +4233,14 @@ async function discoverRuntime(directory) {
|
|
|
3424
4233
|
}
|
|
3425
4234
|
for (const component of components) {
|
|
3426
4235
|
const root = path__default.default.join(runtimeDir, component);
|
|
3427
|
-
const javaPath =
|
|
4236
|
+
const javaPath = javaExecutablePath(root);
|
|
3428
4237
|
if (await fileExists(javaPath)) {
|
|
3429
4238
|
return { component, javaPath };
|
|
3430
4239
|
}
|
|
3431
4240
|
}
|
|
3432
4241
|
return void 0;
|
|
3433
|
-
}
|
|
3434
|
-
|
|
4242
|
+
};
|
|
4243
|
+
var inferLoaderFromVersionId = (versionId) => {
|
|
3435
4244
|
const fabricMatch = /^fabric-loader-([^-]+)-(.+)$/.exec(versionId);
|
|
3436
4245
|
if (fabricMatch?.[1] && fabricMatch[2]) {
|
|
3437
4246
|
return { type: Loaders.FABRIC, version: fabricMatch[1], minecraftVersion: fabricMatch[2] };
|
|
@@ -3441,18 +4250,25 @@ function inferLoaderFromVersionId(versionId) {
|
|
|
3441
4250
|
return { type: Loaders.FORGE, minecraftVersion: forgeMatch[1], version: forgeMatch[2] };
|
|
3442
4251
|
}
|
|
3443
4252
|
return null;
|
|
3444
|
-
}
|
|
4253
|
+
};
|
|
4254
|
+
var javaExecutablePath = (runtimeRoot) => {
|
|
4255
|
+
if (process.platform === "win32") return path__default.default.join(runtimeRoot, "bin", "javaw.exe");
|
|
4256
|
+
if (process.platform === "darwin") {
|
|
4257
|
+
return path__default.default.join(runtimeRoot, "jre.bundle", "Contents", "Home", "bin", "java");
|
|
4258
|
+
}
|
|
4259
|
+
return path__default.default.join(runtimeRoot, "bin", "java");
|
|
4260
|
+
};
|
|
3445
4261
|
|
|
3446
4262
|
// src/update/runner.ts
|
|
3447
|
-
async
|
|
4263
|
+
var planUpdate = async (input) => {
|
|
3448
4264
|
return planInstall({
|
|
3449
4265
|
target: input.target,
|
|
3450
4266
|
http: input.http,
|
|
3451
4267
|
cache: input.cache,
|
|
3452
4268
|
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3453
4269
|
});
|
|
3454
|
-
}
|
|
3455
|
-
async
|
|
4270
|
+
};
|
|
4271
|
+
var runUpdate = async (input) => {
|
|
3456
4272
|
const report = await runInstall({
|
|
3457
4273
|
plan: input.plan,
|
|
3458
4274
|
http: input.http,
|
|
@@ -3468,7 +4284,7 @@ async function runUpdate(input) {
|
|
|
3468
4284
|
actionsSkipped: report.actionsSkipped,
|
|
3469
4285
|
durationMs: report.durationMs
|
|
3470
4286
|
};
|
|
3471
|
-
}
|
|
4287
|
+
};
|
|
3472
4288
|
|
|
3473
4289
|
// src/versions/fabric.ts
|
|
3474
4290
|
var FabricVersionsApi = class {
|
|
@@ -3504,7 +4320,7 @@ var FabricVersionsApi = class {
|
|
|
3504
4320
|
});
|
|
3505
4321
|
if (loaders.length === 0) {
|
|
3506
4322
|
throw new MinecraftKitError(
|
|
3507
|
-
|
|
4323
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3508
4324
|
`No Fabric loader available for Minecraft ${input.minecraftVersion}`,
|
|
3509
4325
|
{ context: { version: input.minecraftVersion } }
|
|
3510
4326
|
);
|
|
@@ -3512,9 +4328,11 @@ var FabricVersionsApi = class {
|
|
|
3512
4328
|
const chosen = pickFabricLoader(loaders, input);
|
|
3513
4329
|
if (!chosen) {
|
|
3514
4330
|
throw new MinecraftKitError(
|
|
3515
|
-
|
|
4331
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3516
4332
|
`Fabric loader version not found: ${input.loaderVersion ?? "(none matched)"}`,
|
|
3517
|
-
{
|
|
4333
|
+
{
|
|
4334
|
+
context: input.loaderVersion !== void 0 ? { version: input.loaderVersion } : {}
|
|
4335
|
+
}
|
|
3518
4336
|
);
|
|
3519
4337
|
}
|
|
3520
4338
|
const profile = await fetchJson(this.ctx.http, this.ctx.cache, {
|
|
@@ -3530,7 +4348,7 @@ var FabricVersionsApi = class {
|
|
|
3530
4348
|
};
|
|
3531
4349
|
}
|
|
3532
4350
|
};
|
|
3533
|
-
|
|
4351
|
+
var pickFabricLoader = (loaders, input) => {
|
|
3534
4352
|
if (input.loaderVersion !== void 0) {
|
|
3535
4353
|
return loaders.find((l) => l.version === input.loaderVersion);
|
|
3536
4354
|
}
|
|
@@ -3540,17 +4358,17 @@ function pickFabricLoader(loaders, input) {
|
|
|
3540
4358
|
if (stable) return stable;
|
|
3541
4359
|
}
|
|
3542
4360
|
return loaders[0];
|
|
3543
|
-
}
|
|
4361
|
+
};
|
|
3544
4362
|
|
|
3545
4363
|
// src/core/xml.ts
|
|
3546
|
-
|
|
4364
|
+
var parseMavenMetadataVersions = (xml) => {
|
|
3547
4365
|
const versions = [];
|
|
3548
4366
|
const regex = /<version>\s*([^<]+?)\s*<\/version>/g;
|
|
3549
4367
|
for (const match of xml.matchAll(regex)) {
|
|
3550
4368
|
if (match[1]) versions.push(match[1]);
|
|
3551
4369
|
}
|
|
3552
4370
|
return versions;
|
|
3553
|
-
}
|
|
4371
|
+
};
|
|
3554
4372
|
|
|
3555
4373
|
// src/versions/forge.ts
|
|
3556
4374
|
var ForgeVersionsApi = class {
|
|
@@ -3583,7 +4401,7 @@ var ForgeVersionsApi = class {
|
|
|
3583
4401
|
});
|
|
3584
4402
|
if (builds.length === 0) {
|
|
3585
4403
|
throw new MinecraftKitError(
|
|
3586
|
-
|
|
4404
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3587
4405
|
`No Forge build available for Minecraft ${input.minecraftVersion}`,
|
|
3588
4406
|
{ context: { version: input.minecraftVersion } }
|
|
3589
4407
|
);
|
|
@@ -3591,9 +4409,11 @@ var ForgeVersionsApi = class {
|
|
|
3591
4409
|
const chosen = pickForge(builds, input);
|
|
3592
4410
|
if (!chosen) {
|
|
3593
4411
|
throw new MinecraftKitError(
|
|
3594
|
-
|
|
4412
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3595
4413
|
`Forge build not found for ${input.minecraftVersion}: ${input.forgeVersion ?? "(none matched)"}`,
|
|
3596
|
-
{
|
|
4414
|
+
{
|
|
4415
|
+
context: input.forgeVersion !== void 0 ? { version: input.forgeVersion } : {}
|
|
4416
|
+
}
|
|
3597
4417
|
);
|
|
3598
4418
|
}
|
|
3599
4419
|
return {
|
|
@@ -3605,7 +4425,7 @@ var ForgeVersionsApi = class {
|
|
|
3605
4425
|
};
|
|
3606
4426
|
}
|
|
3607
4427
|
};
|
|
3608
|
-
|
|
4428
|
+
var buildSummary = (fullVersion, promotions) => {
|
|
3609
4429
|
const dashIndex = fullVersion.indexOf("-");
|
|
3610
4430
|
if (dashIndex <= 0 || dashIndex === fullVersion.length - 1) return null;
|
|
3611
4431
|
const minecraftVersion = fullVersion.slice(0, dashIndex);
|
|
@@ -3620,8 +4440,8 @@ function buildSummary(fullVersion, promotions) {
|
|
|
3620
4440
|
isRecommended: recommended === forgeVersion,
|
|
3621
4441
|
isLatest: latest === forgeVersion
|
|
3622
4442
|
};
|
|
3623
|
-
}
|
|
3624
|
-
|
|
4443
|
+
};
|
|
4444
|
+
var pickForge = (builds, input) => {
|
|
3625
4445
|
if (input.forgeVersion !== void 0) {
|
|
3626
4446
|
return builds.find(
|
|
3627
4447
|
(b) => b.forgeVersion === input.forgeVersion || b.fullVersion === input.forgeVersion
|
|
@@ -3635,7 +4455,30 @@ function pickForge(builds, input) {
|
|
|
3635
4455
|
const latest = builds.find((b) => b.isLatest);
|
|
3636
4456
|
if (latest) return latest;
|
|
3637
4457
|
return builds[builds.length - 1];
|
|
3638
|
-
}
|
|
4458
|
+
};
|
|
4459
|
+
|
|
4460
|
+
// src/core/guards.ts
|
|
4461
|
+
var isPlainObject = (value) => {
|
|
4462
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4463
|
+
};
|
|
4464
|
+
var isNonEmptyString = (value) => {
|
|
4465
|
+
return typeof value === "string" && value.length > 0;
|
|
4466
|
+
};
|
|
4467
|
+
var isArtifactDownload = (value) => {
|
|
4468
|
+
if (!isPlainObject(value)) return false;
|
|
4469
|
+
return typeof value.sha1 === "string" && typeof value.size === "number" && isNonEmptyString(value.url);
|
|
4470
|
+
};
|
|
4471
|
+
var isMinecraftVersionManifestShape = (value) => {
|
|
4472
|
+
if (!isPlainObject(value)) return false;
|
|
4473
|
+
if (!isNonEmptyString(value.id)) return false;
|
|
4474
|
+
if (!isNonEmptyString(value.mainClass)) return false;
|
|
4475
|
+
if (!isPlainObject(value.assetIndex)) return false;
|
|
4476
|
+
if (!isNonEmptyString(value.assetIndex.id) || typeof value.assetIndex.sha1 !== "string" || typeof value.assetIndex.size !== "number" || !isNonEmptyString(value.assetIndex.url)) {
|
|
4477
|
+
return false;
|
|
4478
|
+
}
|
|
4479
|
+
if (!isPlainObject(value.downloads)) return false;
|
|
4480
|
+
return isArtifactDownload(value.downloads.client);
|
|
4481
|
+
};
|
|
3639
4482
|
|
|
3640
4483
|
// src/versions/minecraft.ts
|
|
3641
4484
|
var MinecraftVersionsApi = class {
|
|
@@ -3656,7 +4499,7 @@ var MinecraftVersionsApi = class {
|
|
|
3656
4499
|
const summary = root.versions.find((v) => v.id === targetId);
|
|
3657
4500
|
if (!summary) {
|
|
3658
4501
|
throw new MinecraftKitError(
|
|
3659
|
-
|
|
4502
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3660
4503
|
`Latest version ${targetId} not found in manifest`
|
|
3661
4504
|
);
|
|
3662
4505
|
}
|
|
@@ -3668,7 +4511,7 @@ var MinecraftVersionsApi = class {
|
|
|
3668
4511
|
const summary = root.versions.find((v) => v.id === input.version);
|
|
3669
4512
|
if (!summary) {
|
|
3670
4513
|
throw new MinecraftKitError(
|
|
3671
|
-
|
|
4514
|
+
MinecraftKitErrorCodes.MANIFEST_NOT_FOUND,
|
|
3672
4515
|
`Minecraft version not found: ${input.version}`,
|
|
3673
4516
|
{ context: { version: input.version } }
|
|
3674
4517
|
);
|
|
@@ -3678,18 +4521,19 @@ var MinecraftVersionsApi = class {
|
|
|
3678
4521
|
/** Fetch and parse the per-version manifest in addition to the summary. */
|
|
3679
4522
|
async resolve(input) {
|
|
3680
4523
|
const summary = await this.get(input);
|
|
3681
|
-
const
|
|
4524
|
+
const raw = await fetchJson(this.ctx.http, this.ctx.cache, {
|
|
3682
4525
|
url: summary.url,
|
|
3683
4526
|
cacheKey: `minecraft-manifest:${summary.id}:${summary.sha1}`,
|
|
3684
4527
|
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3685
4528
|
});
|
|
3686
|
-
if (!
|
|
4529
|
+
if (!isMinecraftVersionManifestShape(raw)) {
|
|
3687
4530
|
throw new MinecraftKitError(
|
|
3688
|
-
|
|
3689
|
-
`Per-version manifest
|
|
4531
|
+
MinecraftKitErrorCodes.MANIFEST_INVALID,
|
|
4532
|
+
`Per-version manifest does not match the expected shape: ${summary.id}`,
|
|
3690
4533
|
{ context: { version: summary.id, url: summary.url } }
|
|
3691
4534
|
);
|
|
3692
4535
|
}
|
|
4536
|
+
const manifest = raw;
|
|
3693
4537
|
return {
|
|
3694
4538
|
version: summary.id,
|
|
3695
4539
|
channel: summary.type,
|
|
@@ -3742,7 +4586,7 @@ var RuntimeVersionsApi = class {
|
|
|
3742
4586
|
const platform = index[platformKey];
|
|
3743
4587
|
if (!platform) {
|
|
3744
4588
|
throw new MinecraftKitError(
|
|
3745
|
-
|
|
4589
|
+
MinecraftKitErrorCodes.RUNTIME_UNSUPPORTED_PLATFORM,
|
|
3746
4590
|
`No runtimes published for platform: ${platformKey}`,
|
|
3747
4591
|
{ context: { platform: platformKey } }
|
|
3748
4592
|
);
|
|
@@ -3759,7 +4603,7 @@ var RuntimeVersionsApi = class {
|
|
|
3759
4603
|
}
|
|
3760
4604
|
}
|
|
3761
4605
|
throw new MinecraftKitError(
|
|
3762
|
-
|
|
4606
|
+
MinecraftKitErrorCodes.RUNTIME_NOT_FOUND,
|
|
3763
4607
|
`Runtime component ${component} not available on ${platformKey}`,
|
|
3764
4608
|
{ context: { platform: platformKey, version: component } }
|
|
3765
4609
|
);
|
|
@@ -3767,7 +4611,7 @@ var RuntimeVersionsApi = class {
|
|
|
3767
4611
|
const entry = candidates[0];
|
|
3768
4612
|
if (!entry) {
|
|
3769
4613
|
throw new MinecraftKitError(
|
|
3770
|
-
|
|
4614
|
+
MinecraftKitErrorCodes.RUNTIME_NOT_FOUND,
|
|
3771
4615
|
`Runtime component ${component} list is empty for ${platformKey}`,
|
|
3772
4616
|
{ context: { platform: platformKey, version: component } }
|
|
3773
4617
|
);
|
|
@@ -3782,11 +4626,11 @@ var RuntimeVersionsApi = class {
|
|
|
3782
4626
|
});
|
|
3783
4627
|
}
|
|
3784
4628
|
};
|
|
3785
|
-
|
|
4629
|
+
var pickPlatformKey = (system) => {
|
|
3786
4630
|
const archMap = RUNTIME_PLATFORM_KEYS[system.os];
|
|
3787
4631
|
return archMap[system.arch];
|
|
3788
|
-
}
|
|
3789
|
-
|
|
4632
|
+
};
|
|
4633
|
+
var pickLatestAcrossComponents = (entries) => {
|
|
3790
4634
|
let bestComponent = null;
|
|
3791
4635
|
let bestEntry = null;
|
|
3792
4636
|
for (const [component, list] of entries) {
|
|
@@ -3799,8 +4643,8 @@ function pickLatestAcrossComponents(entries) {
|
|
|
3799
4643
|
}
|
|
3800
4644
|
if (!bestComponent || !bestEntry) return null;
|
|
3801
4645
|
return { component: bestComponent, entry: bestEntry };
|
|
3802
|
-
}
|
|
3803
|
-
|
|
4646
|
+
};
|
|
4647
|
+
var toResolved = (component, platformKey, entry, system) => {
|
|
3804
4648
|
const majorVersion = parseMajorVersion(entry.version.name);
|
|
3805
4649
|
return {
|
|
3806
4650
|
component,
|
|
@@ -3811,13 +4655,13 @@ function toResolved(component, platformKey, entry, system) {
|
|
|
3811
4655
|
manifestUrl: entry.manifest.url,
|
|
3812
4656
|
manifestSha1: entry.manifest.sha1
|
|
3813
4657
|
};
|
|
3814
|
-
}
|
|
3815
|
-
|
|
4658
|
+
};
|
|
4659
|
+
var parseMajorVersion = (versionName) => {
|
|
3816
4660
|
const match = /^(\d+)/.exec(versionName);
|
|
3817
4661
|
if (!match || !match[1]) return void 0;
|
|
3818
4662
|
const parsed = Number.parseInt(match[1], 10);
|
|
3819
4663
|
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3820
|
-
}
|
|
4664
|
+
};
|
|
3821
4665
|
|
|
3822
4666
|
// src/kit.ts
|
|
3823
4667
|
var MinecraftKit = class {
|
|
@@ -3828,6 +4672,12 @@ var MinecraftKit = class {
|
|
|
3828
4672
|
verify;
|
|
3829
4673
|
repair;
|
|
3830
4674
|
launch;
|
|
4675
|
+
/**
|
|
4676
|
+
* Microsoft / Mojang authentication. Implements the device-code flow against Microsoft
|
|
4677
|
+
* Entra, exchanges the resulting tokens for an XSTS + Minecraft session, and returns
|
|
4678
|
+
* everything needed to compose an `OnlineAuth` for `launch.compose`.
|
|
4679
|
+
*/
|
|
4680
|
+
auth;
|
|
3831
4681
|
/** Cache surface useful for advanced consumers (e.g. clearing between operations). */
|
|
3832
4682
|
cache;
|
|
3833
4683
|
constructor(options = {}) {
|
|
@@ -3843,6 +4693,7 @@ var MinecraftKit = class {
|
|
|
3843
4693
|
const runtime = new RuntimeVersionsApi(ctx);
|
|
3844
4694
|
this.versions = { minecraft, fabric, forge, runtime };
|
|
3845
4695
|
this.targets = new TargetsApi({ minecraft, fabric, forge, runtime, system });
|
|
4696
|
+
this.auth = new MojangAuthApi(http);
|
|
3846
4697
|
this.cache = cache;
|
|
3847
4698
|
const carry = (opts) => ({
|
|
3848
4699
|
...opts?.signal !== void 0 ? { signal: opts.signal } : {},
|
|
@@ -3923,26 +4774,9 @@ var MinecraftKit = class {
|
|
|
3923
4774
|
}
|
|
3924
4775
|
};
|
|
3925
4776
|
|
|
3926
|
-
// src/core/
|
|
3927
|
-
var
|
|
3928
|
-
|
|
3929
|
-
#waiters = [];
|
|
3930
|
-
get paused() {
|
|
3931
|
-
return this.#paused;
|
|
3932
|
-
}
|
|
3933
|
-
pause() {
|
|
3934
|
-
this.#paused = true;
|
|
3935
|
-
}
|
|
3936
|
-
resume() {
|
|
3937
|
-
this.#paused = false;
|
|
3938
|
-
const list = this.#waiters;
|
|
3939
|
-
this.#waiters = [];
|
|
3940
|
-
for (const resolve of list) resolve();
|
|
3941
|
-
}
|
|
3942
|
-
waitWhilePaused() {
|
|
3943
|
-
if (!this.#paused) return Promise.resolve();
|
|
3944
|
-
return new Promise((resolve) => this.#waiters.push(resolve));
|
|
3945
|
-
}
|
|
4777
|
+
// src/core/assert-never.ts
|
|
4778
|
+
var assertNever = (value) => {
|
|
4779
|
+
throw new Error(`Unhandled variant: ${JSON.stringify(value)}`);
|
|
3946
4780
|
};
|
|
3947
4781
|
|
|
3948
4782
|
// src/install/progress-tracker.ts
|
|
@@ -3954,15 +4788,15 @@ var InstallStages = {
|
|
|
3954
4788
|
FINALIZE: "finalize"
|
|
3955
4789
|
};
|
|
3956
4790
|
var STAGE_FOR_CATEGORY = {
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
4791
|
+
[DownloadCategories.RUNTIME_FILE]: InstallStages.RUNTIME,
|
|
4792
|
+
[DownloadCategories.CLIENT_JAR]: InstallStages.MINECRAFT,
|
|
4793
|
+
[DownloadCategories.LIBRARY]: InstallStages.MINECRAFT,
|
|
4794
|
+
[DownloadCategories.ASSET_INDEX]: InstallStages.MINECRAFT,
|
|
4795
|
+
[DownloadCategories.ASSET]: InstallStages.MINECRAFT,
|
|
4796
|
+
[DownloadCategories.LOGGING_CONFIG]: InstallStages.MINECRAFT,
|
|
4797
|
+
[DownloadCategories.FABRIC_LIBRARY]: InstallStages.LOADER,
|
|
4798
|
+
[DownloadCategories.FORGE_LIBRARY]: InstallStages.LOADER,
|
|
4799
|
+
[DownloadCategories.FORGE_INSTALLER]: InstallStages.LOADER
|
|
3966
4800
|
};
|
|
3967
4801
|
var STAGE_FOR_PHASE = {
|
|
3968
4802
|
[InstallPhases.PLANNING]: InstallStages.PREPARE,
|
|
@@ -3986,7 +4820,7 @@ var ALL_STAGES = [
|
|
|
3986
4820
|
InstallStages.LOADER,
|
|
3987
4821
|
InstallStages.FINALIZE
|
|
3988
4822
|
];
|
|
3989
|
-
|
|
4823
|
+
var createInstallProgressTracker = (plan, options = {}) => {
|
|
3990
4824
|
const throttleMs = options.throttleMs ?? 100;
|
|
3991
4825
|
const stageOfTarget = /* @__PURE__ */ new Map();
|
|
3992
4826
|
const expectedSizeOf = /* @__PURE__ */ new Map();
|
|
@@ -4070,7 +4904,7 @@ function createInstallProgressTracker(plan, options = {}) {
|
|
|
4070
4904
|
};
|
|
4071
4905
|
const onEvent = (event) => {
|
|
4072
4906
|
switch (event.type) {
|
|
4073
|
-
case
|
|
4907
|
+
case EventTypes.INSTALL_PHASE_CHANGED: {
|
|
4074
4908
|
const next = STAGE_FOR_PHASE[event.phase];
|
|
4075
4909
|
if (next && next !== currentStage) {
|
|
4076
4910
|
currentStage = next;
|
|
@@ -4079,14 +4913,14 @@ function createInstallProgressTracker(plan, options = {}) {
|
|
|
4079
4913
|
}
|
|
4080
4914
|
return;
|
|
4081
4915
|
}
|
|
4082
|
-
case
|
|
4916
|
+
case EventTypes.DOWNLOAD_STARTED: {
|
|
4083
4917
|
const stage = stageOfTarget.get(event.file.target) ?? currentStage;
|
|
4084
4918
|
inFlightByTarget.set(event.file.target, { stage, bytes: 0 });
|
|
4085
4919
|
currentFile = event.file.target;
|
|
4086
4920
|
schedulePush();
|
|
4087
4921
|
return;
|
|
4088
4922
|
}
|
|
4089
|
-
case
|
|
4923
|
+
case EventTypes.DOWNLOAD_PROGRESS: {
|
|
4090
4924
|
const entry = inFlightByTarget.get(event.file.target);
|
|
4091
4925
|
if (entry) {
|
|
4092
4926
|
const delta = event.bytesDownloaded - entry.bytes;
|
|
@@ -4100,7 +4934,7 @@ function createInstallProgressTracker(plan, options = {}) {
|
|
|
4100
4934
|
schedulePush();
|
|
4101
4935
|
return;
|
|
4102
4936
|
}
|
|
4103
|
-
case
|
|
4937
|
+
case EventTypes.DOWNLOAD_SKIPPED: {
|
|
4104
4938
|
const stage = stageOfTarget.get(event.file.target);
|
|
4105
4939
|
if (stage) {
|
|
4106
4940
|
const size = expectedSizeOf.get(event.file.target) ?? 0;
|
|
@@ -4110,7 +4944,7 @@ function createInstallProgressTracker(plan, options = {}) {
|
|
|
4110
4944
|
}
|
|
4111
4945
|
return;
|
|
4112
4946
|
}
|
|
4113
|
-
case
|
|
4947
|
+
case EventTypes.DOWNLOAD_COMPLETED: {
|
|
4114
4948
|
const entry = inFlightByTarget.get(event.file.target);
|
|
4115
4949
|
if (entry) {
|
|
4116
4950
|
const finalBytes = event.bytes ?? entry.bytes;
|
|
@@ -4157,36 +4991,60 @@ function createInstallProgressTracker(plan, options = {}) {
|
|
|
4157
4991
|
for (const listener of listeners) listener(snap);
|
|
4158
4992
|
}
|
|
4159
4993
|
};
|
|
4160
|
-
}
|
|
4161
|
-
|
|
4994
|
+
};
|
|
4995
|
+
var clamp = (value) => {
|
|
4162
4996
|
if (value <= 0) return 0;
|
|
4163
4997
|
if (value >= 100) return 100;
|
|
4164
4998
|
return value;
|
|
4165
|
-
}
|
|
4999
|
+
};
|
|
4166
5000
|
|
|
4167
|
-
// src/
|
|
4168
|
-
var
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
5001
|
+
// src/core/pause-controller.ts
|
|
5002
|
+
var PauseController = class {
|
|
5003
|
+
#paused = false;
|
|
5004
|
+
#waiters = [];
|
|
5005
|
+
get paused() {
|
|
5006
|
+
return this.#paused;
|
|
5007
|
+
}
|
|
5008
|
+
pause() {
|
|
5009
|
+
this.#paused = true;
|
|
5010
|
+
}
|
|
5011
|
+
resume() {
|
|
5012
|
+
this.#paused = false;
|
|
5013
|
+
const list = this.#waiters;
|
|
5014
|
+
this.#waiters = [];
|
|
5015
|
+
for (const resolve of list) resolve();
|
|
5016
|
+
}
|
|
5017
|
+
waitWhilePaused() {
|
|
5018
|
+
if (!this.#paused) return Promise.resolve();
|
|
5019
|
+
return new Promise((resolve) => this.#waiters.push(resolve));
|
|
5020
|
+
}
|
|
5021
|
+
};
|
|
5022
|
+
|
|
5023
|
+
// src/constants/placeholders.ts
|
|
5024
|
+
var LAUNCH_PLACEHOLDERS = {
|
|
5025
|
+
"${auth_player_name}": "Player display name.",
|
|
5026
|
+
"${version_name}": "Resolved Minecraft version id.",
|
|
5027
|
+
"${game_directory}": "Per-target directory.",
|
|
5028
|
+
"${assets_root}": "Assets root (`<directory>/assets`).",
|
|
5029
|
+
"${assets_index_name}": "Asset index id from the manifest.",
|
|
5030
|
+
"${auth_uuid}": "Player UUID (no dashes).",
|
|
5031
|
+
"${auth_access_token}": "Yggdrasil/MSA access token.",
|
|
5032
|
+
"${auth_session}": "Legacy session token (`token:<token>:<uuid>`).",
|
|
5033
|
+
"${clientid}": "MSA client id.",
|
|
5034
|
+
"${auth_xuid}": "Xbox user id.",
|
|
5035
|
+
"${user_type}": "`msa` | `mojang` | `legacy`.",
|
|
5036
|
+
"${user_properties}": "User properties JSON (often `{}`).",
|
|
5037
|
+
"${version_type}": "Channel string, e.g. `release`.",
|
|
5038
|
+
"${game_assets}": "Legacy virtual assets directory.",
|
|
5039
|
+
"${resolution_width}": "Window width (feature-gated).",
|
|
5040
|
+
"${resolution_height}": "Window height (feature-gated).",
|
|
5041
|
+
"${natives_directory}": "Extracted natives directory.",
|
|
5042
|
+
"${classpath}": "Joined classpath of libraries + version jar.",
|
|
5043
|
+
"${classpath_separator}": "OS-specific classpath separator (`:` / `;`).",
|
|
5044
|
+
"${library_directory}": "Per-target libraries directory.",
|
|
5045
|
+
"${launcher_name}": "Launcher brand string.",
|
|
5046
|
+
"${launcher_version}": "Launcher version string.",
|
|
5047
|
+
"${path}": "Path to the log4j config file (logging.client.argument only)."
|
|
4190
5048
|
};
|
|
4191
5049
|
|
|
4192
5050
|
// src/types/minecraft.ts
|
|
@@ -4221,33 +5079,6 @@ var Architectures = {
|
|
|
4221
5079
|
ARM64: "arm64"
|
|
4222
5080
|
};
|
|
4223
5081
|
|
|
4224
|
-
// src/constants/placeholders.ts
|
|
4225
|
-
var LAUNCH_PLACEHOLDERS = {
|
|
4226
|
-
"${auth_player_name}": "Player display name.",
|
|
4227
|
-
"${version_name}": "Resolved Minecraft version id.",
|
|
4228
|
-
"${game_directory}": "Per-target directory.",
|
|
4229
|
-
"${assets_root}": "Assets root (`<directory>/assets`).",
|
|
4230
|
-
"${assets_index_name}": "Asset index id from the manifest.",
|
|
4231
|
-
"${auth_uuid}": "Player UUID (no dashes).",
|
|
4232
|
-
"${auth_access_token}": "Yggdrasil/MSA access token.",
|
|
4233
|
-
"${auth_session}": "Legacy session token (`token:<token>:<uuid>`).",
|
|
4234
|
-
"${clientid}": "MSA client id.",
|
|
4235
|
-
"${auth_xuid}": "Xbox user id.",
|
|
4236
|
-
"${user_type}": "`msa` | `mojang` | `legacy`.",
|
|
4237
|
-
"${user_properties}": "User properties JSON (often `{}`).",
|
|
4238
|
-
"${version_type}": "Channel string, e.g. `release`.",
|
|
4239
|
-
"${game_assets}": "Legacy virtual assets directory.",
|
|
4240
|
-
"${resolution_width}": "Window width (feature-gated).",
|
|
4241
|
-
"${resolution_height}": "Window height (feature-gated).",
|
|
4242
|
-
"${natives_directory}": "Extracted natives directory.",
|
|
4243
|
-
"${classpath}": "Joined classpath of libraries + version jar.",
|
|
4244
|
-
"${classpath_separator}": "OS-specific classpath separator (`:` / `;`).",
|
|
4245
|
-
"${library_directory}": "Per-target libraries directory.",
|
|
4246
|
-
"${launcher_name}": "Launcher brand string.",
|
|
4247
|
-
"${launcher_version}": "Launcher version string.",
|
|
4248
|
-
"${path}": "Path to the log4j config file (logging.client.argument only)."
|
|
4249
|
-
};
|
|
4250
|
-
|
|
4251
5082
|
exports.ASSETS_DIR = ASSETS_DIR;
|
|
4252
5083
|
exports.ASSETS_INDEXES_DIR = ASSETS_INDEXES_DIR;
|
|
4253
5084
|
exports.ASSETS_LEGACY_DIR = ASSETS_LEGACY_DIR;
|
|
@@ -4261,6 +5092,7 @@ exports.AuthModes = AuthModes;
|
|
|
4261
5092
|
exports.BASE_JVM_ARGS = BASE_JVM_ARGS;
|
|
4262
5093
|
exports.CACHE_MAX_ENTRIES = CACHE_MAX_ENTRIES;
|
|
4263
5094
|
exports.CACHE_TTL_MS = CACHE_TTL_MS;
|
|
5095
|
+
exports.CLIENT_ID_ENV_VAR = CLIENT_ID_ENV_VAR;
|
|
4264
5096
|
exports.ChildProcessSpawner = ChildProcessSpawner;
|
|
4265
5097
|
exports.DEFAULT_KILL_GRACE_MS = DEFAULT_KILL_GRACE_MS;
|
|
4266
5098
|
exports.DEFAULT_LAUNCHER_NAME = DEFAULT_LAUNCHER_NAME;
|
|
@@ -4269,6 +5101,7 @@ exports.DEFAULT_LIBRARY_REPOSITORY = DEFAULT_LIBRARY_REPOSITORY;
|
|
|
4269
5101
|
exports.DEFAULT_MAX_MB = DEFAULT_MAX_MB;
|
|
4270
5102
|
exports.DEFAULT_MIN_MB = DEFAULT_MIN_MB;
|
|
4271
5103
|
exports.DOWNLOAD_CONCURRENCY = DOWNLOAD_CONCURRENCY;
|
|
5104
|
+
exports.DownloadCategories = DownloadCategories;
|
|
4272
5105
|
exports.EXTRACTION_MAX_COMPRESSION_RATIO = EXTRACTION_MAX_COMPRESSION_RATIO;
|
|
4273
5106
|
exports.EXTRACTION_MAX_ENTRY_COUNT = EXTRACTION_MAX_ENTRY_COUNT;
|
|
4274
5107
|
exports.EXTRACTION_MAX_FILE_SIZE = EXTRACTION_MAX_FILE_SIZE;
|
|
@@ -4301,7 +5134,9 @@ exports.MAX_PROCESSOR_STDERR_LINES = MAX_PROCESSOR_STDERR_LINES;
|
|
|
4301
5134
|
exports.MinecraftChannels = MinecraftChannels;
|
|
4302
5135
|
exports.MinecraftKit = MinecraftKit;
|
|
4303
5136
|
exports.MinecraftKitError = MinecraftKitError;
|
|
5137
|
+
exports.MinecraftKitErrorCodes = MinecraftKitErrorCodes;
|
|
4304
5138
|
exports.MinecraftVersionsApi = MinecraftVersionsApi;
|
|
5139
|
+
exports.MojangAuthApi = MojangAuthApi;
|
|
4305
5140
|
exports.NATIVES_DIR_NAME = NATIVES_DIR_NAME;
|
|
4306
5141
|
exports.NODE_ARCH_TO_MOJANG_ARCH = NODE_ARCH_TO_MOJANG_ARCH;
|
|
4307
5142
|
exports.NODE_PLATFORM_TO_MOJANG_OS = NODE_PLATFORM_TO_MOJANG_OS;
|
|
@@ -4322,6 +5157,7 @@ exports.VerificationKinds = VerificationKinds;
|
|
|
4322
5157
|
exports.VerifyFileCategories = VerifyFileCategories;
|
|
4323
5158
|
exports.VerifyFileStatuses = VerifyFileStatuses;
|
|
4324
5159
|
exports.VersionPreference = VersionPreference;
|
|
5160
|
+
exports.assertNever = assertNever;
|
|
4325
5161
|
exports.consoleLogger = consoleLogger;
|
|
4326
5162
|
exports.createInstallProgressTracker = createInstallProgressTracker;
|
|
4327
5163
|
exports.createMemoryCache = createMemoryCache;
|
|
@@ -4340,9 +5176,11 @@ exports.planStandaloneRuntimeInstall = planStandaloneRuntimeInstall;
|
|
|
4340
5176
|
exports.repairAll = repairAll;
|
|
4341
5177
|
exports.resolveLaunchVersion = resolveLaunchVersion;
|
|
4342
5178
|
exports.runRepair = runRepair;
|
|
5179
|
+
exports.scopedLogger = scopedLogger;
|
|
4343
5180
|
exports.silentLogger = silentLogger;
|
|
4344
5181
|
exports.stripUuidDashes = stripUuidDashes;
|
|
4345
5182
|
exports.targetPaths = targetPaths;
|
|
5183
|
+
exports.toOnlineAuth = toOnlineAuth;
|
|
4346
5184
|
exports.verifyFabric = verifyFabric;
|
|
4347
5185
|
exports.verifyForge = verifyForge;
|
|
4348
5186
|
exports.verifyMinecraft = verifyMinecraft;
|