@loontail/minecraft-kit 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +1018 -715
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +1017 -713
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +1210 -663
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -220
- package/dist/index.d.ts +341 -220
- package/dist/index.mjs +1202 -662
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import yauzl from 'yauzl';
|
|
|
8
8
|
import crypto2 from 'crypto';
|
|
9
9
|
import fs from 'fs/promises';
|
|
10
10
|
import { Readable } from 'stream';
|
|
11
|
-
import
|
|
11
|
+
import { AsyncResource } from 'async_hooks';
|
|
12
12
|
import { Buffer as Buffer$1 } from 'buffer';
|
|
13
13
|
import { spawn } from 'child_process';
|
|
14
14
|
|
|
@@ -1113,6 +1113,12 @@ async function downloadFile(http, input) {
|
|
|
1113
1113
|
const sourceIterable = response.stream();
|
|
1114
1114
|
const counting = (async function* () {
|
|
1115
1115
|
for await (const chunk of sourceIterable) {
|
|
1116
|
+
if (input.pauseController?.paused) {
|
|
1117
|
+
await input.pauseController.waitWhilePaused();
|
|
1118
|
+
}
|
|
1119
|
+
if (input.signal?.aborted) {
|
|
1120
|
+
throw new MinecraftKitError("LAUNCH_ABORTED", "Download aborted by signal");
|
|
1121
|
+
}
|
|
1116
1122
|
bytesDownloaded += chunk.byteLength;
|
|
1117
1123
|
hash.update(chunk);
|
|
1118
1124
|
input.onEvent?.({
|
|
@@ -1441,7 +1447,8 @@ function substituteToken(raw, tokens) {
|
|
|
1441
1447
|
});
|
|
1442
1448
|
}
|
|
1443
1449
|
function stripLiteralPrefix(value) {
|
|
1444
|
-
|
|
1450
|
+
const stripped = value.startsWith("'") ? value.slice(1) : value;
|
|
1451
|
+
return stripped.endsWith("'") ? stripped.slice(0, -1) : stripped;
|
|
1445
1452
|
}
|
|
1446
1453
|
async function planRuntimeDownloads(input) {
|
|
1447
1454
|
const manifest = await fetchJson(input.http, input.cache, {
|
|
@@ -1564,6 +1571,123 @@ async function planInstall(input) {
|
|
|
1564
1571
|
totalBytes
|
|
1565
1572
|
};
|
|
1566
1573
|
}
|
|
1574
|
+
|
|
1575
|
+
// node_modules/yocto-queue/index.js
|
|
1576
|
+
var Node = class {
|
|
1577
|
+
value;
|
|
1578
|
+
next;
|
|
1579
|
+
constructor(value) {
|
|
1580
|
+
this.value = value;
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
var Queue = class {
|
|
1584
|
+
#head;
|
|
1585
|
+
#tail;
|
|
1586
|
+
#size;
|
|
1587
|
+
constructor() {
|
|
1588
|
+
this.clear();
|
|
1589
|
+
}
|
|
1590
|
+
enqueue(value) {
|
|
1591
|
+
const node = new Node(value);
|
|
1592
|
+
if (this.#head) {
|
|
1593
|
+
this.#tail.next = node;
|
|
1594
|
+
this.#tail = node;
|
|
1595
|
+
} else {
|
|
1596
|
+
this.#head = node;
|
|
1597
|
+
this.#tail = node;
|
|
1598
|
+
}
|
|
1599
|
+
this.#size++;
|
|
1600
|
+
}
|
|
1601
|
+
dequeue() {
|
|
1602
|
+
const current = this.#head;
|
|
1603
|
+
if (!current) {
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
this.#head = this.#head.next;
|
|
1607
|
+
this.#size--;
|
|
1608
|
+
if (!this.#head) {
|
|
1609
|
+
this.#tail = void 0;
|
|
1610
|
+
}
|
|
1611
|
+
return current.value;
|
|
1612
|
+
}
|
|
1613
|
+
peek() {
|
|
1614
|
+
if (!this.#head) {
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
return this.#head.value;
|
|
1618
|
+
}
|
|
1619
|
+
clear() {
|
|
1620
|
+
this.#head = void 0;
|
|
1621
|
+
this.#tail = void 0;
|
|
1622
|
+
this.#size = 0;
|
|
1623
|
+
}
|
|
1624
|
+
get size() {
|
|
1625
|
+
return this.#size;
|
|
1626
|
+
}
|
|
1627
|
+
*[Symbol.iterator]() {
|
|
1628
|
+
let current = this.#head;
|
|
1629
|
+
while (current) {
|
|
1630
|
+
yield current.value;
|
|
1631
|
+
current = current.next;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
*drain() {
|
|
1635
|
+
while (this.#head) {
|
|
1636
|
+
yield this.dequeue();
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
function pLimit(concurrency) {
|
|
1641
|
+
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
|
1642
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
1643
|
+
}
|
|
1644
|
+
const queue = new Queue();
|
|
1645
|
+
let activeCount = 0;
|
|
1646
|
+
const next = () => {
|
|
1647
|
+
activeCount--;
|
|
1648
|
+
if (queue.size > 0) {
|
|
1649
|
+
queue.dequeue()();
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
const run = async (function_, resolve, arguments_) => {
|
|
1653
|
+
activeCount++;
|
|
1654
|
+
const result = (async () => function_(...arguments_))();
|
|
1655
|
+
resolve(result);
|
|
1656
|
+
try {
|
|
1657
|
+
await result;
|
|
1658
|
+
} catch {
|
|
1659
|
+
}
|
|
1660
|
+
next();
|
|
1661
|
+
};
|
|
1662
|
+
const enqueue = (function_, resolve, arguments_) => {
|
|
1663
|
+
queue.enqueue(
|
|
1664
|
+
AsyncResource.bind(run.bind(void 0, function_, resolve, arguments_))
|
|
1665
|
+
);
|
|
1666
|
+
(async () => {
|
|
1667
|
+
await Promise.resolve();
|
|
1668
|
+
if (activeCount < concurrency && queue.size > 0) {
|
|
1669
|
+
queue.dequeue()();
|
|
1670
|
+
}
|
|
1671
|
+
})();
|
|
1672
|
+
};
|
|
1673
|
+
const generator = (function_, ...arguments_) => new Promise((resolve) => {
|
|
1674
|
+
enqueue(function_, resolve, arguments_);
|
|
1675
|
+
});
|
|
1676
|
+
Object.defineProperties(generator, {
|
|
1677
|
+
activeCount: {
|
|
1678
|
+
get: () => activeCount
|
|
1679
|
+
},
|
|
1680
|
+
pendingCount: {
|
|
1681
|
+
get: () => queue.size
|
|
1682
|
+
},
|
|
1683
|
+
clearQueue: {
|
|
1684
|
+
value() {
|
|
1685
|
+
queue.clear();
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1689
|
+
return generator;
|
|
1690
|
+
}
|
|
1567
1691
|
async function materializeRuntimeExtras(input) {
|
|
1568
1692
|
const root = targetPaths.runtimeRoot(
|
|
1569
1693
|
input.directory,
|
|
@@ -1628,6 +1752,16 @@ function errorMessage(error) {
|
|
|
1628
1752
|
}
|
|
1629
1753
|
|
|
1630
1754
|
// src/install/runner.ts
|
|
1755
|
+
var DOWNLOAD_GROUPS = [
|
|
1756
|
+
{ categories: ["runtime-file"], phase: InstallPhases.INSTALLING_RUNTIME },
|
|
1757
|
+
{ categories: ["client-jar"], phase: InstallPhases.DOWNLOADING_CLIENT_JAR },
|
|
1758
|
+
{ categories: ["library"], phase: InstallPhases.DOWNLOADING_LIBRARIES },
|
|
1759
|
+
{ categories: ["asset-index"], phase: InstallPhases.DOWNLOADING_ASSET_INDEX },
|
|
1760
|
+
{ categories: ["asset"], phase: InstallPhases.DOWNLOADING_ASSETS },
|
|
1761
|
+
{ categories: ["logging-config"], phase: InstallPhases.WRITING_FILES },
|
|
1762
|
+
{ categories: ["fabric-library"], phase: InstallPhases.INSTALLING_FABRIC },
|
|
1763
|
+
{ categories: ["forge-installer", "forge-library"], phase: InstallPhases.INSTALLING_FORGE }
|
|
1764
|
+
];
|
|
1631
1765
|
async function runInstall(input) {
|
|
1632
1766
|
const startedAt = Date.now();
|
|
1633
1767
|
let bytesDownloaded = 0;
|
|
@@ -1640,50 +1774,89 @@ async function runInstall(input) {
|
|
|
1640
1774
|
onEvent?.({ type: "install:phase-changed", phase, previous: currentPhase });
|
|
1641
1775
|
currentPhase = phase;
|
|
1642
1776
|
};
|
|
1643
|
-
const
|
|
1777
|
+
const checkpoint = async () => {
|
|
1778
|
+
if (input.signal?.aborted) {
|
|
1779
|
+
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1780
|
+
}
|
|
1781
|
+
await input.pauseController?.waitWhilePaused();
|
|
1782
|
+
if (input.signal?.aborted) {
|
|
1783
|
+
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
const categoryFilter = input.actionCategories;
|
|
1787
|
+
const downloads = input.plan.actions.filter(isDownload).filter((a) => categoryFilter ? categoryFilter.has(a.category) : true);
|
|
1644
1788
|
const natives = input.plan.actions.filter(isNative);
|
|
1645
1789
|
const writeActions = input.plan.actions.filter(isWrite);
|
|
1646
1790
|
const processors = input.plan.actions.filter(isProcessor);
|
|
1647
1791
|
enterPhase(InstallPhases.PLANNING);
|
|
1648
|
-
enterPhase(InstallPhases.DOWNLOADING_LIBRARIES);
|
|
1649
1792
|
const limit = pLimit(input.concurrency ?? DOWNLOAD_CONCURRENCY);
|
|
1650
|
-
|
|
1651
|
-
downloads.
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1793
|
+
for (const group of DOWNLOAD_GROUPS) {
|
|
1794
|
+
const groupActions = downloads.filter((action) => group.categories.includes(action.category));
|
|
1795
|
+
if (groupActions.length === 0) continue;
|
|
1796
|
+
await checkpoint();
|
|
1797
|
+
enterPhase(group.phase);
|
|
1798
|
+
await Promise.all(
|
|
1799
|
+
groupActions.map(
|
|
1800
|
+
(action) => limit(async () => {
|
|
1801
|
+
await checkpoint();
|
|
1802
|
+
const result = await downloadFile(input.http, {
|
|
1803
|
+
url: action.url,
|
|
1804
|
+
target: action.target,
|
|
1805
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
1806
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
1807
|
+
...action.category !== void 0 ? { category: action.category } : {},
|
|
1808
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1809
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1810
|
+
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1811
|
+
});
|
|
1812
|
+
bytesDownloaded += result.bytesDownloaded;
|
|
1813
|
+
if (result.skipped) actionsSkipped++;
|
|
1814
|
+
actionsCompleted++;
|
|
1815
|
+
})
|
|
1816
|
+
)
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
const ungrouped = downloads.filter(
|
|
1820
|
+
(action) => !DOWNLOAD_GROUPS.some((g) => g.categories.includes(action.category))
|
|
1670
1821
|
);
|
|
1822
|
+
if (ungrouped.length > 0) {
|
|
1823
|
+
await checkpoint();
|
|
1824
|
+
enterPhase(InstallPhases.DOWNLOADING_LIBRARIES);
|
|
1825
|
+
await Promise.all(
|
|
1826
|
+
ungrouped.map(
|
|
1827
|
+
(action) => limit(async () => {
|
|
1828
|
+
await checkpoint();
|
|
1829
|
+
const result = await downloadFile(input.http, {
|
|
1830
|
+
url: action.url,
|
|
1831
|
+
target: action.target,
|
|
1832
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
1833
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
1834
|
+
...action.category !== void 0 ? { category: action.category } : {},
|
|
1835
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1836
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1837
|
+
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1838
|
+
});
|
|
1839
|
+
bytesDownloaded += result.bytesDownloaded;
|
|
1840
|
+
if (result.skipped) actionsSkipped++;
|
|
1841
|
+
actionsCompleted++;
|
|
1842
|
+
})
|
|
1843
|
+
)
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1671
1846
|
if (writeActions.length > 0) {
|
|
1847
|
+
await checkpoint();
|
|
1672
1848
|
enterPhase(InstallPhases.WRITING_FILES);
|
|
1673
1849
|
for (const action of writeActions) {
|
|
1674
|
-
|
|
1675
|
-
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1676
|
-
}
|
|
1850
|
+
await checkpoint();
|
|
1677
1851
|
await atomicWrite(action.path, action.content);
|
|
1678
1852
|
actionsCompleted++;
|
|
1679
1853
|
}
|
|
1680
1854
|
}
|
|
1681
1855
|
if (natives.length > 0) {
|
|
1856
|
+
await checkpoint();
|
|
1682
1857
|
enterPhase(InstallPhases.EXTRACTING_NATIVES);
|
|
1683
1858
|
for (const action of natives) {
|
|
1684
|
-
|
|
1685
|
-
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1686
|
-
}
|
|
1859
|
+
await checkpoint();
|
|
1687
1860
|
const { fileCount } = await extractAllToDir(action.source, action.destination, {
|
|
1688
1861
|
excludePrefixes: action.exclude
|
|
1689
1862
|
});
|
|
@@ -1697,6 +1870,7 @@ async function runInstall(input) {
|
|
|
1697
1870
|
}
|
|
1698
1871
|
}
|
|
1699
1872
|
if (input.plan.target.runtime !== void 0) {
|
|
1873
|
+
await checkpoint();
|
|
1700
1874
|
enterPhase(InstallPhases.INSTALLING_RUNTIME);
|
|
1701
1875
|
const runtimePlan = await planRuntimeDownloads({
|
|
1702
1876
|
runtime: input.plan.target.runtime,
|
|
@@ -1712,6 +1886,7 @@ async function runInstall(input) {
|
|
|
1712
1886
|
});
|
|
1713
1887
|
}
|
|
1714
1888
|
if (processors.length > 0) {
|
|
1889
|
+
await checkpoint();
|
|
1715
1890
|
enterPhase(InstallPhases.RUNNING_FORGE_PROCESSORS);
|
|
1716
1891
|
if (input.plan.target.loader.type !== Loaders.FORGE) {
|
|
1717
1892
|
throw new MinecraftKitError(
|
|
@@ -1726,9 +1901,7 @@ async function runInstall(input) {
|
|
|
1726
1901
|
input.plan.target.runtime.installRoot
|
|
1727
1902
|
);
|
|
1728
1903
|
for (const action of processors) {
|
|
1729
|
-
|
|
1730
|
-
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1731
|
-
}
|
|
1904
|
+
await checkpoint();
|
|
1732
1905
|
await runProcessor({
|
|
1733
1906
|
action,
|
|
1734
1907
|
javaPath,
|
|
@@ -1943,6 +2116,33 @@ function pickArguments(args, context) {
|
|
|
1943
2116
|
};
|
|
1944
2117
|
}
|
|
1945
2118
|
|
|
2119
|
+
// src/launch/jvm-compat.ts
|
|
2120
|
+
var FLAG_MIN_JAVA = [
|
|
2121
|
+
{ prefix: "--sun-misc-unsafe-memory-access", minJava: 23 },
|
|
2122
|
+
{ prefix: "--enable-native-access", minJava: 17 },
|
|
2123
|
+
{ prefix: "-XX:+UseCompactObjectHeaders", minJava: 24 },
|
|
2124
|
+
{ prefix: "-XX:+UseZGC", minJava: 15 }
|
|
2125
|
+
];
|
|
2126
|
+
function filterArgsForJava(input) {
|
|
2127
|
+
if (!Number.isFinite(input.javaMajor) || input.javaMajor <= 0) return input.args;
|
|
2128
|
+
const out = [];
|
|
2129
|
+
for (const arg of input.args) {
|
|
2130
|
+
const incompatible = FLAG_MIN_JAVA.find(
|
|
2131
|
+
({ prefix }) => arg === prefix || arg.startsWith(`${prefix}=`) || arg.startsWith(`${prefix} `)
|
|
2132
|
+
);
|
|
2133
|
+
if (incompatible && input.javaMajor < incompatible.minJava) {
|
|
2134
|
+
input.logger?.log(
|
|
2135
|
+
"warn",
|
|
2136
|
+
`Dropping JVM arg "${arg}" \u2014 requires Java ${incompatible.minJava}, runtime is Java ${input.javaMajor}`,
|
|
2137
|
+
{ flag: arg, minJava: incompatible.minJava, runtimeJava: input.javaMajor }
|
|
2138
|
+
);
|
|
2139
|
+
continue;
|
|
2140
|
+
}
|
|
2141
|
+
out.push(arg);
|
|
2142
|
+
}
|
|
2143
|
+
return out;
|
|
2144
|
+
}
|
|
2145
|
+
|
|
1946
2146
|
// src/launch/placeholders.ts
|
|
1947
2147
|
function substituteArg(raw, values) {
|
|
1948
2148
|
return raw.replaceAll(/\$\{([a-zA-Z0-9_]+)\}/g, (match, key) => {
|
|
@@ -1982,7 +2182,13 @@ function composeArgs(input) {
|
|
|
1982
2182
|
const baseJvm = [...memoryArgs, ...BASE_JVM_ARGS, ...macosArgs];
|
|
1983
2183
|
const substitutedJvm = substituteArgs(rawJvm, input.placeholderValues);
|
|
1984
2184
|
const substitutedGame = substituteArgs(rawGame, input.placeholderValues);
|
|
1985
|
-
const
|
|
2185
|
+
const javaMajor = input.target.runtime.majorVersion;
|
|
2186
|
+
const filteredManifestJvm = javaMajor !== void 0 ? filterArgsForJava({
|
|
2187
|
+
args: substitutedJvm,
|
|
2188
|
+
javaMajor,
|
|
2189
|
+
logger: input.logger ?? silentLogger
|
|
2190
|
+
}) : substitutedJvm;
|
|
2191
|
+
const jvmArgs = [...baseJvm, ...filteredManifestJvm];
|
|
1986
2192
|
if (input.merged.logging?.client?.argument) {
|
|
1987
2193
|
const logging = input.merged.logging.client;
|
|
1988
2194
|
const loggingArg = substituteArgs([logging.argument], {
|
|
@@ -2112,8 +2318,28 @@ function mergeManifest(parent, child) {
|
|
|
2112
2318
|
};
|
|
2113
2319
|
return merged;
|
|
2114
2320
|
}
|
|
2321
|
+
function libraryDedupeKey(library) {
|
|
2322
|
+
if (!library.name) return null;
|
|
2323
|
+
try {
|
|
2324
|
+
const coord = parseMavenCoordinate(library.name);
|
|
2325
|
+
const classifier = coord.classifier ? `:${coord.classifier}` : "";
|
|
2326
|
+
return `${coord.group}:${coord.artifact}${classifier}`;
|
|
2327
|
+
} catch {
|
|
2328
|
+
return null;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2115
2331
|
function mergeLibraries(parent, child) {
|
|
2116
|
-
|
|
2332
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2333
|
+
const unkeyed = [];
|
|
2334
|
+
for (const lib of [...parent, ...child]) {
|
|
2335
|
+
const key = libraryDedupeKey(lib);
|
|
2336
|
+
if (key === null) {
|
|
2337
|
+
unkeyed.push(lib);
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
byKey.set(key, lib);
|
|
2341
|
+
}
|
|
2342
|
+
return [...byKey.values(), ...unkeyed];
|
|
2117
2343
|
}
|
|
2118
2344
|
function mergeArguments(parent, child) {
|
|
2119
2345
|
if (!parent && !child) return void 0;
|
|
@@ -2237,7 +2463,8 @@ async function composeLaunch(input) {
|
|
|
2237
2463
|
merged: resolved.merged,
|
|
2238
2464
|
options,
|
|
2239
2465
|
placeholderValues,
|
|
2240
|
-
features
|
|
2466
|
+
features,
|
|
2467
|
+
logger: input.logger ?? silentLogger
|
|
2241
2468
|
});
|
|
2242
2469
|
return {
|
|
2243
2470
|
targetId: target.id,
|
|
@@ -2419,193 +2646,553 @@ var VerifyFileCategories = {
|
|
|
2419
2646
|
RUNTIME_FILE: "runtime-file",
|
|
2420
2647
|
LOGGING_CONFIG: "logging-config"
|
|
2421
2648
|
};
|
|
2649
|
+
async function sha1OfFile(filePath) {
|
|
2650
|
+
const hash = crypto2.createHash("sha1");
|
|
2651
|
+
await new Promise((resolve, reject) => {
|
|
2652
|
+
const stream = createReadStream(filePath);
|
|
2653
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
2654
|
+
stream.on("end", resolve);
|
|
2655
|
+
stream.on("error", reject);
|
|
2656
|
+
});
|
|
2657
|
+
return hash.digest("hex");
|
|
2658
|
+
}
|
|
2422
2659
|
|
|
2423
|
-
// src/
|
|
2424
|
-
function
|
|
2425
|
-
|
|
2660
|
+
// src/verify/helpers.ts
|
|
2661
|
+
async function runVerification(input, check) {
|
|
2662
|
+
const startedAt = Date.now();
|
|
2663
|
+
const results = [];
|
|
2664
|
+
const record = (result) => {
|
|
2665
|
+
results.push(result);
|
|
2666
|
+
input.onEvent?.({ type: "verify:file-checked", file: result });
|
|
2667
|
+
};
|
|
2668
|
+
await check(record);
|
|
2669
|
+
return {
|
|
2670
|
+
targetId: input.targetId,
|
|
2671
|
+
kind: input.kind,
|
|
2672
|
+
isValid: results.every((r) => r.status === VerifyFileStatuses.OK),
|
|
2673
|
+
issues: results.filter((r) => r.status !== VerifyFileStatuses.OK),
|
|
2674
|
+
checkedFiles: results.length,
|
|
2675
|
+
durationMs: Date.now() - startedAt
|
|
2676
|
+
};
|
|
2426
2677
|
}
|
|
2427
|
-
function
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2678
|
+
async function verifyHashedFile(input) {
|
|
2679
|
+
if (!await fileExists(input.path)) {
|
|
2680
|
+
return {
|
|
2681
|
+
path: input.path,
|
|
2682
|
+
category: input.category,
|
|
2683
|
+
status: VerifyFileStatuses.MISSING,
|
|
2684
|
+
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2685
|
+
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2686
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
if (input.expectedSize !== void 0) {
|
|
2690
|
+
const size = await fileSize(input.path);
|
|
2691
|
+
if (size !== input.expectedSize) {
|
|
2692
|
+
return {
|
|
2693
|
+
path: input.path,
|
|
2694
|
+
category: input.category,
|
|
2695
|
+
status: VerifyFileStatuses.WRONG_SIZE,
|
|
2696
|
+
expectedSize: input.expectedSize,
|
|
2697
|
+
actualSize: size,
|
|
2698
|
+
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2699
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
if (input.expectedSha1 !== void 0) {
|
|
2704
|
+
const actualSha1 = await sha1OfFile(input.path);
|
|
2705
|
+
if (actualSha1 !== input.expectedSha1) {
|
|
2706
|
+
return {
|
|
2707
|
+
path: input.path,
|
|
2708
|
+
category: input.category,
|
|
2709
|
+
status: VerifyFileStatuses.CORRUPT,
|
|
2710
|
+
expectedSha1: input.expectedSha1,
|
|
2711
|
+
actualSha1,
|
|
2712
|
+
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2713
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2714
|
+
};
|
|
2434
2715
|
}
|
|
2435
2716
|
}
|
|
2436
2717
|
return {
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
}
|
|
2444
|
-
return false;
|
|
2445
|
-
},
|
|
2446
|
-
categoriesAt: (path13) => map.get(path13) ?? /* @__PURE__ */ new Set()
|
|
2718
|
+
path: input.path,
|
|
2719
|
+
category: input.category,
|
|
2720
|
+
status: VerifyFileStatuses.OK,
|
|
2721
|
+
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2722
|
+
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2723
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2447
2724
|
};
|
|
2448
2725
|
}
|
|
2449
|
-
function
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2726
|
+
async function verifyExistence(input) {
|
|
2727
|
+
if (await fileExists(input.path)) {
|
|
2728
|
+
return {
|
|
2729
|
+
path: input.path,
|
|
2730
|
+
category: input.category,
|
|
2731
|
+
status: VerifyFileStatuses.OK,
|
|
2732
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2458
2735
|
return {
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
totalActions: actions.length,
|
|
2464
|
-
totalBytes: sumDownloadBytes(actions)
|
|
2736
|
+
path: input.path,
|
|
2737
|
+
category: input.category,
|
|
2738
|
+
status: VerifyFileStatuses.MISSING,
|
|
2739
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2465
2740
|
};
|
|
2466
2741
|
}
|
|
2467
|
-
async function
|
|
2468
|
-
const
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
const actions = selectRepairActions({
|
|
2476
|
-
target: input.target,
|
|
2477
|
-
installPlan,
|
|
2478
|
-
issues,
|
|
2479
|
-
aspectFilter
|
|
2480
|
-
});
|
|
2481
|
-
postprocess?.({ actions, installPlan, issues });
|
|
2482
|
-
return buildRepairPlan(input.target, actions);
|
|
2483
|
-
}
|
|
2484
|
-
function selectRepairActions(input) {
|
|
2485
|
-
const matching = [];
|
|
2486
|
-
for (const action of input.installPlan.actions) {
|
|
2487
|
-
if (!input.aspectFilter(action)) continue;
|
|
2488
|
-
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
2489
|
-
if (input.issues.hasNonNative(action.target)) {
|
|
2490
|
-
matching.push(action);
|
|
2491
|
-
}
|
|
2492
|
-
} else if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
2493
|
-
if (input.issues.has(action.path)) {
|
|
2494
|
-
matching.push(action);
|
|
2495
|
-
}
|
|
2496
|
-
} else if (action.kind === InstallActionKinds.EXTRACT_NATIVE) {
|
|
2497
|
-
if (input.issues.has(action.source)) {
|
|
2498
|
-
matching.push(action);
|
|
2499
|
-
}
|
|
2500
|
-
} else {
|
|
2501
|
-
matching.push(action);
|
|
2742
|
+
async function findForgeVersionJsonPath(directory, minecraftVersion) {
|
|
2743
|
+
const versionsDir = targetPaths.versionsDir(directory);
|
|
2744
|
+
const dirs = await listChildDirectories(versionsDir);
|
|
2745
|
+
for (const id of dirs) {
|
|
2746
|
+
if (!id.startsWith(`${minecraftVersion}-forge-`)) continue;
|
|
2747
|
+
const jsonPath = targetPaths.versionJson(directory, id);
|
|
2748
|
+
if (!await fileExists(jsonPath)) {
|
|
2749
|
+
return jsonPath;
|
|
2502
2750
|
}
|
|
2751
|
+
const parsed = await tryParseInheritsFrom(jsonPath);
|
|
2752
|
+
if (parsed === minecraftVersion) return jsonPath;
|
|
2753
|
+
}
|
|
2754
|
+
return null;
|
|
2755
|
+
}
|
|
2756
|
+
async function tryParseInheritsFrom(jsonPath) {
|
|
2757
|
+
try {
|
|
2758
|
+
const parsed = JSON.parse(await readText(jsonPath));
|
|
2759
|
+
return parsed.inheritsFrom;
|
|
2760
|
+
} catch {
|
|
2761
|
+
return void 0;
|
|
2503
2762
|
}
|
|
2504
|
-
return matching;
|
|
2505
2763
|
}
|
|
2506
2764
|
|
|
2507
|
-
// src/
|
|
2508
|
-
async function
|
|
2765
|
+
// src/verify/fabric.ts
|
|
2766
|
+
async function verifyFabric(input) {
|
|
2509
2767
|
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
2510
2768
|
throw new MinecraftKitError(
|
|
2511
2769
|
"INVALID_INPUT",
|
|
2512
|
-
`
|
|
2770
|
+
`verify.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
2513
2771
|
);
|
|
2514
2772
|
}
|
|
2515
|
-
const
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2773
|
+
const loader = input.target.loader;
|
|
2774
|
+
return runVerification(
|
|
2775
|
+
{
|
|
2776
|
+
targetId: input.target.id,
|
|
2777
|
+
kind: VerificationKinds.FABRIC,
|
|
2778
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2779
|
+
},
|
|
2780
|
+
async (record) => {
|
|
2781
|
+
record(
|
|
2782
|
+
await verifyExistence({
|
|
2783
|
+
path: targetPaths.versionJson(input.target.directory, loader.profile.id),
|
|
2784
|
+
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2785
|
+
})
|
|
2786
|
+
);
|
|
2787
|
+
const fabricLibraries = planLibraryDownloads({
|
|
2788
|
+
libraries: loader.profile.libraries,
|
|
2789
|
+
directory: input.target.directory,
|
|
2790
|
+
system: input.target.runtime.system,
|
|
2791
|
+
versionId: input.target.minecraft.version,
|
|
2792
|
+
category: "fabric-library"
|
|
2793
|
+
});
|
|
2794
|
+
for (const action of fabricLibraries.downloads) {
|
|
2795
|
+
record(
|
|
2796
|
+
await verifyHashedFile({
|
|
2797
|
+
path: action.target,
|
|
2798
|
+
expectedSha1: action.expectedSha1,
|
|
2799
|
+
expectedSize: action.expectedSize,
|
|
2800
|
+
...action.url ? { url: action.url } : {},
|
|
2801
|
+
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2802
|
+
})
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2525
2805
|
}
|
|
2526
|
-
|
|
2527
|
-
});
|
|
2806
|
+
);
|
|
2528
2807
|
}
|
|
2529
2808
|
|
|
2530
|
-
// src/
|
|
2531
|
-
|
|
2532
|
-
"forge-library",
|
|
2533
|
-
"forge-installer"
|
|
2534
|
-
]);
|
|
2535
|
-
async function planForgeRepair(input) {
|
|
2809
|
+
// src/verify/forge.ts
|
|
2810
|
+
async function verifyForge(input) {
|
|
2536
2811
|
if (input.target.loader.type !== Loaders.FORGE) {
|
|
2537
2812
|
throw new MinecraftKitError(
|
|
2538
2813
|
"INVALID_INPUT",
|
|
2539
|
-
`
|
|
2814
|
+
`verify.forge requires a Forge target (got ${input.target.loader.type})`
|
|
2540
2815
|
);
|
|
2541
2816
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
input,
|
|
2548
|
-
(action) => {
|
|
2549
|
-
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
2550
|
-
return FORGE_DOWNLOAD_CATEGORIES.has(action.category);
|
|
2551
|
-
}
|
|
2552
|
-
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
2553
|
-
return action.path === forgeJsonPath;
|
|
2554
|
-
}
|
|
2555
|
-
return false;
|
|
2817
|
+
return runVerification(
|
|
2818
|
+
{
|
|
2819
|
+
targetId: input.target.id,
|
|
2820
|
+
kind: VerificationKinds.FORGE,
|
|
2821
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2556
2822
|
},
|
|
2557
|
-
(
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2823
|
+
async (record) => {
|
|
2824
|
+
const forgeVersionJsonPath = await findForgeVersionJsonPath(
|
|
2825
|
+
input.target.directory,
|
|
2826
|
+
input.target.minecraft.version
|
|
2561
2827
|
);
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
}
|
|
2828
|
+
if (forgeVersionJsonPath === null) return;
|
|
2829
|
+
record(
|
|
2830
|
+
await verifyExistence({
|
|
2831
|
+
path: forgeVersionJsonPath,
|
|
2832
|
+
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2833
|
+
})
|
|
2834
|
+
);
|
|
2835
|
+
if (!await fileExists(forgeVersionJsonPath)) return;
|
|
2836
|
+
let parsed;
|
|
2837
|
+
try {
|
|
2838
|
+
parsed = JSON.parse(await readText(forgeVersionJsonPath));
|
|
2839
|
+
} catch {
|
|
2840
|
+
record({
|
|
2841
|
+
path: forgeVersionJsonPath,
|
|
2842
|
+
category: VerifyFileCategories.LOADER_LIBRARY,
|
|
2843
|
+
status: VerifyFileStatuses.CORRUPT
|
|
2844
|
+
});
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
const forgeLibraries = planLibraryDownloads({
|
|
2848
|
+
libraries: parsed.libraries,
|
|
2849
|
+
directory: input.target.directory,
|
|
2850
|
+
system: input.target.runtime.system,
|
|
2851
|
+
versionId: input.target.minecraft.version,
|
|
2852
|
+
category: "forge-library"
|
|
2853
|
+
});
|
|
2854
|
+
for (const action of forgeLibraries.downloads) {
|
|
2855
|
+
record(
|
|
2856
|
+
await verifyHashedFile({
|
|
2857
|
+
path: action.target,
|
|
2858
|
+
expectedSha1: action.expectedSha1,
|
|
2859
|
+
expectedSize: action.expectedSize,
|
|
2860
|
+
...action.url ? { url: action.url } : {},
|
|
2861
|
+
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2862
|
+
})
|
|
2863
|
+
);
|
|
2568
2864
|
}
|
|
2569
2865
|
}
|
|
2570
2866
|
);
|
|
2571
2867
|
}
|
|
2572
2868
|
|
|
2573
|
-
// src/
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
async
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2869
|
+
// src/verify/minecraft.ts
|
|
2870
|
+
async function verifyMinecraft(input) {
|
|
2871
|
+
return runVerification(
|
|
2872
|
+
{
|
|
2873
|
+
targetId: input.target.id,
|
|
2874
|
+
kind: VerificationKinds.MINECRAFT,
|
|
2875
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2876
|
+
},
|
|
2877
|
+
async (record) => {
|
|
2878
|
+
const { directory, minecraft, runtime } = input.target;
|
|
2879
|
+
record(
|
|
2880
|
+
await verifyHashedFile({
|
|
2881
|
+
path: targetPaths.versionJar(directory, minecraft.version),
|
|
2882
|
+
expectedSha1: minecraft.manifest.downloads.client.sha1,
|
|
2883
|
+
expectedSize: minecraft.manifest.downloads.client.size,
|
|
2884
|
+
url: minecraft.manifest.downloads.client.url,
|
|
2885
|
+
category: VerifyFileCategories.CLIENT_JAR
|
|
2886
|
+
})
|
|
2887
|
+
);
|
|
2888
|
+
record(
|
|
2889
|
+
await verifyExistence({
|
|
2890
|
+
path: targetPaths.versionJson(directory, minecraft.version),
|
|
2891
|
+
category: VerifyFileCategories.CLIENT_JAR
|
|
2892
|
+
})
|
|
2893
|
+
);
|
|
2894
|
+
if (minecraft.manifest.logging?.client) {
|
|
2895
|
+
const logging = minecraft.manifest.logging.client;
|
|
2896
|
+
record(
|
|
2897
|
+
await verifyHashedFile({
|
|
2898
|
+
path: targetPaths.loggingConfig(directory, logging.file.id),
|
|
2899
|
+
expectedSha1: logging.file.sha1,
|
|
2900
|
+
expectedSize: logging.file.size,
|
|
2901
|
+
url: logging.file.url,
|
|
2902
|
+
category: VerifyFileCategories.LOGGING_CONFIG
|
|
2903
|
+
})
|
|
2904
|
+
);
|
|
2905
|
+
}
|
|
2906
|
+
const libraryPlan = planLibraryDownloads({
|
|
2907
|
+
libraries: minecraft.manifest.libraries,
|
|
2908
|
+
directory,
|
|
2909
|
+
system: runtime.system,
|
|
2910
|
+
versionId: minecraft.version,
|
|
2911
|
+
category: "library"
|
|
2912
|
+
});
|
|
2913
|
+
for (const action of libraryPlan.downloads) {
|
|
2914
|
+
record(
|
|
2915
|
+
await verifyHashedFile({
|
|
2916
|
+
path: action.target,
|
|
2917
|
+
expectedSha1: action.expectedSha1,
|
|
2918
|
+
expectedSize: action.expectedSize,
|
|
2919
|
+
url: action.url,
|
|
2920
|
+
category: VerifyFileCategories.LIBRARY
|
|
2921
|
+
})
|
|
2922
|
+
);
|
|
2923
|
+
}
|
|
2924
|
+
const indexUrl = minecraft.manifest.assetIndex.url;
|
|
2925
|
+
const indexPath = targetPaths.assetIndex(directory, minecraft.manifest.assetIndex.id);
|
|
2926
|
+
record(
|
|
2927
|
+
await verifyHashedFile({
|
|
2928
|
+
path: indexPath,
|
|
2929
|
+
expectedSha1: minecraft.manifest.assetIndex.sha1,
|
|
2930
|
+
expectedSize: minecraft.manifest.assetIndex.size,
|
|
2931
|
+
url: indexUrl,
|
|
2932
|
+
category: VerifyFileCategories.ASSET_INDEX
|
|
2933
|
+
})
|
|
2934
|
+
);
|
|
2935
|
+
const indexDocument = await fetchJson(input.http, input.cache, {
|
|
2936
|
+
url: indexUrl,
|
|
2937
|
+
cacheKey: `asset-index:${minecraft.manifest.assetIndex.id}:${minecraft.manifest.assetIndex.sha1}`,
|
|
2938
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2939
|
+
});
|
|
2940
|
+
const seenAssetHashes = /* @__PURE__ */ new Set();
|
|
2941
|
+
for (const entry of Object.values(indexDocument.objects)) {
|
|
2942
|
+
if (seenAssetHashes.has(entry.hash)) continue;
|
|
2943
|
+
seenAssetHashes.add(entry.hash);
|
|
2944
|
+
record(
|
|
2945
|
+
await verifyHashedFile({
|
|
2946
|
+
path: targetPaths.assetObject(directory, entry.hash),
|
|
2947
|
+
expectedSha1: entry.hash,
|
|
2948
|
+
expectedSize: entry.size,
|
|
2949
|
+
category: VerifyFileCategories.ASSET
|
|
2950
|
+
})
|
|
2951
|
+
);
|
|
2952
|
+
}
|
|
2953
|
+
const nativesDir = targetPaths.nativesDir(directory, minecraft.version);
|
|
2954
|
+
if (!await fileExists(nativesDir)) {
|
|
2955
|
+
for (const extraction of libraryPlan.nativeExtractions) {
|
|
2956
|
+
record({
|
|
2957
|
+
path: extraction.source,
|
|
2958
|
+
category: VerifyFileCategories.NATIVE,
|
|
2959
|
+
status: VerifyFileStatuses.MISSING
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2595
2963
|
}
|
|
2596
|
-
|
|
2597
|
-
});
|
|
2964
|
+
);
|
|
2598
2965
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
...input.
|
|
2605
|
-
totalActions: input.plan.actions.length,
|
|
2606
|
-
totalBytes: input.plan.totalBytes
|
|
2966
|
+
async function verifyRuntime(input) {
|
|
2967
|
+
return runVerification(
|
|
2968
|
+
{
|
|
2969
|
+
targetId: input.target.id,
|
|
2970
|
+
kind: VerificationKinds.RUNTIME,
|
|
2971
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2607
2972
|
},
|
|
2608
|
-
|
|
2973
|
+
async (record) => {
|
|
2974
|
+
let manifest;
|
|
2975
|
+
try {
|
|
2976
|
+
manifest = await fetchJson(input.http, input.cache, {
|
|
2977
|
+
url: input.target.runtime.manifestUrl,
|
|
2978
|
+
cacheKey: `runtime-manifest:${input.target.runtime.component}:${input.target.runtime.platformKey}:${input.target.runtime.manifestSha1}`,
|
|
2979
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2980
|
+
});
|
|
2981
|
+
} catch {
|
|
2982
|
+
record({
|
|
2983
|
+
path: input.target.runtime.manifestUrl,
|
|
2984
|
+
category: VerifyFileCategories.RUNTIME_FILE,
|
|
2985
|
+
status: VerifyFileStatuses.MISSING
|
|
2986
|
+
});
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
const runtimeRoot = targetPaths.runtimeRoot(
|
|
2990
|
+
input.target.directory,
|
|
2991
|
+
input.target.runtime.component,
|
|
2992
|
+
input.target.runtime.installRoot
|
|
2993
|
+
);
|
|
2994
|
+
for (const [relative, entry] of Object.entries(manifest.files)) {
|
|
2995
|
+
if (entry.type !== "file") continue;
|
|
2996
|
+
record(
|
|
2997
|
+
await verifyHashedFile({
|
|
2998
|
+
path: path.join(runtimeRoot, relative),
|
|
2999
|
+
expectedSha1: entry.downloads.raw.sha1,
|
|
3000
|
+
expectedSize: entry.downloads.raw.size,
|
|
3001
|
+
url: entry.downloads.raw.url,
|
|
3002
|
+
category: VerifyFileCategories.RUNTIME_FILE
|
|
3003
|
+
})
|
|
3004
|
+
);
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
);
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
// src/repair/helpers.ts
|
|
3011
|
+
function asResultArray(from) {
|
|
3012
|
+
return Array.isArray(from) ? from : [from];
|
|
3013
|
+
}
|
|
3014
|
+
function buildIssueIndex(from) {
|
|
3015
|
+
const map = /* @__PURE__ */ new Map();
|
|
3016
|
+
for (const v of asResultArray(from)) {
|
|
3017
|
+
for (const issue of v.issues) {
|
|
3018
|
+
const set = map.get(issue.path);
|
|
3019
|
+
if (set) set.add(issue.category);
|
|
3020
|
+
else map.set(issue.path, /* @__PURE__ */ new Set([issue.category]));
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
return {
|
|
3024
|
+
has: (path13) => map.has(path13),
|
|
3025
|
+
hasNonNative: (path13) => {
|
|
3026
|
+
const cats = map.get(path13);
|
|
3027
|
+
if (!cats) return false;
|
|
3028
|
+
for (const c of cats) {
|
|
3029
|
+
if (c !== VerifyFileCategories.NATIVE) return true;
|
|
3030
|
+
}
|
|
3031
|
+
return false;
|
|
3032
|
+
},
|
|
3033
|
+
categoriesAt: (path13) => map.get(path13) ?? /* @__PURE__ */ new Set()
|
|
3034
|
+
};
|
|
3035
|
+
}
|
|
3036
|
+
function sumDownloadBytes(actions) {
|
|
3037
|
+
return actions.reduce((sum, action) => {
|
|
3038
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3039
|
+
return sum + (action.expectedSize ?? 0);
|
|
3040
|
+
}
|
|
3041
|
+
return sum;
|
|
3042
|
+
}, 0);
|
|
3043
|
+
}
|
|
3044
|
+
function buildRepairPlan(target, actions) {
|
|
3045
|
+
return {
|
|
3046
|
+
targetId: target.id,
|
|
3047
|
+
directory: target.directory,
|
|
3048
|
+
target,
|
|
3049
|
+
actions,
|
|
3050
|
+
totalActions: actions.length,
|
|
3051
|
+
totalBytes: sumDownloadBytes(actions)
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
async function planAspectRepair(input, aspectFilter, postprocess) {
|
|
3055
|
+
const installPlan = await planInstall({
|
|
3056
|
+
target: input.target,
|
|
3057
|
+
http: input.http,
|
|
3058
|
+
cache: input.cache,
|
|
3059
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3060
|
+
});
|
|
3061
|
+
const issues = buildIssueIndex(input.from);
|
|
3062
|
+
const actions = selectRepairActions({
|
|
3063
|
+
target: input.target,
|
|
3064
|
+
installPlan,
|
|
3065
|
+
issues,
|
|
3066
|
+
aspectFilter
|
|
3067
|
+
});
|
|
3068
|
+
postprocess?.({ actions, installPlan, issues });
|
|
3069
|
+
return buildRepairPlan(input.target, actions);
|
|
3070
|
+
}
|
|
3071
|
+
function selectRepairActions(input) {
|
|
3072
|
+
const matching = [];
|
|
3073
|
+
for (const action of input.installPlan.actions) {
|
|
3074
|
+
if (!input.aspectFilter(action)) continue;
|
|
3075
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3076
|
+
if (input.issues.hasNonNative(action.target)) {
|
|
3077
|
+
matching.push(action);
|
|
3078
|
+
}
|
|
3079
|
+
} else if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3080
|
+
if (input.issues.has(action.path)) {
|
|
3081
|
+
matching.push(action);
|
|
3082
|
+
}
|
|
3083
|
+
} else if (action.kind === InstallActionKinds.EXTRACT_NATIVE) {
|
|
3084
|
+
if (input.issues.has(action.source)) {
|
|
3085
|
+
matching.push(action);
|
|
3086
|
+
}
|
|
3087
|
+
} else {
|
|
3088
|
+
matching.push(action);
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
return matching;
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
// src/repair/fabric.ts
|
|
3095
|
+
async function planFabricRepair(input) {
|
|
3096
|
+
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
3097
|
+
throw new MinecraftKitError(
|
|
3098
|
+
"INVALID_INPUT",
|
|
3099
|
+
`repair.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
3100
|
+
);
|
|
3101
|
+
}
|
|
3102
|
+
const fabricJsonPath = targetPaths.versionJson(
|
|
3103
|
+
input.target.directory,
|
|
3104
|
+
input.target.loader.profile.id
|
|
3105
|
+
);
|
|
3106
|
+
return planAspectRepair(input, (action) => {
|
|
3107
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3108
|
+
return action.category === "fabric-library";
|
|
3109
|
+
}
|
|
3110
|
+
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3111
|
+
return action.path === fabricJsonPath;
|
|
3112
|
+
}
|
|
3113
|
+
return false;
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
// src/repair/forge.ts
|
|
3118
|
+
var FORGE_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3119
|
+
"forge-library",
|
|
3120
|
+
"forge-installer"
|
|
3121
|
+
]);
|
|
3122
|
+
async function planForgeRepair(input) {
|
|
3123
|
+
if (input.target.loader.type !== Loaders.FORGE) {
|
|
3124
|
+
throw new MinecraftKitError(
|
|
3125
|
+
"INVALID_INPUT",
|
|
3126
|
+
`repair.forge requires a Forge target (got ${input.target.loader.type})`
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
const forgeJsonPath = targetPaths.versionJson(
|
|
3130
|
+
input.target.directory,
|
|
3131
|
+
input.target.loader.fullVersion
|
|
3132
|
+
);
|
|
3133
|
+
return planAspectRepair(
|
|
3134
|
+
input,
|
|
3135
|
+
(action) => {
|
|
3136
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3137
|
+
return FORGE_DOWNLOAD_CATEGORIES.has(action.category);
|
|
3138
|
+
}
|
|
3139
|
+
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3140
|
+
return action.path === forgeJsonPath;
|
|
3141
|
+
}
|
|
3142
|
+
return false;
|
|
3143
|
+
},
|
|
3144
|
+
({ actions, installPlan, issues }) => {
|
|
3145
|
+
if (!issues.has(forgeJsonPath)) return;
|
|
3146
|
+
const alreadyIncluded = new Set(
|
|
3147
|
+
actions.filter((a) => a.kind === InstallActionKinds.DOWNLOAD_FILE).map((a) => a.target)
|
|
3148
|
+
);
|
|
3149
|
+
for (const action of installPlan.actions) {
|
|
3150
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category === "forge-library" && !alreadyIncluded.has(action.target)) {
|
|
3151
|
+
actions.push(action);
|
|
3152
|
+
} else if (action.kind === InstallActionKinds.RUN_FORGE_PROCESSOR) {
|
|
3153
|
+
actions.push(action);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
);
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
// src/repair/minecraft.ts
|
|
3161
|
+
var MINECRAFT_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3162
|
+
"client-jar",
|
|
3163
|
+
"library",
|
|
3164
|
+
"asset-index",
|
|
3165
|
+
"asset",
|
|
3166
|
+
"logging-config"
|
|
3167
|
+
]);
|
|
3168
|
+
async function planMinecraftRepair(input) {
|
|
3169
|
+
const vanillaJsonPath = targetPaths.versionJson(
|
|
3170
|
+
input.target.directory,
|
|
3171
|
+
input.target.minecraft.version
|
|
3172
|
+
);
|
|
3173
|
+
return planAspectRepair(input, (action) => {
|
|
3174
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3175
|
+
return MINECRAFT_DOWNLOAD_CATEGORIES.has(action.category);
|
|
3176
|
+
}
|
|
3177
|
+
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3178
|
+
return action.path === vanillaJsonPath;
|
|
3179
|
+
}
|
|
3180
|
+
if (action.kind === InstallActionKinds.EXTRACT_NATIVE) {
|
|
3181
|
+
return true;
|
|
3182
|
+
}
|
|
3183
|
+
return false;
|
|
3184
|
+
});
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
// src/repair/runner.ts
|
|
3188
|
+
async function runRepair(input) {
|
|
3189
|
+
const report = await runInstall({
|
|
3190
|
+
plan: {
|
|
3191
|
+
...input.plan,
|
|
3192
|
+
totalActions: input.plan.actions.length,
|
|
3193
|
+
totalBytes: input.plan.totalBytes
|
|
3194
|
+
},
|
|
3195
|
+
http: input.http,
|
|
2609
3196
|
cache: input.cache,
|
|
2610
3197
|
spawner: input.spawner,
|
|
2611
3198
|
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
@@ -2627,6 +3214,58 @@ async function planRuntimeRepair(input) {
|
|
|
2627
3214
|
);
|
|
2628
3215
|
}
|
|
2629
3216
|
|
|
3217
|
+
// src/repair/all.ts
|
|
3218
|
+
async function repairAll(input) {
|
|
3219
|
+
const startedAt = Date.now();
|
|
3220
|
+
const ctx = {
|
|
3221
|
+
target: input.target,
|
|
3222
|
+
http: input.http,
|
|
3223
|
+
cache: input.cache,
|
|
3224
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3225
|
+
};
|
|
3226
|
+
const verifications = [];
|
|
3227
|
+
const mc = await verifyMinecraft(ctx);
|
|
3228
|
+
verifications.push(mc);
|
|
3229
|
+
const rt = await verifyRuntime(ctx);
|
|
3230
|
+
verifications.push(rt);
|
|
3231
|
+
if (input.target.loader.type === Loaders.FABRIC) {
|
|
3232
|
+
verifications.push(await verifyFabric(ctx));
|
|
3233
|
+
} else if (input.target.loader.type === Loaders.FORGE) {
|
|
3234
|
+
verifications.push(await verifyForge(ctx));
|
|
3235
|
+
}
|
|
3236
|
+
const repairs = /* @__PURE__ */ new Map();
|
|
3237
|
+
let bytesDownloaded = 0;
|
|
3238
|
+
for (const verification of verifications) {
|
|
3239
|
+
if (verification.isValid) continue;
|
|
3240
|
+
const planner = PLANNERS[verification.kind];
|
|
3241
|
+
if (!planner) continue;
|
|
3242
|
+
const plan = await planner({ ...ctx, from: verification });
|
|
3243
|
+
if (plan.totalActions === 0) continue;
|
|
3244
|
+
const report = await runRepair({
|
|
3245
|
+
plan,
|
|
3246
|
+
http: input.http,
|
|
3247
|
+
cache: input.cache,
|
|
3248
|
+
spawner: input.spawner,
|
|
3249
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
3250
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
3251
|
+
});
|
|
3252
|
+
repairs.set(verification.kind, report);
|
|
3253
|
+
bytesDownloaded += report.bytesDownloaded;
|
|
3254
|
+
}
|
|
3255
|
+
return {
|
|
3256
|
+
verifications,
|
|
3257
|
+
repairs,
|
|
3258
|
+
bytesDownloaded,
|
|
3259
|
+
durationMs: Date.now() - startedAt
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3262
|
+
var PLANNERS = {
|
|
3263
|
+
minecraft: planMinecraftRepair,
|
|
3264
|
+
runtime: planRuntimeRepair,
|
|
3265
|
+
fabric: planFabricRepair,
|
|
3266
|
+
forge: planForgeRepair
|
|
3267
|
+
};
|
|
3268
|
+
|
|
2630
3269
|
// src/types/runtime.ts
|
|
2631
3270
|
var RuntimeComponents = {
|
|
2632
3271
|
JRE_LEGACY: "jre-legacy",
|
|
@@ -2701,485 +3340,125 @@ var TargetsApi = class {
|
|
|
2701
3340
|
const runtime = input.runtime?.installRoot !== void 0 ? { ...resolvedRuntime, installRoot: input.runtime.installRoot } : resolvedRuntime;
|
|
2702
3341
|
let loader;
|
|
2703
3342
|
if (input.loader.type === Loaders.VANILLA) {
|
|
2704
|
-
loader = {
|
|
2705
|
-
type: Loaders.VANILLA,
|
|
2706
|
-
minecraftVersion: minecraft.version,
|
|
2707
|
-
minecraft
|
|
2708
|
-
};
|
|
2709
|
-
} else if (input.loader.type === Loaders.FABRIC) {
|
|
2710
|
-
loader = await this.ctx.fabric.resolve({
|
|
2711
|
-
minecraftVersion: minecraft.version,
|
|
2712
|
-
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
2713
|
-
...input.loader.version !== void 0 ? { loaderVersion: input.loader.version } : {},
|
|
2714
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2715
|
-
});
|
|
2716
|
-
} else {
|
|
2717
|
-
loader = await this.ctx.forge.resolve({
|
|
2718
|
-
minecraftVersion: minecraft.version,
|
|
2719
|
-
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
2720
|
-
...input.loader.version !== void 0 ? { forgeVersion: input.loader.version } : {},
|
|
2721
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2722
|
-
});
|
|
2723
|
-
}
|
|
2724
|
-
return this.create({
|
|
2725
|
-
id: input.id,
|
|
2726
|
-
directory: input.directory,
|
|
2727
|
-
minecraft,
|
|
2728
|
-
loader,
|
|
2729
|
-
runtime
|
|
2730
|
-
});
|
|
2731
|
-
}
|
|
2732
|
-
/** Scan a root directory for Minecraft installations. Returns only what is on disk. */
|
|
2733
|
-
async list(input) {
|
|
2734
|
-
if (!await dirExists(input.rootDir)) return [];
|
|
2735
|
-
const subdirs = await listChildDirectories(input.rootDir);
|
|
2736
|
-
const results = [];
|
|
2737
|
-
for (const id of subdirs) {
|
|
2738
|
-
const directory = path.join(input.rootDir, id);
|
|
2739
|
-
const discovered = await discoverInstallation(id, directory);
|
|
2740
|
-
if (discovered) results.push(discovered);
|
|
2741
|
-
}
|
|
2742
|
-
return results;
|
|
2743
|
-
}
|
|
2744
|
-
};
|
|
2745
|
-
async function discoverInstallation(id, directory) {
|
|
2746
|
-
const versionsDir = path.join(directory, VERSIONS_DIR);
|
|
2747
|
-
const librariesDir = path.join(directory, LIBRARIES_DIR);
|
|
2748
|
-
const assetsDir = path.join(directory, ASSETS_DIR);
|
|
2749
|
-
const looksLikeInstall = await dirExists(versionsDir) && (await dirExists(librariesDir) || await dirExists(assetsDir));
|
|
2750
|
-
if (!looksLikeInstall) return null;
|
|
2751
|
-
const versionDirs = await listChildDirectories(versionsDir);
|
|
2752
|
-
const minecraftVersions = [];
|
|
2753
|
-
const loaders = [];
|
|
2754
|
-
for (const versionId of versionDirs) {
|
|
2755
|
-
const hint = inferLoaderFromVersionId(versionId);
|
|
2756
|
-
if (hint) {
|
|
2757
|
-
loaders.push(hint);
|
|
2758
|
-
if (hint.minecraftVersion && !minecraftVersions.includes(hint.minecraftVersion)) {
|
|
2759
|
-
minecraftVersions.push(hint.minecraftVersion);
|
|
2760
|
-
}
|
|
2761
|
-
} else {
|
|
2762
|
-
minecraftVersions.push(versionId);
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
const runtime = await discoverRuntime(directory);
|
|
2766
|
-
return { id, directory, minecraftVersions, loaders, ...runtime ? { runtime } : {} };
|
|
2767
|
-
}
|
|
2768
|
-
async function discoverRuntime(directory) {
|
|
2769
|
-
const runtimeDir = path.join(directory, RUNTIMES_DIR);
|
|
2770
|
-
if (!await dirExists(runtimeDir)) return void 0;
|
|
2771
|
-
let components;
|
|
2772
|
-
try {
|
|
2773
|
-
components = await listChildDirectories(runtimeDir);
|
|
2774
|
-
} catch {
|
|
2775
|
-
return void 0;
|
|
2776
|
-
}
|
|
2777
|
-
for (const component of components) {
|
|
2778
|
-
const root = path.join(runtimeDir, component);
|
|
2779
|
-
const javaPath = process.platform === "win32" ? path.join(root, "bin", "javaw.exe") : process.platform === "darwin" ? path.join(root, "jre.bundle", "Contents", "Home", "bin", "java") : path.join(root, "bin", "java");
|
|
2780
|
-
if (await fileExists(javaPath)) {
|
|
2781
|
-
return { component, javaPath };
|
|
2782
|
-
}
|
|
2783
|
-
}
|
|
2784
|
-
return void 0;
|
|
2785
|
-
}
|
|
2786
|
-
function inferLoaderFromVersionId(versionId) {
|
|
2787
|
-
const fabricMatch = /^fabric-loader-([^-]+)-(.+)$/.exec(versionId);
|
|
2788
|
-
if (fabricMatch?.[1] && fabricMatch[2]) {
|
|
2789
|
-
return { type: Loaders.FABRIC, version: fabricMatch[1], minecraftVersion: fabricMatch[2] };
|
|
2790
|
-
}
|
|
2791
|
-
const forgeMatch = /^([^-]+)-forge-(.+)$/.exec(versionId);
|
|
2792
|
-
if (forgeMatch?.[1] && forgeMatch[2]) {
|
|
2793
|
-
return { type: Loaders.FORGE, minecraftVersion: forgeMatch[1], version: forgeMatch[2] };
|
|
2794
|
-
}
|
|
2795
|
-
return null;
|
|
2796
|
-
}
|
|
2797
|
-
|
|
2798
|
-
// src/update/runner.ts
|
|
2799
|
-
async function planUpdate(input) {
|
|
2800
|
-
return planInstall({
|
|
2801
|
-
target: input.target,
|
|
2802
|
-
http: input.http,
|
|
2803
|
-
cache: input.cache,
|
|
2804
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2805
|
-
});
|
|
2806
|
-
}
|
|
2807
|
-
async function runUpdate(input) {
|
|
2808
|
-
const report = await runInstall({
|
|
2809
|
-
plan: input.plan,
|
|
2810
|
-
http: input.http,
|
|
2811
|
-
cache: input.cache,
|
|
2812
|
-
spawner: input.spawner,
|
|
2813
|
-
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
2814
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2815
|
-
});
|
|
2816
|
-
return {
|
|
2817
|
-
targetId: report.targetId,
|
|
2818
|
-
bytesDownloaded: report.bytesDownloaded,
|
|
2819
|
-
actionsCompleted: report.actionsCompleted,
|
|
2820
|
-
actionsSkipped: report.actionsSkipped,
|
|
2821
|
-
durationMs: report.durationMs
|
|
2822
|
-
};
|
|
2823
|
-
}
|
|
2824
|
-
async function sha1OfFile(filePath) {
|
|
2825
|
-
const hash = crypto2.createHash("sha1");
|
|
2826
|
-
await new Promise((resolve, reject) => {
|
|
2827
|
-
const stream = createReadStream(filePath);
|
|
2828
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
2829
|
-
stream.on("end", resolve);
|
|
2830
|
-
stream.on("error", reject);
|
|
2831
|
-
});
|
|
2832
|
-
return hash.digest("hex");
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
// src/verify/helpers.ts
|
|
2836
|
-
async function runVerification(input, check) {
|
|
2837
|
-
const startedAt = Date.now();
|
|
2838
|
-
const results = [];
|
|
2839
|
-
const record = (result) => {
|
|
2840
|
-
results.push(result);
|
|
2841
|
-
input.onEvent?.({ type: "verify:file-checked", file: result });
|
|
2842
|
-
};
|
|
2843
|
-
await check(record);
|
|
2844
|
-
return {
|
|
2845
|
-
targetId: input.targetId,
|
|
2846
|
-
kind: input.kind,
|
|
2847
|
-
isValid: results.every((r) => r.status === VerifyFileStatuses.OK),
|
|
2848
|
-
issues: results.filter((r) => r.status !== VerifyFileStatuses.OK),
|
|
2849
|
-
checkedFiles: results.length,
|
|
2850
|
-
durationMs: Date.now() - startedAt
|
|
2851
|
-
};
|
|
2852
|
-
}
|
|
2853
|
-
async function verifyHashedFile(input) {
|
|
2854
|
-
if (!await fileExists(input.path)) {
|
|
2855
|
-
return {
|
|
2856
|
-
path: input.path,
|
|
2857
|
-
category: input.category,
|
|
2858
|
-
status: VerifyFileStatuses.MISSING,
|
|
2859
|
-
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2860
|
-
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2861
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2862
|
-
};
|
|
2863
|
-
}
|
|
2864
|
-
if (input.expectedSize !== void 0) {
|
|
2865
|
-
const size = await fileSize(input.path);
|
|
2866
|
-
if (size !== input.expectedSize) {
|
|
2867
|
-
return {
|
|
2868
|
-
path: input.path,
|
|
2869
|
-
category: input.category,
|
|
2870
|
-
status: VerifyFileStatuses.WRONG_SIZE,
|
|
2871
|
-
expectedSize: input.expectedSize,
|
|
2872
|
-
actualSize: size,
|
|
2873
|
-
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2874
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2875
|
-
};
|
|
2876
|
-
}
|
|
2877
|
-
}
|
|
2878
|
-
if (input.expectedSha1 !== void 0) {
|
|
2879
|
-
const actualSha1 = await sha1OfFile(input.path);
|
|
2880
|
-
if (actualSha1 !== input.expectedSha1) {
|
|
2881
|
-
return {
|
|
2882
|
-
path: input.path,
|
|
2883
|
-
category: input.category,
|
|
2884
|
-
status: VerifyFileStatuses.CORRUPT,
|
|
2885
|
-
expectedSha1: input.expectedSha1,
|
|
2886
|
-
actualSha1,
|
|
2887
|
-
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2888
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2889
|
-
};
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
return {
|
|
2893
|
-
path: input.path,
|
|
2894
|
-
category: input.category,
|
|
2895
|
-
status: VerifyFileStatuses.OK,
|
|
2896
|
-
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2897
|
-
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2898
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2899
|
-
};
|
|
2900
|
-
}
|
|
2901
|
-
async function verifyExistence(input) {
|
|
2902
|
-
if (await fileExists(input.path)) {
|
|
2903
|
-
return {
|
|
2904
|
-
path: input.path,
|
|
2905
|
-
category: input.category,
|
|
2906
|
-
status: VerifyFileStatuses.OK,
|
|
2907
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2908
|
-
};
|
|
2909
|
-
}
|
|
2910
|
-
return {
|
|
2911
|
-
path: input.path,
|
|
2912
|
-
category: input.category,
|
|
2913
|
-
status: VerifyFileStatuses.MISSING,
|
|
2914
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2915
|
-
};
|
|
2916
|
-
}
|
|
2917
|
-
async function findForgeVersionJsonPath(directory, minecraftVersion) {
|
|
2918
|
-
const versionsDir = targetPaths.versionsDir(directory);
|
|
2919
|
-
const dirs = await listChildDirectories(versionsDir);
|
|
2920
|
-
for (const id of dirs) {
|
|
2921
|
-
if (!id.startsWith(`${minecraftVersion}-forge-`)) continue;
|
|
2922
|
-
const jsonPath = targetPaths.versionJson(directory, id);
|
|
2923
|
-
if (!await fileExists(jsonPath)) {
|
|
2924
|
-
return jsonPath;
|
|
2925
|
-
}
|
|
2926
|
-
const parsed = await tryParseInheritsFrom(jsonPath);
|
|
2927
|
-
if (parsed === minecraftVersion) return jsonPath;
|
|
2928
|
-
}
|
|
2929
|
-
return null;
|
|
2930
|
-
}
|
|
2931
|
-
async function tryParseInheritsFrom(jsonPath) {
|
|
2932
|
-
try {
|
|
2933
|
-
const parsed = JSON.parse(await readText(jsonPath));
|
|
2934
|
-
return parsed.inheritsFrom;
|
|
2935
|
-
} catch {
|
|
2936
|
-
return void 0;
|
|
2937
|
-
}
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
|
-
// src/verify/fabric.ts
|
|
2941
|
-
async function verifyFabric(input) {
|
|
2942
|
-
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
2943
|
-
throw new MinecraftKitError(
|
|
2944
|
-
"INVALID_INPUT",
|
|
2945
|
-
`verify.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
2946
|
-
);
|
|
2947
|
-
}
|
|
2948
|
-
const loader = input.target.loader;
|
|
2949
|
-
return runVerification(
|
|
2950
|
-
{
|
|
2951
|
-
targetId: input.target.id,
|
|
2952
|
-
kind: VerificationKinds.FABRIC,
|
|
2953
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2954
|
-
},
|
|
2955
|
-
async (record) => {
|
|
2956
|
-
record(
|
|
2957
|
-
await verifyExistence({
|
|
2958
|
-
path: targetPaths.versionJson(input.target.directory, loader.profile.id),
|
|
2959
|
-
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2960
|
-
})
|
|
2961
|
-
);
|
|
2962
|
-
const fabricLibraries = planLibraryDownloads({
|
|
2963
|
-
libraries: loader.profile.libraries,
|
|
2964
|
-
directory: input.target.directory,
|
|
2965
|
-
system: input.target.runtime.system,
|
|
2966
|
-
versionId: input.target.minecraft.version,
|
|
2967
|
-
category: "fabric-library"
|
|
2968
|
-
});
|
|
2969
|
-
for (const action of fabricLibraries.downloads) {
|
|
2970
|
-
record(
|
|
2971
|
-
await verifyHashedFile({
|
|
2972
|
-
path: action.target,
|
|
2973
|
-
expectedSha1: action.expectedSha1,
|
|
2974
|
-
expectedSize: action.expectedSize,
|
|
2975
|
-
...action.url ? { url: action.url } : {},
|
|
2976
|
-
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2977
|
-
})
|
|
2978
|
-
);
|
|
2979
|
-
}
|
|
2980
|
-
}
|
|
2981
|
-
);
|
|
2982
|
-
}
|
|
2983
|
-
|
|
2984
|
-
// src/verify/forge.ts
|
|
2985
|
-
async function verifyForge(input) {
|
|
2986
|
-
if (input.target.loader.type !== Loaders.FORGE) {
|
|
2987
|
-
throw new MinecraftKitError(
|
|
2988
|
-
"INVALID_INPUT",
|
|
2989
|
-
`verify.forge requires a Forge target (got ${input.target.loader.type})`
|
|
2990
|
-
);
|
|
2991
|
-
}
|
|
2992
|
-
return runVerification(
|
|
2993
|
-
{
|
|
2994
|
-
targetId: input.target.id,
|
|
2995
|
-
kind: VerificationKinds.FORGE,
|
|
2996
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2997
|
-
},
|
|
2998
|
-
async (record) => {
|
|
2999
|
-
const forgeVersionJsonPath = await findForgeVersionJsonPath(
|
|
3000
|
-
input.target.directory,
|
|
3001
|
-
input.target.minecraft.version
|
|
3002
|
-
);
|
|
3003
|
-
if (forgeVersionJsonPath === null) return;
|
|
3004
|
-
record(
|
|
3005
|
-
await verifyExistence({
|
|
3006
|
-
path: forgeVersionJsonPath,
|
|
3007
|
-
category: VerifyFileCategories.LOADER_LIBRARY
|
|
3008
|
-
})
|
|
3009
|
-
);
|
|
3010
|
-
if (!await fileExists(forgeVersionJsonPath)) return;
|
|
3011
|
-
let parsed;
|
|
3012
|
-
try {
|
|
3013
|
-
parsed = JSON.parse(await readText(forgeVersionJsonPath));
|
|
3014
|
-
} catch {
|
|
3015
|
-
record({
|
|
3016
|
-
path: forgeVersionJsonPath,
|
|
3017
|
-
category: VerifyFileCategories.LOADER_LIBRARY,
|
|
3018
|
-
status: VerifyFileStatuses.CORRUPT
|
|
3019
|
-
});
|
|
3020
|
-
return;
|
|
3021
|
-
}
|
|
3022
|
-
const forgeLibraries = planLibraryDownloads({
|
|
3023
|
-
libraries: parsed.libraries,
|
|
3024
|
-
directory: input.target.directory,
|
|
3025
|
-
system: input.target.runtime.system,
|
|
3026
|
-
versionId: input.target.minecraft.version,
|
|
3027
|
-
category: "forge-library"
|
|
3028
|
-
});
|
|
3029
|
-
for (const action of forgeLibraries.downloads) {
|
|
3030
|
-
record(
|
|
3031
|
-
await verifyHashedFile({
|
|
3032
|
-
path: action.target,
|
|
3033
|
-
expectedSha1: action.expectedSha1,
|
|
3034
|
-
expectedSize: action.expectedSize,
|
|
3035
|
-
...action.url ? { url: action.url } : {},
|
|
3036
|
-
category: VerifyFileCategories.LOADER_LIBRARY
|
|
3037
|
-
})
|
|
3038
|
-
);
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
);
|
|
3042
|
-
}
|
|
3043
|
-
|
|
3044
|
-
// src/verify/minecraft.ts
|
|
3045
|
-
async function verifyMinecraft(input) {
|
|
3046
|
-
return runVerification(
|
|
3047
|
-
{
|
|
3048
|
-
targetId: input.target.id,
|
|
3049
|
-
kind: VerificationKinds.MINECRAFT,
|
|
3050
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
3051
|
-
},
|
|
3052
|
-
async (record) => {
|
|
3053
|
-
const { directory, minecraft, runtime } = input.target;
|
|
3054
|
-
record(
|
|
3055
|
-
await verifyHashedFile({
|
|
3056
|
-
path: targetPaths.versionJar(directory, minecraft.version),
|
|
3057
|
-
expectedSha1: minecraft.manifest.downloads.client.sha1,
|
|
3058
|
-
expectedSize: minecraft.manifest.downloads.client.size,
|
|
3059
|
-
url: minecraft.manifest.downloads.client.url,
|
|
3060
|
-
category: VerifyFileCategories.CLIENT_JAR
|
|
3061
|
-
})
|
|
3062
|
-
);
|
|
3063
|
-
record(
|
|
3064
|
-
await verifyExistence({
|
|
3065
|
-
path: targetPaths.versionJson(directory, minecraft.version),
|
|
3066
|
-
category: VerifyFileCategories.CLIENT_JAR
|
|
3067
|
-
})
|
|
3068
|
-
);
|
|
3069
|
-
if (minecraft.manifest.logging?.client) {
|
|
3070
|
-
const logging = minecraft.manifest.logging.client;
|
|
3071
|
-
record(
|
|
3072
|
-
await verifyHashedFile({
|
|
3073
|
-
path: targetPaths.loggingConfig(directory, logging.file.id),
|
|
3074
|
-
expectedSha1: logging.file.sha1,
|
|
3075
|
-
expectedSize: logging.file.size,
|
|
3076
|
-
url: logging.file.url,
|
|
3077
|
-
category: VerifyFileCategories.LOGGING_CONFIG
|
|
3078
|
-
})
|
|
3079
|
-
);
|
|
3080
|
-
}
|
|
3081
|
-
const libraryPlan = planLibraryDownloads({
|
|
3082
|
-
libraries: minecraft.manifest.libraries,
|
|
3083
|
-
directory,
|
|
3084
|
-
system: runtime.system,
|
|
3085
|
-
versionId: minecraft.version,
|
|
3086
|
-
category: "library"
|
|
3343
|
+
loader = {
|
|
3344
|
+
type: Loaders.VANILLA,
|
|
3345
|
+
minecraftVersion: minecraft.version,
|
|
3346
|
+
minecraft
|
|
3347
|
+
};
|
|
3348
|
+
} else if (input.loader.type === Loaders.FABRIC) {
|
|
3349
|
+
loader = await this.ctx.fabric.resolve({
|
|
3350
|
+
minecraftVersion: minecraft.version,
|
|
3351
|
+
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
3352
|
+
...input.loader.version !== void 0 ? { loaderVersion: input.loader.version } : {},
|
|
3353
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3087
3354
|
});
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
expectedSize: action.expectedSize,
|
|
3094
|
-
url: action.url,
|
|
3095
|
-
category: VerifyFileCategories.LIBRARY
|
|
3096
|
-
})
|
|
3097
|
-
);
|
|
3098
|
-
}
|
|
3099
|
-
const indexUrl = minecraft.manifest.assetIndex.url;
|
|
3100
|
-
const indexPath = targetPaths.assetIndex(directory, minecraft.manifest.assetIndex.id);
|
|
3101
|
-
record(
|
|
3102
|
-
await verifyHashedFile({
|
|
3103
|
-
path: indexPath,
|
|
3104
|
-
expectedSha1: minecraft.manifest.assetIndex.sha1,
|
|
3105
|
-
expectedSize: minecraft.manifest.assetIndex.size,
|
|
3106
|
-
url: indexUrl,
|
|
3107
|
-
category: VerifyFileCategories.ASSET_INDEX
|
|
3108
|
-
})
|
|
3109
|
-
);
|
|
3110
|
-
const indexDocument = await fetchJson(input.http, input.cache, {
|
|
3111
|
-
url: indexUrl,
|
|
3112
|
-
cacheKey: `asset-index:${minecraft.manifest.assetIndex.id}:${minecraft.manifest.assetIndex.sha1}`,
|
|
3355
|
+
} else {
|
|
3356
|
+
loader = await this.ctx.forge.resolve({
|
|
3357
|
+
minecraftVersion: minecraft.version,
|
|
3358
|
+
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
3359
|
+
...input.loader.version !== void 0 ? { forgeVersion: input.loader.version } : {},
|
|
3113
3360
|
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3114
3361
|
});
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3362
|
+
}
|
|
3363
|
+
return this.create({
|
|
3364
|
+
id: input.id,
|
|
3365
|
+
directory: input.directory,
|
|
3366
|
+
minecraft,
|
|
3367
|
+
loader,
|
|
3368
|
+
runtime
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
/** Scan a root directory for Minecraft installations. Returns only what is on disk. */
|
|
3372
|
+
async list(input) {
|
|
3373
|
+
if (!await dirExists(input.rootDir)) return [];
|
|
3374
|
+
const subdirs = await listChildDirectories(input.rootDir);
|
|
3375
|
+
const results = [];
|
|
3376
|
+
for (const id of subdirs) {
|
|
3377
|
+
const directory = path.join(input.rootDir, id);
|
|
3378
|
+
const discovered = await discoverInstallation(id, directory);
|
|
3379
|
+
if (discovered) results.push(discovered);
|
|
3380
|
+
}
|
|
3381
|
+
return results;
|
|
3382
|
+
}
|
|
3383
|
+
};
|
|
3384
|
+
async function discoverInstallation(id, directory) {
|
|
3385
|
+
const versionsDir = path.join(directory, VERSIONS_DIR);
|
|
3386
|
+
const librariesDir = path.join(directory, LIBRARIES_DIR);
|
|
3387
|
+
const assetsDir = path.join(directory, ASSETS_DIR);
|
|
3388
|
+
const looksLikeInstall = await dirExists(versionsDir) && (await dirExists(librariesDir) || await dirExists(assetsDir));
|
|
3389
|
+
if (!looksLikeInstall) return null;
|
|
3390
|
+
const versionDirs = await listChildDirectories(versionsDir);
|
|
3391
|
+
const minecraftVersions = [];
|
|
3392
|
+
const loaders = [];
|
|
3393
|
+
for (const versionId of versionDirs) {
|
|
3394
|
+
const hint = inferLoaderFromVersionId(versionId);
|
|
3395
|
+
if (hint) {
|
|
3396
|
+
loaders.push(hint);
|
|
3397
|
+
if (hint.minecraftVersion && !minecraftVersions.includes(hint.minecraftVersion)) {
|
|
3398
|
+
minecraftVersions.push(hint.minecraftVersion);
|
|
3137
3399
|
}
|
|
3400
|
+
} else {
|
|
3401
|
+
minecraftVersions.push(versionId);
|
|
3138
3402
|
}
|
|
3139
|
-
|
|
3403
|
+
}
|
|
3404
|
+
const runtime = await discoverRuntime(directory);
|
|
3405
|
+
return { id, directory, minecraftVersions, loaders, ...runtime ? { runtime } : {} };
|
|
3140
3406
|
}
|
|
3141
|
-
async function
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
});
|
|
3156
|
-
} catch {
|
|
3157
|
-
record({
|
|
3158
|
-
path: input.target.runtime.manifestUrl,
|
|
3159
|
-
category: VerifyFileCategories.RUNTIME_FILE,
|
|
3160
|
-
status: VerifyFileStatuses.MISSING
|
|
3161
|
-
});
|
|
3162
|
-
return;
|
|
3163
|
-
}
|
|
3164
|
-
const runtimeRoot = targetPaths.runtimeRoot(
|
|
3165
|
-
input.target.directory,
|
|
3166
|
-
input.target.runtime.component,
|
|
3167
|
-
input.target.runtime.installRoot
|
|
3168
|
-
);
|
|
3169
|
-
for (const [relative, entry] of Object.entries(manifest.files)) {
|
|
3170
|
-
if (entry.type !== "file") continue;
|
|
3171
|
-
record(
|
|
3172
|
-
await verifyHashedFile({
|
|
3173
|
-
path: path.join(runtimeRoot, relative),
|
|
3174
|
-
expectedSha1: entry.downloads.raw.sha1,
|
|
3175
|
-
expectedSize: entry.downloads.raw.size,
|
|
3176
|
-
url: entry.downloads.raw.url,
|
|
3177
|
-
category: VerifyFileCategories.RUNTIME_FILE
|
|
3178
|
-
})
|
|
3179
|
-
);
|
|
3180
|
-
}
|
|
3407
|
+
async function discoverRuntime(directory) {
|
|
3408
|
+
const runtimeDir = path.join(directory, RUNTIMES_DIR);
|
|
3409
|
+
if (!await dirExists(runtimeDir)) return void 0;
|
|
3410
|
+
let components;
|
|
3411
|
+
try {
|
|
3412
|
+
components = await listChildDirectories(runtimeDir);
|
|
3413
|
+
} catch {
|
|
3414
|
+
return void 0;
|
|
3415
|
+
}
|
|
3416
|
+
for (const component of components) {
|
|
3417
|
+
const root = path.join(runtimeDir, component);
|
|
3418
|
+
const javaPath = process.platform === "win32" ? path.join(root, "bin", "javaw.exe") : process.platform === "darwin" ? path.join(root, "jre.bundle", "Contents", "Home", "bin", "java") : path.join(root, "bin", "java");
|
|
3419
|
+
if (await fileExists(javaPath)) {
|
|
3420
|
+
return { component, javaPath };
|
|
3181
3421
|
}
|
|
3182
|
-
|
|
3422
|
+
}
|
|
3423
|
+
return void 0;
|
|
3424
|
+
}
|
|
3425
|
+
function inferLoaderFromVersionId(versionId) {
|
|
3426
|
+
const fabricMatch = /^fabric-loader-([^-]+)-(.+)$/.exec(versionId);
|
|
3427
|
+
if (fabricMatch?.[1] && fabricMatch[2]) {
|
|
3428
|
+
return { type: Loaders.FABRIC, version: fabricMatch[1], minecraftVersion: fabricMatch[2] };
|
|
3429
|
+
}
|
|
3430
|
+
const forgeMatch = /^([^-]+)-forge-(.+)$/.exec(versionId);
|
|
3431
|
+
if (forgeMatch?.[1] && forgeMatch[2]) {
|
|
3432
|
+
return { type: Loaders.FORGE, minecraftVersion: forgeMatch[1], version: forgeMatch[2] };
|
|
3433
|
+
}
|
|
3434
|
+
return null;
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
// src/update/runner.ts
|
|
3438
|
+
async function planUpdate(input) {
|
|
3439
|
+
return planInstall({
|
|
3440
|
+
target: input.target,
|
|
3441
|
+
http: input.http,
|
|
3442
|
+
cache: input.cache,
|
|
3443
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
async function runUpdate(input) {
|
|
3447
|
+
const report = await runInstall({
|
|
3448
|
+
plan: input.plan,
|
|
3449
|
+
http: input.http,
|
|
3450
|
+
cache: input.cache,
|
|
3451
|
+
spawner: input.spawner,
|
|
3452
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
3453
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
3454
|
+
});
|
|
3455
|
+
return {
|
|
3456
|
+
targetId: report.targetId,
|
|
3457
|
+
bytesDownloaded: report.bytesDownloaded,
|
|
3458
|
+
actionsCompleted: report.actionsCompleted,
|
|
3459
|
+
actionsSkipped: report.actionsSkipped,
|
|
3460
|
+
durationMs: report.durationMs
|
|
3461
|
+
};
|
|
3183
3462
|
}
|
|
3184
3463
|
|
|
3185
3464
|
// src/versions/fabric.ts
|
|
@@ -3513,15 +3792,23 @@ function pickLatestAcrossComponents(entries) {
|
|
|
3513
3792
|
return { component: bestComponent, entry: bestEntry };
|
|
3514
3793
|
}
|
|
3515
3794
|
function toResolved(component, platformKey, entry, system) {
|
|
3795
|
+
const majorVersion = parseMajorVersion(entry.version.name);
|
|
3516
3796
|
return {
|
|
3517
3797
|
component,
|
|
3518
3798
|
platformKey,
|
|
3519
3799
|
versionName: entry.version.name,
|
|
3800
|
+
...majorVersion !== void 0 ? { majorVersion } : {},
|
|
3520
3801
|
system,
|
|
3521
3802
|
manifestUrl: entry.manifest.url,
|
|
3522
3803
|
manifestSha1: entry.manifest.sha1
|
|
3523
3804
|
};
|
|
3524
3805
|
}
|
|
3806
|
+
function parseMajorVersion(versionName) {
|
|
3807
|
+
const match = /^(\d+)/.exec(versionName);
|
|
3808
|
+
if (!match || !match[1]) return void 0;
|
|
3809
|
+
const parsed = Number.parseInt(match[1], 10);
|
|
3810
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3811
|
+
}
|
|
3525
3812
|
|
|
3526
3813
|
// src/kit.ts
|
|
3527
3814
|
var MinecraftKit = class {
|
|
@@ -3552,7 +3839,12 @@ var MinecraftKit = class {
|
|
|
3552
3839
|
...opts?.signal !== void 0 ? { signal: opts.signal } : {},
|
|
3553
3840
|
...opts?.onEvent !== void 0 ? { onEvent: opts.onEvent } : {}
|
|
3554
3841
|
});
|
|
3555
|
-
const
|
|
3842
|
+
const carryInstall = (opts) => ({
|
|
3843
|
+
...carry(opts),
|
|
3844
|
+
...opts?.pauseController !== void 0 ? { pauseController: opts.pauseController } : {},
|
|
3845
|
+
...opts?.actionCategories !== void 0 ? { actionCategories: opts.actionCategories } : {}
|
|
3846
|
+
});
|
|
3847
|
+
const runInstallPlan = (plan, opts) => runInstall({ plan, http, cache, spawner, ...carryInstall(opts) });
|
|
3556
3848
|
this.install = {
|
|
3557
3849
|
plan: (target, opts) => planInstall({ target, http, cache, ...carry(opts) }),
|
|
3558
3850
|
run: runInstallPlan,
|
|
@@ -3602,10 +3894,17 @@ var MinecraftKit = class {
|
|
|
3602
3894
|
runtime: {
|
|
3603
3895
|
plan: (target, opts) => planRuntimeRepair(repairArgs(target, opts)),
|
|
3604
3896
|
run: runRepairPlan
|
|
3605
|
-
}
|
|
3897
|
+
},
|
|
3898
|
+
all: (target, opts) => repairAll({
|
|
3899
|
+
target,
|
|
3900
|
+
http,
|
|
3901
|
+
cache,
|
|
3902
|
+
spawner,
|
|
3903
|
+
...carry(opts)
|
|
3904
|
+
})
|
|
3606
3905
|
};
|
|
3607
3906
|
this.launch = {
|
|
3608
|
-
compose: (target, opts) => composeLaunch({ target, options: opts }),
|
|
3907
|
+
compose: (target, opts) => composeLaunch({ target, options: opts, logger }),
|
|
3609
3908
|
run: (composition, opts) => runLaunch({
|
|
3610
3909
|
composition,
|
|
3611
3910
|
...opts !== void 0 ? { options: opts } : {},
|
|
@@ -3615,6 +3914,247 @@ var MinecraftKit = class {
|
|
|
3615
3914
|
}
|
|
3616
3915
|
};
|
|
3617
3916
|
|
|
3917
|
+
// src/core/pause-controller.ts
|
|
3918
|
+
var PauseController = class {
|
|
3919
|
+
#paused = false;
|
|
3920
|
+
#waiters = [];
|
|
3921
|
+
get paused() {
|
|
3922
|
+
return this.#paused;
|
|
3923
|
+
}
|
|
3924
|
+
pause() {
|
|
3925
|
+
this.#paused = true;
|
|
3926
|
+
}
|
|
3927
|
+
resume() {
|
|
3928
|
+
this.#paused = false;
|
|
3929
|
+
const list = this.#waiters;
|
|
3930
|
+
this.#waiters = [];
|
|
3931
|
+
for (const resolve of list) resolve();
|
|
3932
|
+
}
|
|
3933
|
+
waitWhilePaused() {
|
|
3934
|
+
if (!this.#paused) return Promise.resolve();
|
|
3935
|
+
return new Promise((resolve) => this.#waiters.push(resolve));
|
|
3936
|
+
}
|
|
3937
|
+
};
|
|
3938
|
+
|
|
3939
|
+
// src/install/progress-tracker.ts
|
|
3940
|
+
var InstallStages = {
|
|
3941
|
+
PREPARE: "prepare",
|
|
3942
|
+
RUNTIME: "runtime",
|
|
3943
|
+
MINECRAFT: "minecraft",
|
|
3944
|
+
LOADER: "loader",
|
|
3945
|
+
FINALIZE: "finalize"
|
|
3946
|
+
};
|
|
3947
|
+
var STAGE_FOR_CATEGORY = {
|
|
3948
|
+
"runtime-file": InstallStages.RUNTIME,
|
|
3949
|
+
"client-jar": InstallStages.MINECRAFT,
|
|
3950
|
+
library: InstallStages.MINECRAFT,
|
|
3951
|
+
"asset-index": InstallStages.MINECRAFT,
|
|
3952
|
+
asset: InstallStages.MINECRAFT,
|
|
3953
|
+
"logging-config": InstallStages.MINECRAFT,
|
|
3954
|
+
"fabric-library": InstallStages.LOADER,
|
|
3955
|
+
"forge-library": InstallStages.LOADER,
|
|
3956
|
+
"forge-installer": InstallStages.LOADER
|
|
3957
|
+
};
|
|
3958
|
+
var STAGE_FOR_PHASE = {
|
|
3959
|
+
[InstallPhases.PLANNING]: InstallStages.PREPARE,
|
|
3960
|
+
[InstallPhases.DOWNLOADING_VERSION_MANIFEST]: InstallStages.PREPARE,
|
|
3961
|
+
[InstallPhases.INSTALLING_RUNTIME]: InstallStages.RUNTIME,
|
|
3962
|
+
[InstallPhases.DOWNLOADING_CLIENT_JAR]: InstallStages.MINECRAFT,
|
|
3963
|
+
[InstallPhases.DOWNLOADING_LIBRARIES]: InstallStages.MINECRAFT,
|
|
3964
|
+
[InstallPhases.DOWNLOADING_ASSET_INDEX]: InstallStages.MINECRAFT,
|
|
3965
|
+
[InstallPhases.DOWNLOADING_ASSETS]: InstallStages.MINECRAFT,
|
|
3966
|
+
[InstallPhases.EXTRACTING_NATIVES]: InstallStages.MINECRAFT,
|
|
3967
|
+
[InstallPhases.WRITING_FILES]: InstallStages.MINECRAFT,
|
|
3968
|
+
[InstallPhases.INSTALLING_FABRIC]: InstallStages.LOADER,
|
|
3969
|
+
[InstallPhases.INSTALLING_FORGE]: InstallStages.LOADER,
|
|
3970
|
+
[InstallPhases.RUNNING_FORGE_PROCESSORS]: InstallStages.LOADER,
|
|
3971
|
+
[InstallPhases.COMPLETED]: InstallStages.FINALIZE
|
|
3972
|
+
};
|
|
3973
|
+
var ALL_STAGES = [
|
|
3974
|
+
InstallStages.PREPARE,
|
|
3975
|
+
InstallStages.RUNTIME,
|
|
3976
|
+
InstallStages.MINECRAFT,
|
|
3977
|
+
InstallStages.LOADER,
|
|
3978
|
+
InstallStages.FINALIZE
|
|
3979
|
+
];
|
|
3980
|
+
function createInstallProgressTracker(plan, options = {}) {
|
|
3981
|
+
const throttleMs = options.throttleMs ?? 100;
|
|
3982
|
+
const stageOfTarget = /* @__PURE__ */ new Map();
|
|
3983
|
+
const expectedSizeOf = /* @__PURE__ */ new Map();
|
|
3984
|
+
const stageTotals = {
|
|
3985
|
+
prepare: 0,
|
|
3986
|
+
runtime: 0,
|
|
3987
|
+
minecraft: 0,
|
|
3988
|
+
loader: 0,
|
|
3989
|
+
finalize: 0
|
|
3990
|
+
};
|
|
3991
|
+
let overallTotal = 0;
|
|
3992
|
+
for (const action of plan.actions) {
|
|
3993
|
+
if (action.kind !== "download-file") continue;
|
|
3994
|
+
const stage = STAGE_FOR_CATEGORY[action.category] ?? InstallStages.MINECRAFT;
|
|
3995
|
+
stageOfTarget.set(action.target, stage);
|
|
3996
|
+
const size = action.expectedSize ?? 0;
|
|
3997
|
+
expectedSizeOf.set(action.target, size);
|
|
3998
|
+
stageTotals[stage] += size;
|
|
3999
|
+
overallTotal += size;
|
|
4000
|
+
}
|
|
4001
|
+
const stageDone = {
|
|
4002
|
+
prepare: 0,
|
|
4003
|
+
runtime: 0,
|
|
4004
|
+
minecraft: 0,
|
|
4005
|
+
loader: 0,
|
|
4006
|
+
finalize: 0
|
|
4007
|
+
};
|
|
4008
|
+
const stageInFlight = {
|
|
4009
|
+
prepare: 0,
|
|
4010
|
+
runtime: 0,
|
|
4011
|
+
minecraft: 0,
|
|
4012
|
+
loader: 0,
|
|
4013
|
+
finalize: 0
|
|
4014
|
+
};
|
|
4015
|
+
let totalDone = 0;
|
|
4016
|
+
let totalInFlight = 0;
|
|
4017
|
+
const inFlightByTarget = /* @__PURE__ */ new Map();
|
|
4018
|
+
let currentStage = InstallStages.PREPARE;
|
|
4019
|
+
let currentFile;
|
|
4020
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4021
|
+
let lastPushAt = 0;
|
|
4022
|
+
let pending = false;
|
|
4023
|
+
let pendingTimer = null;
|
|
4024
|
+
let finished = false;
|
|
4025
|
+
const snapshot = () => {
|
|
4026
|
+
const stageTotal = stageTotals[currentStage];
|
|
4027
|
+
const stageBytes = stageDone[currentStage] + stageInFlight[currentStage];
|
|
4028
|
+
const overallBytes = totalDone + totalInFlight;
|
|
4029
|
+
return {
|
|
4030
|
+
stage: currentStage,
|
|
4031
|
+
stagePercent: stageTotal > 0 ? clamp(stageBytes / stageTotal * 100) : 0,
|
|
4032
|
+
overallPercent: overallTotal > 0 ? clamp(overallBytes / overallTotal * 100) : 0,
|
|
4033
|
+
bytesDownloaded: overallBytes,
|
|
4034
|
+
totalBytes: stageTotal,
|
|
4035
|
+
...currentFile !== void 0 ? { currentFile } : {}
|
|
4036
|
+
};
|
|
4037
|
+
};
|
|
4038
|
+
const clearTimer = () => {
|
|
4039
|
+
if (pendingTimer) {
|
|
4040
|
+
clearTimeout(pendingTimer);
|
|
4041
|
+
pendingTimer = null;
|
|
4042
|
+
}
|
|
4043
|
+
};
|
|
4044
|
+
const push = () => {
|
|
4045
|
+
pending = false;
|
|
4046
|
+
clearTimer();
|
|
4047
|
+
lastPushAt = Date.now();
|
|
4048
|
+
const snap = snapshot();
|
|
4049
|
+
for (const listener of listeners) listener(snap);
|
|
4050
|
+
};
|
|
4051
|
+
const schedulePush = () => {
|
|
4052
|
+
if (finished) return;
|
|
4053
|
+
const elapsed = Date.now() - lastPushAt;
|
|
4054
|
+
if (elapsed >= throttleMs) {
|
|
4055
|
+
push();
|
|
4056
|
+
return;
|
|
4057
|
+
}
|
|
4058
|
+
if (pending) return;
|
|
4059
|
+
pending = true;
|
|
4060
|
+
pendingTimer = setTimeout(push, throttleMs - elapsed);
|
|
4061
|
+
};
|
|
4062
|
+
const onEvent = (event) => {
|
|
4063
|
+
switch (event.type) {
|
|
4064
|
+
case "install:phase-changed": {
|
|
4065
|
+
const next = STAGE_FOR_PHASE[event.phase];
|
|
4066
|
+
if (next && next !== currentStage) {
|
|
4067
|
+
currentStage = next;
|
|
4068
|
+
currentFile = void 0;
|
|
4069
|
+
push();
|
|
4070
|
+
}
|
|
4071
|
+
return;
|
|
4072
|
+
}
|
|
4073
|
+
case "download:started": {
|
|
4074
|
+
const stage = stageOfTarget.get(event.file.target) ?? currentStage;
|
|
4075
|
+
inFlightByTarget.set(event.file.target, { stage, bytes: 0 });
|
|
4076
|
+
currentFile = event.file.target;
|
|
4077
|
+
schedulePush();
|
|
4078
|
+
return;
|
|
4079
|
+
}
|
|
4080
|
+
case "download:progress": {
|
|
4081
|
+
const entry = inFlightByTarget.get(event.file.target);
|
|
4082
|
+
if (entry) {
|
|
4083
|
+
const delta = event.bytesDownloaded - entry.bytes;
|
|
4084
|
+
if (delta !== 0) {
|
|
4085
|
+
entry.bytes = event.bytesDownloaded;
|
|
4086
|
+
stageInFlight[entry.stage] += delta;
|
|
4087
|
+
totalInFlight += delta;
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
currentFile = event.file.target;
|
|
4091
|
+
schedulePush();
|
|
4092
|
+
return;
|
|
4093
|
+
}
|
|
4094
|
+
case "download:skipped": {
|
|
4095
|
+
const stage = stageOfTarget.get(event.file.target);
|
|
4096
|
+
if (stage) {
|
|
4097
|
+
const size = expectedSizeOf.get(event.file.target) ?? 0;
|
|
4098
|
+
stageDone[stage] += size;
|
|
4099
|
+
totalDone += size;
|
|
4100
|
+
schedulePush();
|
|
4101
|
+
}
|
|
4102
|
+
return;
|
|
4103
|
+
}
|
|
4104
|
+
case "download:completed": {
|
|
4105
|
+
const entry = inFlightByTarget.get(event.file.target);
|
|
4106
|
+
if (entry) {
|
|
4107
|
+
const finalBytes = event.bytes ?? entry.bytes;
|
|
4108
|
+
stageInFlight[entry.stage] -= entry.bytes;
|
|
4109
|
+
totalInFlight -= entry.bytes;
|
|
4110
|
+
stageDone[entry.stage] += finalBytes;
|
|
4111
|
+
totalDone += finalBytes;
|
|
4112
|
+
inFlightByTarget.delete(event.file.target);
|
|
4113
|
+
} else {
|
|
4114
|
+
const stage = stageOfTarget.get(event.file.target);
|
|
4115
|
+
if (stage) {
|
|
4116
|
+
const bytes = event.bytes ?? expectedSizeOf.get(event.file.target) ?? 0;
|
|
4117
|
+
stageDone[stage] += bytes;
|
|
4118
|
+
totalDone += bytes;
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
schedulePush();
|
|
4122
|
+
return;
|
|
4123
|
+
}
|
|
4124
|
+
default:
|
|
4125
|
+
return;
|
|
4126
|
+
}
|
|
4127
|
+
};
|
|
4128
|
+
return {
|
|
4129
|
+
onEvent,
|
|
4130
|
+
snapshot,
|
|
4131
|
+
subscribe(listener) {
|
|
4132
|
+
listeners.add(listener);
|
|
4133
|
+
listener(snapshot());
|
|
4134
|
+
return () => listeners.delete(listener);
|
|
4135
|
+
},
|
|
4136
|
+
finish() {
|
|
4137
|
+
finished = true;
|
|
4138
|
+
clearTimer();
|
|
4139
|
+
currentStage = InstallStages.FINALIZE;
|
|
4140
|
+
currentFile = void 0;
|
|
4141
|
+
totalDone = overallTotal;
|
|
4142
|
+
totalInFlight = 0;
|
|
4143
|
+
for (const stage of ALL_STAGES) {
|
|
4144
|
+
stageDone[stage] = stageTotals[stage];
|
|
4145
|
+
stageInFlight[stage] = 0;
|
|
4146
|
+
}
|
|
4147
|
+
const snap = snapshot();
|
|
4148
|
+
for (const listener of listeners) listener(snap);
|
|
4149
|
+
}
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
4152
|
+
function clamp(value) {
|
|
4153
|
+
if (value <= 0) return 0;
|
|
4154
|
+
if (value >= 100) return 100;
|
|
4155
|
+
return value;
|
|
4156
|
+
}
|
|
4157
|
+
|
|
3618
4158
|
// src/types/events.ts
|
|
3619
4159
|
var EventTypes = {
|
|
3620
4160
|
INSTALL_PHASE_CHANGED: "install:phase-changed",
|
|
@@ -3699,6 +4239,6 @@ var LAUNCH_PLACEHOLDERS = {
|
|
|
3699
4239
|
"${path}": "Path to the log4j config file (logging.client.argument only)."
|
|
3700
4240
|
};
|
|
3701
4241
|
|
|
3702
|
-
export { ASSETS_DIR, ASSETS_INDEXES_DIR, ASSETS_LEGACY_DIR, ASSETS_LOG_CONFIGS_DIR, ASSETS_OBJECTS_DIR, ASSETS_RESOURCES_DIR, ASSETS_VIRTUAL_DIR, ApiEndpoints, Architectures, AuthModes, BASE_JVM_ARGS, CACHE_MAX_ENTRIES, CACHE_TTL_MS, ChildProcessSpawner, DEFAULT_KILL_GRACE_MS, DEFAULT_LAUNCHER_NAME, DEFAULT_LAUNCHER_VERSION, DEFAULT_LIBRARY_REPOSITORY, DEFAULT_MAX_MB, DEFAULT_MIN_MB, DOWNLOAD_CONCURRENCY, EXTRACTION_MAX_COMPRESSION_RATIO, EXTRACTION_MAX_ENTRY_COUNT, EXTRACTION_MAX_FILE_SIZE, EXTRACTION_MAX_TOTAL_SIZE, EventTypes, FABRIC_MAVEN_BASE, FALLBACK_COMPONENT, FORGE_INSTALLERS_DIR, FORGE_INSTALLER_MAX_SIZE, FORGE_MAVEN_BASE, FabricVersionsApi, FetchHttpClient, ForgeVersionsApi, HTTP_RETRY_BACKOFF_BASE_MS, HTTP_RETRY_BACKOFF_CAP_MS, HTTP_RETRY_MAX, HTTP_TIMEOUT_MS, InstallActionKinds, InstallPhases, JAVA_EXECUTABLE, LAUNCH_PLACEHOLDERS, LEGACY_JVM_ARGS, LIBRARIES_DIR, Loaders, LogLevels, MACOS_JVM_ARGS, MAC_RUNTIME_PREFIX, MAX_PROCESSOR_STDERR_LINES, MinecraftChannels, MinecraftKit, MinecraftKitError, MinecraftVersionsApi, NATIVES_DIR_NAME, NODE_ARCH_TO_MOJANG_ARCH, NODE_PLATFORM_TO_MOJANG_OS, OperatingSystems, PROGRESS_EVENT_INTERVAL_MS, RUNTIMES_DIR, RUNTIME_PLATFORM_KEYS, RepairPhases, RuntimeComponents, RuntimePreference, RuntimeVersionsApi, SPAWNER_MAX_LINE_BYTES, TargetsApi, USER_AGENT, VERSIONS_DIR, VerificationKinds, VerifyFileCategories, VerifyFileStatuses, VersionPreference, consoleLogger, createMemoryCache, detectSystem, isErrorCode, isMinecraftKitError, offlineUuidFor, planFabricRepair, planForgeRepair, planMinecraftRepair, planRuntimeInstall, planRuntimeRepair, planStandaloneRuntimeInstall, runRepair, silentLogger, stripUuidDashes, verifyFabric, verifyForge, verifyMinecraft, verifyRuntime };
|
|
4242
|
+
export { ASSETS_DIR, ASSETS_INDEXES_DIR, ASSETS_LEGACY_DIR, ASSETS_LOG_CONFIGS_DIR, ASSETS_OBJECTS_DIR, ASSETS_RESOURCES_DIR, ASSETS_VIRTUAL_DIR, ApiEndpoints, Architectures, AuthModes, BASE_JVM_ARGS, CACHE_MAX_ENTRIES, CACHE_TTL_MS, ChildProcessSpawner, DEFAULT_KILL_GRACE_MS, DEFAULT_LAUNCHER_NAME, DEFAULT_LAUNCHER_VERSION, DEFAULT_LIBRARY_REPOSITORY, DEFAULT_MAX_MB, DEFAULT_MIN_MB, DOWNLOAD_CONCURRENCY, EXTRACTION_MAX_COMPRESSION_RATIO, EXTRACTION_MAX_ENTRY_COUNT, EXTRACTION_MAX_FILE_SIZE, EXTRACTION_MAX_TOTAL_SIZE, EventTypes, FABRIC_MAVEN_BASE, FALLBACK_COMPONENT, FORGE_INSTALLERS_DIR, FORGE_INSTALLER_MAX_SIZE, FORGE_MAVEN_BASE, FabricVersionsApi, FetchHttpClient, ForgeVersionsApi, HTTP_RETRY_BACKOFF_BASE_MS, HTTP_RETRY_BACKOFF_CAP_MS, HTTP_RETRY_MAX, HTTP_TIMEOUT_MS, InstallActionKinds, InstallPhases, InstallStages, JAVA_EXECUTABLE, LAUNCH_PLACEHOLDERS, LEGACY_JVM_ARGS, LIBRARIES_DIR, Loaders, LogLevels, MACOS_JVM_ARGS, MAC_RUNTIME_PREFIX, MAX_PROCESSOR_STDERR_LINES, MinecraftChannels, MinecraftKit, MinecraftKitError, MinecraftVersionsApi, NATIVES_DIR_NAME, NODE_ARCH_TO_MOJANG_ARCH, NODE_PLATFORM_TO_MOJANG_OS, OperatingSystems, PROGRESS_EVENT_INTERVAL_MS, PauseController, RUNTIMES_DIR, RUNTIME_PLATFORM_KEYS, RepairPhases, RuntimeComponents, RuntimePreference, RuntimeVersionsApi, SPAWNER_MAX_LINE_BYTES, TargetsApi, USER_AGENT, VERSIONS_DIR, VerificationKinds, VerifyFileCategories, VerifyFileStatuses, VersionPreference, consoleLogger, createInstallProgressTracker, createMemoryCache, detectSystem, isErrorCode, isMinecraftKitError, offlineUuidFor, parseMajorVersion, pickClientJarVersionId, planFabricRepair, planForgeRepair, planMinecraftRepair, planRuntimeInstall, planRuntimeRepair, planStandaloneRuntimeInstall, repairAll, resolveLaunchVersion, runRepair, silentLogger, stripUuidDashes, targetPaths, verifyFabric, verifyForge, verifyMinecraft, verifyRuntime };
|
|
3703
4243
|
//# sourceMappingURL=index.mjs.map
|
|
3704
4244
|
//# sourceMappingURL=index.mjs.map
|