@loontail/minecraft-kit 0.4.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 +5340 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +2 -0
- package/dist/cli/{index.js → index.mjs} +1019 -715
- package/dist/cli/index.mjs.map +1 -0
- package/dist/index.cjs +4351 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1835 -0
- package/dist/index.d.ts +341 -220
- package/dist/{index.js → index.mjs} +1204 -664
- package/dist/index.mjs.map +1 -0
- package/package.json +8 -5
- package/dist/cli/index.js.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -9,7 +9,7 @@ import yauzl from 'yauzl';
|
|
|
9
9
|
import crypto2 from 'crypto';
|
|
10
10
|
import fs from 'fs/promises';
|
|
11
11
|
import { Readable } from 'stream';
|
|
12
|
-
import
|
|
12
|
+
import { AsyncResource } from 'async_hooks';
|
|
13
13
|
import { Buffer as Buffer$1 } from 'buffer';
|
|
14
14
|
import { spawn } from 'child_process';
|
|
15
15
|
|
|
@@ -277,9 +277,14 @@ var targetPaths = {
|
|
|
277
277
|
// src/types/install.ts
|
|
278
278
|
var InstallPhases = {
|
|
279
279
|
PLANNING: "planning",
|
|
280
|
+
DOWNLOADING_CLIENT_JAR: "downloading-client-jar",
|
|
280
281
|
DOWNLOADING_LIBRARIES: "downloading-libraries",
|
|
282
|
+
DOWNLOADING_ASSET_INDEX: "downloading-asset-index",
|
|
283
|
+
DOWNLOADING_ASSETS: "downloading-assets",
|
|
281
284
|
EXTRACTING_NATIVES: "extracting-natives",
|
|
282
285
|
INSTALLING_RUNTIME: "installing-runtime",
|
|
286
|
+
INSTALLING_FABRIC: "installing-fabric",
|
|
287
|
+
INSTALLING_FORGE: "installing-forge",
|
|
283
288
|
RUNNING_FORGE_PROCESSORS: "running-forge-processors",
|
|
284
289
|
WRITING_FILES: "writing-files",
|
|
285
290
|
COMPLETED: "completed"
|
|
@@ -1076,6 +1081,12 @@ async function downloadFile(http, input) {
|
|
|
1076
1081
|
const sourceIterable = response.stream();
|
|
1077
1082
|
const counting = (async function* () {
|
|
1078
1083
|
for await (const chunk of sourceIterable) {
|
|
1084
|
+
if (input.pauseController?.paused) {
|
|
1085
|
+
await input.pauseController.waitWhilePaused();
|
|
1086
|
+
}
|
|
1087
|
+
if (input.signal?.aborted) {
|
|
1088
|
+
throw new MinecraftKitError("LAUNCH_ABORTED", "Download aborted by signal");
|
|
1089
|
+
}
|
|
1079
1090
|
bytesDownloaded += chunk.byteLength;
|
|
1080
1091
|
hash.update(chunk);
|
|
1081
1092
|
input.onEvent?.({
|
|
@@ -1404,7 +1415,8 @@ function substituteToken(raw, tokens) {
|
|
|
1404
1415
|
});
|
|
1405
1416
|
}
|
|
1406
1417
|
function stripLiteralPrefix(value) {
|
|
1407
|
-
|
|
1418
|
+
const stripped = value.startsWith("'") ? value.slice(1) : value;
|
|
1419
|
+
return stripped.endsWith("'") ? stripped.slice(0, -1) : stripped;
|
|
1408
1420
|
}
|
|
1409
1421
|
async function planRuntimeDownloads(input) {
|
|
1410
1422
|
const manifest = await fetchJson(input.http, input.cache, {
|
|
@@ -1527,6 +1539,123 @@ async function planInstall(input) {
|
|
|
1527
1539
|
totalBytes
|
|
1528
1540
|
};
|
|
1529
1541
|
}
|
|
1542
|
+
|
|
1543
|
+
// node_modules/yocto-queue/index.js
|
|
1544
|
+
var Node = class {
|
|
1545
|
+
value;
|
|
1546
|
+
next;
|
|
1547
|
+
constructor(value) {
|
|
1548
|
+
this.value = value;
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
var Queue = class {
|
|
1552
|
+
#head;
|
|
1553
|
+
#tail;
|
|
1554
|
+
#size;
|
|
1555
|
+
constructor() {
|
|
1556
|
+
this.clear();
|
|
1557
|
+
}
|
|
1558
|
+
enqueue(value) {
|
|
1559
|
+
const node = new Node(value);
|
|
1560
|
+
if (this.#head) {
|
|
1561
|
+
this.#tail.next = node;
|
|
1562
|
+
this.#tail = node;
|
|
1563
|
+
} else {
|
|
1564
|
+
this.#head = node;
|
|
1565
|
+
this.#tail = node;
|
|
1566
|
+
}
|
|
1567
|
+
this.#size++;
|
|
1568
|
+
}
|
|
1569
|
+
dequeue() {
|
|
1570
|
+
const current = this.#head;
|
|
1571
|
+
if (!current) {
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
this.#head = this.#head.next;
|
|
1575
|
+
this.#size--;
|
|
1576
|
+
if (!this.#head) {
|
|
1577
|
+
this.#tail = void 0;
|
|
1578
|
+
}
|
|
1579
|
+
return current.value;
|
|
1580
|
+
}
|
|
1581
|
+
peek() {
|
|
1582
|
+
if (!this.#head) {
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
return this.#head.value;
|
|
1586
|
+
}
|
|
1587
|
+
clear() {
|
|
1588
|
+
this.#head = void 0;
|
|
1589
|
+
this.#tail = void 0;
|
|
1590
|
+
this.#size = 0;
|
|
1591
|
+
}
|
|
1592
|
+
get size() {
|
|
1593
|
+
return this.#size;
|
|
1594
|
+
}
|
|
1595
|
+
*[Symbol.iterator]() {
|
|
1596
|
+
let current = this.#head;
|
|
1597
|
+
while (current) {
|
|
1598
|
+
yield current.value;
|
|
1599
|
+
current = current.next;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
*drain() {
|
|
1603
|
+
while (this.#head) {
|
|
1604
|
+
yield this.dequeue();
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
function pLimit(concurrency) {
|
|
1609
|
+
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
|
1610
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
1611
|
+
}
|
|
1612
|
+
const queue = new Queue();
|
|
1613
|
+
let activeCount = 0;
|
|
1614
|
+
const next = () => {
|
|
1615
|
+
activeCount--;
|
|
1616
|
+
if (queue.size > 0) {
|
|
1617
|
+
queue.dequeue()();
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
const run = async (function_, resolve, arguments_) => {
|
|
1621
|
+
activeCount++;
|
|
1622
|
+
const result = (async () => function_(...arguments_))();
|
|
1623
|
+
resolve(result);
|
|
1624
|
+
try {
|
|
1625
|
+
await result;
|
|
1626
|
+
} catch {
|
|
1627
|
+
}
|
|
1628
|
+
next();
|
|
1629
|
+
};
|
|
1630
|
+
const enqueue = (function_, resolve, arguments_) => {
|
|
1631
|
+
queue.enqueue(
|
|
1632
|
+
AsyncResource.bind(run.bind(void 0, function_, resolve, arguments_))
|
|
1633
|
+
);
|
|
1634
|
+
(async () => {
|
|
1635
|
+
await Promise.resolve();
|
|
1636
|
+
if (activeCount < concurrency && queue.size > 0) {
|
|
1637
|
+
queue.dequeue()();
|
|
1638
|
+
}
|
|
1639
|
+
})();
|
|
1640
|
+
};
|
|
1641
|
+
const generator = (function_, ...arguments_) => new Promise((resolve) => {
|
|
1642
|
+
enqueue(function_, resolve, arguments_);
|
|
1643
|
+
});
|
|
1644
|
+
Object.defineProperties(generator, {
|
|
1645
|
+
activeCount: {
|
|
1646
|
+
get: () => activeCount
|
|
1647
|
+
},
|
|
1648
|
+
pendingCount: {
|
|
1649
|
+
get: () => queue.size
|
|
1650
|
+
},
|
|
1651
|
+
clearQueue: {
|
|
1652
|
+
value() {
|
|
1653
|
+
queue.clear();
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
});
|
|
1657
|
+
return generator;
|
|
1658
|
+
}
|
|
1530
1659
|
async function materializeRuntimeExtras(input) {
|
|
1531
1660
|
const root = targetPaths.runtimeRoot(
|
|
1532
1661
|
input.directory,
|
|
@@ -1591,6 +1720,16 @@ function errorMessage(error) {
|
|
|
1591
1720
|
}
|
|
1592
1721
|
|
|
1593
1722
|
// src/install/runner.ts
|
|
1723
|
+
var DOWNLOAD_GROUPS = [
|
|
1724
|
+
{ categories: ["runtime-file"], phase: InstallPhases.INSTALLING_RUNTIME },
|
|
1725
|
+
{ categories: ["client-jar"], phase: InstallPhases.DOWNLOADING_CLIENT_JAR },
|
|
1726
|
+
{ categories: ["library"], phase: InstallPhases.DOWNLOADING_LIBRARIES },
|
|
1727
|
+
{ categories: ["asset-index"], phase: InstallPhases.DOWNLOADING_ASSET_INDEX },
|
|
1728
|
+
{ categories: ["asset"], phase: InstallPhases.DOWNLOADING_ASSETS },
|
|
1729
|
+
{ categories: ["logging-config"], phase: InstallPhases.WRITING_FILES },
|
|
1730
|
+
{ categories: ["fabric-library"], phase: InstallPhases.INSTALLING_FABRIC },
|
|
1731
|
+
{ categories: ["forge-installer", "forge-library"], phase: InstallPhases.INSTALLING_FORGE }
|
|
1732
|
+
];
|
|
1594
1733
|
async function runInstall(input) {
|
|
1595
1734
|
const startedAt = Date.now();
|
|
1596
1735
|
let bytesDownloaded = 0;
|
|
@@ -1603,50 +1742,89 @@ async function runInstall(input) {
|
|
|
1603
1742
|
onEvent?.({ type: "install:phase-changed", phase, previous: currentPhase });
|
|
1604
1743
|
currentPhase = phase;
|
|
1605
1744
|
};
|
|
1606
|
-
const
|
|
1745
|
+
const checkpoint = async () => {
|
|
1746
|
+
if (input.signal?.aborted) {
|
|
1747
|
+
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1748
|
+
}
|
|
1749
|
+
await input.pauseController?.waitWhilePaused();
|
|
1750
|
+
if (input.signal?.aborted) {
|
|
1751
|
+
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
const categoryFilter = input.actionCategories;
|
|
1755
|
+
const downloads = input.plan.actions.filter(isDownload).filter((a) => categoryFilter ? categoryFilter.has(a.category) : true);
|
|
1607
1756
|
const natives = input.plan.actions.filter(isNative);
|
|
1608
1757
|
const writeActions = input.plan.actions.filter(isWrite);
|
|
1609
1758
|
const processors = input.plan.actions.filter(isProcessor);
|
|
1610
1759
|
enterPhase(InstallPhases.PLANNING);
|
|
1611
|
-
enterPhase(InstallPhases.DOWNLOADING_LIBRARIES);
|
|
1612
1760
|
const limit = pLimit(input.concurrency ?? DOWNLOAD_CONCURRENCY);
|
|
1613
|
-
|
|
1614
|
-
downloads.
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1761
|
+
for (const group of DOWNLOAD_GROUPS) {
|
|
1762
|
+
const groupActions = downloads.filter((action) => group.categories.includes(action.category));
|
|
1763
|
+
if (groupActions.length === 0) continue;
|
|
1764
|
+
await checkpoint();
|
|
1765
|
+
enterPhase(group.phase);
|
|
1766
|
+
await Promise.all(
|
|
1767
|
+
groupActions.map(
|
|
1768
|
+
(action) => limit(async () => {
|
|
1769
|
+
await checkpoint();
|
|
1770
|
+
const result = await downloadFile(input.http, {
|
|
1771
|
+
url: action.url,
|
|
1772
|
+
target: action.target,
|
|
1773
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
1774
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
1775
|
+
...action.category !== void 0 ? { category: action.category } : {},
|
|
1776
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1777
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1778
|
+
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1779
|
+
});
|
|
1780
|
+
bytesDownloaded += result.bytesDownloaded;
|
|
1781
|
+
if (result.skipped) actionsSkipped++;
|
|
1782
|
+
actionsCompleted++;
|
|
1783
|
+
})
|
|
1784
|
+
)
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
const ungrouped = downloads.filter(
|
|
1788
|
+
(action) => !DOWNLOAD_GROUPS.some((g) => g.categories.includes(action.category))
|
|
1633
1789
|
);
|
|
1790
|
+
if (ungrouped.length > 0) {
|
|
1791
|
+
await checkpoint();
|
|
1792
|
+
enterPhase(InstallPhases.DOWNLOADING_LIBRARIES);
|
|
1793
|
+
await Promise.all(
|
|
1794
|
+
ungrouped.map(
|
|
1795
|
+
(action) => limit(async () => {
|
|
1796
|
+
await checkpoint();
|
|
1797
|
+
const result = await downloadFile(input.http, {
|
|
1798
|
+
url: action.url,
|
|
1799
|
+
target: action.target,
|
|
1800
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
1801
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
1802
|
+
...action.category !== void 0 ? { category: action.category } : {},
|
|
1803
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1804
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1805
|
+
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1806
|
+
});
|
|
1807
|
+
bytesDownloaded += result.bytesDownloaded;
|
|
1808
|
+
if (result.skipped) actionsSkipped++;
|
|
1809
|
+
actionsCompleted++;
|
|
1810
|
+
})
|
|
1811
|
+
)
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1634
1814
|
if (writeActions.length > 0) {
|
|
1815
|
+
await checkpoint();
|
|
1635
1816
|
enterPhase(InstallPhases.WRITING_FILES);
|
|
1636
1817
|
for (const action of writeActions) {
|
|
1637
|
-
|
|
1638
|
-
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1639
|
-
}
|
|
1818
|
+
await checkpoint();
|
|
1640
1819
|
await atomicWrite(action.path, action.content);
|
|
1641
1820
|
actionsCompleted++;
|
|
1642
1821
|
}
|
|
1643
1822
|
}
|
|
1644
1823
|
if (natives.length > 0) {
|
|
1824
|
+
await checkpoint();
|
|
1645
1825
|
enterPhase(InstallPhases.EXTRACTING_NATIVES);
|
|
1646
1826
|
for (const action of natives) {
|
|
1647
|
-
|
|
1648
|
-
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1649
|
-
}
|
|
1827
|
+
await checkpoint();
|
|
1650
1828
|
const { fileCount } = await extractAllToDir(action.source, action.destination, {
|
|
1651
1829
|
excludePrefixes: action.exclude
|
|
1652
1830
|
});
|
|
@@ -1660,6 +1838,7 @@ async function runInstall(input) {
|
|
|
1660
1838
|
}
|
|
1661
1839
|
}
|
|
1662
1840
|
if (input.plan.target.runtime !== void 0) {
|
|
1841
|
+
await checkpoint();
|
|
1663
1842
|
enterPhase(InstallPhases.INSTALLING_RUNTIME);
|
|
1664
1843
|
const runtimePlan = await planRuntimeDownloads({
|
|
1665
1844
|
runtime: input.plan.target.runtime,
|
|
@@ -1675,6 +1854,7 @@ async function runInstall(input) {
|
|
|
1675
1854
|
});
|
|
1676
1855
|
}
|
|
1677
1856
|
if (processors.length > 0) {
|
|
1857
|
+
await checkpoint();
|
|
1678
1858
|
enterPhase(InstallPhases.RUNNING_FORGE_PROCESSORS);
|
|
1679
1859
|
if (input.plan.target.loader.type !== Loaders.FORGE) {
|
|
1680
1860
|
throw new MinecraftKitError(
|
|
@@ -1689,9 +1869,7 @@ async function runInstall(input) {
|
|
|
1689
1869
|
input.plan.target.runtime.installRoot
|
|
1690
1870
|
);
|
|
1691
1871
|
for (const action of processors) {
|
|
1692
|
-
|
|
1693
|
-
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1694
|
-
}
|
|
1872
|
+
await checkpoint();
|
|
1695
1873
|
await runProcessor({
|
|
1696
1874
|
action,
|
|
1697
1875
|
javaPath,
|
|
@@ -1906,6 +2084,33 @@ function pickArguments(args, context) {
|
|
|
1906
2084
|
};
|
|
1907
2085
|
}
|
|
1908
2086
|
|
|
2087
|
+
// src/launch/jvm-compat.ts
|
|
2088
|
+
var FLAG_MIN_JAVA = [
|
|
2089
|
+
{ prefix: "--sun-misc-unsafe-memory-access", minJava: 23 },
|
|
2090
|
+
{ prefix: "--enable-native-access", minJava: 17 },
|
|
2091
|
+
{ prefix: "-XX:+UseCompactObjectHeaders", minJava: 24 },
|
|
2092
|
+
{ prefix: "-XX:+UseZGC", minJava: 15 }
|
|
2093
|
+
];
|
|
2094
|
+
function filterArgsForJava(input) {
|
|
2095
|
+
if (!Number.isFinite(input.javaMajor) || input.javaMajor <= 0) return input.args;
|
|
2096
|
+
const out = [];
|
|
2097
|
+
for (const arg of input.args) {
|
|
2098
|
+
const incompatible = FLAG_MIN_JAVA.find(
|
|
2099
|
+
({ prefix }) => arg === prefix || arg.startsWith(`${prefix}=`) || arg.startsWith(`${prefix} `)
|
|
2100
|
+
);
|
|
2101
|
+
if (incompatible && input.javaMajor < incompatible.minJava) {
|
|
2102
|
+
input.logger?.log(
|
|
2103
|
+
"warn",
|
|
2104
|
+
`Dropping JVM arg "${arg}" \u2014 requires Java ${incompatible.minJava}, runtime is Java ${input.javaMajor}`,
|
|
2105
|
+
{ flag: arg, minJava: incompatible.minJava, runtimeJava: input.javaMajor }
|
|
2106
|
+
);
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
out.push(arg);
|
|
2110
|
+
}
|
|
2111
|
+
return out;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
1909
2114
|
// src/launch/placeholders.ts
|
|
1910
2115
|
function substituteArg(raw, values) {
|
|
1911
2116
|
return raw.replaceAll(/\$\{([a-zA-Z0-9_]+)\}/g, (match, key) => {
|
|
@@ -1945,7 +2150,13 @@ function composeArgs(input) {
|
|
|
1945
2150
|
const baseJvm = [...memoryArgs, ...BASE_JVM_ARGS, ...macosArgs];
|
|
1946
2151
|
const substitutedJvm = substituteArgs(rawJvm, input.placeholderValues);
|
|
1947
2152
|
const substitutedGame = substituteArgs(rawGame, input.placeholderValues);
|
|
1948
|
-
const
|
|
2153
|
+
const javaMajor = input.target.runtime.majorVersion;
|
|
2154
|
+
const filteredManifestJvm = javaMajor !== void 0 ? filterArgsForJava({
|
|
2155
|
+
args: substitutedJvm,
|
|
2156
|
+
javaMajor,
|
|
2157
|
+
logger: input.logger ?? silentLogger
|
|
2158
|
+
}) : substitutedJvm;
|
|
2159
|
+
const jvmArgs = [...baseJvm, ...filteredManifestJvm];
|
|
1949
2160
|
if (input.merged.logging?.client?.argument) {
|
|
1950
2161
|
const logging = input.merged.logging.client;
|
|
1951
2162
|
const loggingArg = substituteArgs([logging.argument], {
|
|
@@ -2075,8 +2286,28 @@ function mergeManifest(parent, child) {
|
|
|
2075
2286
|
};
|
|
2076
2287
|
return merged;
|
|
2077
2288
|
}
|
|
2289
|
+
function libraryDedupeKey(library) {
|
|
2290
|
+
if (!library.name) return null;
|
|
2291
|
+
try {
|
|
2292
|
+
const coord = parseMavenCoordinate(library.name);
|
|
2293
|
+
const classifier = coord.classifier ? `:${coord.classifier}` : "";
|
|
2294
|
+
return `${coord.group}:${coord.artifact}${classifier}`;
|
|
2295
|
+
} catch {
|
|
2296
|
+
return null;
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2078
2299
|
function mergeLibraries(parent, child) {
|
|
2079
|
-
|
|
2300
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2301
|
+
const unkeyed = [];
|
|
2302
|
+
for (const lib of [...parent, ...child]) {
|
|
2303
|
+
const key = libraryDedupeKey(lib);
|
|
2304
|
+
if (key === null) {
|
|
2305
|
+
unkeyed.push(lib);
|
|
2306
|
+
continue;
|
|
2307
|
+
}
|
|
2308
|
+
byKey.set(key, lib);
|
|
2309
|
+
}
|
|
2310
|
+
return [...byKey.values(), ...unkeyed];
|
|
2080
2311
|
}
|
|
2081
2312
|
function mergeArguments(parent, child) {
|
|
2082
2313
|
if (!parent && !child) return void 0;
|
|
@@ -2200,7 +2431,8 @@ async function composeLaunch(input) {
|
|
|
2200
2431
|
merged: resolved.merged,
|
|
2201
2432
|
options,
|
|
2202
2433
|
placeholderValues,
|
|
2203
|
-
features
|
|
2434
|
+
features,
|
|
2435
|
+
logger: input.logger ?? silentLogger
|
|
2204
2436
|
});
|
|
2205
2437
|
return {
|
|
2206
2438
|
targetId: target.id,
|
|
@@ -2382,546 +2614,212 @@ var VerifyFileCategories = {
|
|
|
2382
2614
|
RUNTIME_FILE: "runtime-file",
|
|
2383
2615
|
LOGGING_CONFIG: "logging-config"
|
|
2384
2616
|
};
|
|
2617
|
+
async function sha1OfFile(filePath) {
|
|
2618
|
+
const hash = crypto2.createHash("sha1");
|
|
2619
|
+
await new Promise((resolve, reject) => {
|
|
2620
|
+
const stream = createReadStream(filePath);
|
|
2621
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
2622
|
+
stream.on("end", resolve);
|
|
2623
|
+
stream.on("error", reject);
|
|
2624
|
+
});
|
|
2625
|
+
return hash.digest("hex");
|
|
2626
|
+
}
|
|
2385
2627
|
|
|
2386
|
-
// src/
|
|
2387
|
-
function
|
|
2388
|
-
|
|
2628
|
+
// src/verify/helpers.ts
|
|
2629
|
+
async function runVerification(input, check) {
|
|
2630
|
+
const startedAt = Date.now();
|
|
2631
|
+
const results = [];
|
|
2632
|
+
const record = (result) => {
|
|
2633
|
+
results.push(result);
|
|
2634
|
+
input.onEvent?.({ type: "verify:file-checked", file: result });
|
|
2635
|
+
};
|
|
2636
|
+
await check(record);
|
|
2637
|
+
return {
|
|
2638
|
+
targetId: input.targetId,
|
|
2639
|
+
kind: input.kind,
|
|
2640
|
+
isValid: results.every((r) => r.status === VerifyFileStatuses.OK),
|
|
2641
|
+
issues: results.filter((r) => r.status !== VerifyFileStatuses.OK),
|
|
2642
|
+
checkedFiles: results.length,
|
|
2643
|
+
durationMs: Date.now() - startedAt
|
|
2644
|
+
};
|
|
2389
2645
|
}
|
|
2390
|
-
function
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2646
|
+
async function verifyHashedFile(input) {
|
|
2647
|
+
if (!await fileExists(input.path)) {
|
|
2648
|
+
return {
|
|
2649
|
+
path: input.path,
|
|
2650
|
+
category: input.category,
|
|
2651
|
+
status: VerifyFileStatuses.MISSING,
|
|
2652
|
+
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2653
|
+
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2654
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
if (input.expectedSize !== void 0) {
|
|
2658
|
+
const size = await fileSize(input.path);
|
|
2659
|
+
if (size !== input.expectedSize) {
|
|
2660
|
+
return {
|
|
2661
|
+
path: input.path,
|
|
2662
|
+
category: input.category,
|
|
2663
|
+
status: VerifyFileStatuses.WRONG_SIZE,
|
|
2664
|
+
expectedSize: input.expectedSize,
|
|
2665
|
+
actualSize: size,
|
|
2666
|
+
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2667
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
if (input.expectedSha1 !== void 0) {
|
|
2672
|
+
const actualSha1 = await sha1OfFile(input.path);
|
|
2673
|
+
if (actualSha1 !== input.expectedSha1) {
|
|
2674
|
+
return {
|
|
2675
|
+
path: input.path,
|
|
2676
|
+
category: input.category,
|
|
2677
|
+
status: VerifyFileStatuses.CORRUPT,
|
|
2678
|
+
expectedSha1: input.expectedSha1,
|
|
2679
|
+
actualSha1,
|
|
2680
|
+
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2681
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2682
|
+
};
|
|
2397
2683
|
}
|
|
2398
2684
|
}
|
|
2399
2685
|
return {
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
}
|
|
2407
|
-
return false;
|
|
2408
|
-
},
|
|
2409
|
-
categoriesAt: (path16) => map.get(path16) ?? /* @__PURE__ */ new Set()
|
|
2686
|
+
path: input.path,
|
|
2687
|
+
category: input.category,
|
|
2688
|
+
status: VerifyFileStatuses.OK,
|
|
2689
|
+
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2690
|
+
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2691
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2410
2692
|
};
|
|
2411
2693
|
}
|
|
2412
|
-
function
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2694
|
+
async function verifyExistence(input) {
|
|
2695
|
+
if (await fileExists(input.path)) {
|
|
2696
|
+
return {
|
|
2697
|
+
path: input.path,
|
|
2698
|
+
category: input.category,
|
|
2699
|
+
status: VerifyFileStatuses.OK,
|
|
2700
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2421
2703
|
return {
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
totalActions: actions.length,
|
|
2427
|
-
totalBytes: sumDownloadBytes(actions)
|
|
2704
|
+
path: input.path,
|
|
2705
|
+
category: input.category,
|
|
2706
|
+
status: VerifyFileStatuses.MISSING,
|
|
2707
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2428
2708
|
};
|
|
2429
2709
|
}
|
|
2430
|
-
async function
|
|
2431
|
-
const
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
const actions = selectRepairActions({
|
|
2439
|
-
target: input.target,
|
|
2440
|
-
installPlan,
|
|
2441
|
-
issues,
|
|
2442
|
-
aspectFilter
|
|
2443
|
-
});
|
|
2444
|
-
postprocess?.({ actions, installPlan, issues });
|
|
2445
|
-
return buildRepairPlan(input.target, actions);
|
|
2446
|
-
}
|
|
2447
|
-
function selectRepairActions(input) {
|
|
2448
|
-
const matching = [];
|
|
2449
|
-
for (const action of input.installPlan.actions) {
|
|
2450
|
-
if (!input.aspectFilter(action)) continue;
|
|
2451
|
-
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
2452
|
-
if (input.issues.hasNonNative(action.target)) {
|
|
2453
|
-
matching.push(action);
|
|
2454
|
-
}
|
|
2455
|
-
} else if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
2456
|
-
if (input.issues.has(action.path)) {
|
|
2457
|
-
matching.push(action);
|
|
2458
|
-
}
|
|
2459
|
-
} else if (action.kind === InstallActionKinds.EXTRACT_NATIVE) {
|
|
2460
|
-
if (input.issues.has(action.source)) {
|
|
2461
|
-
matching.push(action);
|
|
2462
|
-
}
|
|
2463
|
-
} else {
|
|
2464
|
-
matching.push(action);
|
|
2710
|
+
async function findForgeVersionJsonPath(directory, minecraftVersion) {
|
|
2711
|
+
const versionsDir = targetPaths.versionsDir(directory);
|
|
2712
|
+
const dirs = await listChildDirectories(versionsDir);
|
|
2713
|
+
for (const id of dirs) {
|
|
2714
|
+
if (!id.startsWith(`${minecraftVersion}-forge-`)) continue;
|
|
2715
|
+
const jsonPath = targetPaths.versionJson(directory, id);
|
|
2716
|
+
if (!await fileExists(jsonPath)) {
|
|
2717
|
+
return jsonPath;
|
|
2465
2718
|
}
|
|
2719
|
+
const parsed = await tryParseInheritsFrom(jsonPath);
|
|
2720
|
+
if (parsed === minecraftVersion) return jsonPath;
|
|
2721
|
+
}
|
|
2722
|
+
return null;
|
|
2723
|
+
}
|
|
2724
|
+
async function tryParseInheritsFrom(jsonPath) {
|
|
2725
|
+
try {
|
|
2726
|
+
const parsed = JSON.parse(await readText(jsonPath));
|
|
2727
|
+
return parsed.inheritsFrom;
|
|
2728
|
+
} catch {
|
|
2729
|
+
return void 0;
|
|
2466
2730
|
}
|
|
2467
|
-
return matching;
|
|
2468
2731
|
}
|
|
2469
2732
|
|
|
2470
|
-
// src/
|
|
2471
|
-
async function
|
|
2733
|
+
// src/verify/fabric.ts
|
|
2734
|
+
async function verifyFabric(input) {
|
|
2472
2735
|
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
2473
2736
|
throw new MinecraftKitError(
|
|
2474
2737
|
"INVALID_INPUT",
|
|
2475
|
-
`
|
|
2738
|
+
`verify.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
2476
2739
|
);
|
|
2477
2740
|
}
|
|
2478
|
-
const
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
return action.category === "fabric-library";
|
|
2485
|
-
}
|
|
2486
|
-
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
2487
|
-
return action.path === fabricJsonPath;
|
|
2488
|
-
}
|
|
2489
|
-
return false;
|
|
2490
|
-
});
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
|
-
// src/repair/forge.ts
|
|
2494
|
-
var FORGE_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
2495
|
-
"forge-library",
|
|
2496
|
-
"forge-installer"
|
|
2497
|
-
]);
|
|
2498
|
-
async function planForgeRepair(input) {
|
|
2499
|
-
if (input.target.loader.type !== Loaders.FORGE) {
|
|
2500
|
-
throw new MinecraftKitError(
|
|
2501
|
-
"INVALID_INPUT",
|
|
2502
|
-
`repair.forge requires a Forge target (got ${input.target.loader.type})`
|
|
2503
|
-
);
|
|
2504
|
-
}
|
|
2505
|
-
const forgeJsonPath = targetPaths.versionJson(
|
|
2506
|
-
input.target.directory,
|
|
2507
|
-
input.target.loader.fullVersion
|
|
2508
|
-
);
|
|
2509
|
-
return planAspectRepair(
|
|
2510
|
-
input,
|
|
2511
|
-
(action) => {
|
|
2512
|
-
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
2513
|
-
return FORGE_DOWNLOAD_CATEGORIES.has(action.category);
|
|
2514
|
-
}
|
|
2515
|
-
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
2516
|
-
return action.path === forgeJsonPath;
|
|
2517
|
-
}
|
|
2518
|
-
return false;
|
|
2519
|
-
},
|
|
2520
|
-
({ actions, installPlan, issues }) => {
|
|
2521
|
-
if (!issues.has(forgeJsonPath)) return;
|
|
2522
|
-
const alreadyIncluded = new Set(
|
|
2523
|
-
actions.filter((a) => a.kind === InstallActionKinds.DOWNLOAD_FILE).map((a) => a.target)
|
|
2524
|
-
);
|
|
2525
|
-
for (const action of installPlan.actions) {
|
|
2526
|
-
if (action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category === "forge-library" && !alreadyIncluded.has(action.target)) {
|
|
2527
|
-
actions.push(action);
|
|
2528
|
-
} else if (action.kind === InstallActionKinds.RUN_FORGE_PROCESSOR) {
|
|
2529
|
-
actions.push(action);
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
|
-
}
|
|
2533
|
-
);
|
|
2534
|
-
}
|
|
2535
|
-
|
|
2536
|
-
// src/repair/minecraft.ts
|
|
2537
|
-
var MINECRAFT_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
2538
|
-
"client-jar",
|
|
2539
|
-
"library",
|
|
2540
|
-
"asset-index",
|
|
2541
|
-
"asset",
|
|
2542
|
-
"logging-config"
|
|
2543
|
-
]);
|
|
2544
|
-
async function planMinecraftRepair(input) {
|
|
2545
|
-
const vanillaJsonPath = targetPaths.versionJson(
|
|
2546
|
-
input.target.directory,
|
|
2547
|
-
input.target.minecraft.version
|
|
2548
|
-
);
|
|
2549
|
-
return planAspectRepair(input, (action) => {
|
|
2550
|
-
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
2551
|
-
return MINECRAFT_DOWNLOAD_CATEGORIES.has(action.category);
|
|
2552
|
-
}
|
|
2553
|
-
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
2554
|
-
return action.path === vanillaJsonPath;
|
|
2555
|
-
}
|
|
2556
|
-
if (action.kind === InstallActionKinds.EXTRACT_NATIVE) {
|
|
2557
|
-
return true;
|
|
2558
|
-
}
|
|
2559
|
-
return false;
|
|
2560
|
-
});
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
// src/repair/runner.ts
|
|
2564
|
-
async function runRepair(input) {
|
|
2565
|
-
const report = await runInstall({
|
|
2566
|
-
plan: {
|
|
2567
|
-
...input.plan,
|
|
2568
|
-
totalActions: input.plan.actions.length,
|
|
2569
|
-
totalBytes: input.plan.totalBytes
|
|
2741
|
+
const loader = input.target.loader;
|
|
2742
|
+
return runVerification(
|
|
2743
|
+
{
|
|
2744
|
+
targetId: input.target.id,
|
|
2745
|
+
kind: VerificationKinds.FABRIC,
|
|
2746
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2570
2747
|
},
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
return {
|
|
2578
|
-
targetId: report.targetId,
|
|
2579
|
-
bytesDownloaded: report.bytesDownloaded,
|
|
2580
|
-
actionsCompleted: report.actionsCompleted,
|
|
2581
|
-
durationMs: report.durationMs
|
|
2582
|
-
};
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
// src/repair/runtime.ts
|
|
2586
|
-
async function planRuntimeRepair(input) {
|
|
2587
|
-
return planAspectRepair(
|
|
2588
|
-
input,
|
|
2589
|
-
(action) => action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category === "runtime-file"
|
|
2590
|
-
);
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
// src/types/runtime.ts
|
|
2594
|
-
var RuntimeComponents = {
|
|
2595
|
-
JRE_LEGACY: "jre-legacy"};
|
|
2596
|
-
var RuntimePreference = {
|
|
2597
|
-
/** Component declared by the Minecraft manifest. */
|
|
2598
|
-
RECOMMENDED: "recommended",
|
|
2599
|
-
/** Newest component available for the platform. */
|
|
2600
|
-
LATEST: "latest"
|
|
2601
|
-
};
|
|
2602
|
-
|
|
2603
|
-
// src/targets/index.ts
|
|
2604
|
-
var TargetsApi = class {
|
|
2605
|
-
constructor(ctx) {
|
|
2606
|
-
this.ctx = ctx;
|
|
2607
|
-
}
|
|
2608
|
-
ctx;
|
|
2609
|
-
/** The detected host system used by `resolve()` when no `system` is supplied. */
|
|
2610
|
-
get system() {
|
|
2611
|
-
return this.ctx.system;
|
|
2612
|
-
}
|
|
2613
|
-
/** Build a {@link Target} from already-resolved components. */
|
|
2614
|
-
create(input) {
|
|
2615
|
-
if (!input.id) {
|
|
2616
|
-
throw new MinecraftKitError("INVALID_INPUT", "Target id must be non-empty");
|
|
2617
|
-
}
|
|
2618
|
-
if (!input.directory) {
|
|
2619
|
-
throw new MinecraftKitError("INVALID_INPUT", "Target directory must be non-empty");
|
|
2620
|
-
}
|
|
2621
|
-
if (input.loader.minecraftVersion !== input.minecraft.version) {
|
|
2622
|
-
throw new MinecraftKitError(
|
|
2623
|
-
"INVALID_INPUT",
|
|
2624
|
-
`Loader Minecraft version (${input.loader.minecraftVersion}) does not match resolved Minecraft (${input.minecraft.version})`,
|
|
2625
|
-
{
|
|
2626
|
-
context: {
|
|
2627
|
-
loaderMinecraft: input.loader.minecraftVersion,
|
|
2628
|
-
minecraftVersion: input.minecraft.version
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2748
|
+
async (record) => {
|
|
2749
|
+
record(
|
|
2750
|
+
await verifyExistence({
|
|
2751
|
+
path: targetPaths.versionJson(input.target.directory, loader.profile.id),
|
|
2752
|
+
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2753
|
+
})
|
|
2631
2754
|
);
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
runtime: input.runtime
|
|
2639
|
-
};
|
|
2640
|
-
}
|
|
2641
|
-
/** Sugar API: resolve every component then assemble a target. */
|
|
2642
|
-
async resolve(input) {
|
|
2643
|
-
const minecraft = await this.ctx.minecraft.resolve({
|
|
2644
|
-
version: input.minecraft.version,
|
|
2645
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2646
|
-
});
|
|
2647
|
-
const system = input.system ?? this.ctx.system;
|
|
2648
|
-
const componentOverride = input.runtime?.component;
|
|
2649
|
-
const runtimeComponent = componentOverride ?? minecraft.manifest.javaVersion?.component;
|
|
2650
|
-
const resolvedRuntime = await this.ctx.runtime.resolve({
|
|
2651
|
-
system,
|
|
2652
|
-
...runtimeComponent !== void 0 ? { component: runtimeComponent } : {},
|
|
2653
|
-
preference: input.runtime?.preference ?? RuntimePreference.RECOMMENDED,
|
|
2654
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2655
|
-
});
|
|
2656
|
-
const runtime = input.runtime?.installRoot !== void 0 ? { ...resolvedRuntime, installRoot: input.runtime.installRoot } : resolvedRuntime;
|
|
2657
|
-
let loader;
|
|
2658
|
-
if (input.loader.type === Loaders.VANILLA) {
|
|
2659
|
-
loader = {
|
|
2660
|
-
type: Loaders.VANILLA,
|
|
2661
|
-
minecraftVersion: minecraft.version,
|
|
2662
|
-
minecraft
|
|
2663
|
-
};
|
|
2664
|
-
} else if (input.loader.type === Loaders.FABRIC) {
|
|
2665
|
-
loader = await this.ctx.fabric.resolve({
|
|
2666
|
-
minecraftVersion: minecraft.version,
|
|
2667
|
-
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
2668
|
-
...input.loader.version !== void 0 ? { loaderVersion: input.loader.version } : {},
|
|
2669
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2670
|
-
});
|
|
2671
|
-
} else {
|
|
2672
|
-
loader = await this.ctx.forge.resolve({
|
|
2673
|
-
minecraftVersion: minecraft.version,
|
|
2674
|
-
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
2675
|
-
...input.loader.version !== void 0 ? { forgeVersion: input.loader.version } : {},
|
|
2676
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2755
|
+
const fabricLibraries = planLibraryDownloads({
|
|
2756
|
+
libraries: loader.profile.libraries,
|
|
2757
|
+
directory: input.target.directory,
|
|
2758
|
+
system: input.target.runtime.system,
|
|
2759
|
+
versionId: input.target.minecraft.version,
|
|
2760
|
+
category: "fabric-library"
|
|
2677
2761
|
});
|
|
2762
|
+
for (const action of fabricLibraries.downloads) {
|
|
2763
|
+
record(
|
|
2764
|
+
await verifyHashedFile({
|
|
2765
|
+
path: action.target,
|
|
2766
|
+
expectedSha1: action.expectedSha1,
|
|
2767
|
+
expectedSize: action.expectedSize,
|
|
2768
|
+
...action.url ? { url: action.url } : {},
|
|
2769
|
+
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2770
|
+
})
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2678
2773
|
}
|
|
2679
|
-
|
|
2680
|
-
id: input.id,
|
|
2681
|
-
directory: input.directory,
|
|
2682
|
-
minecraft,
|
|
2683
|
-
loader,
|
|
2684
|
-
runtime
|
|
2685
|
-
});
|
|
2686
|
-
}
|
|
2687
|
-
/** Scan a root directory for Minecraft installations. Returns only what is on disk. */
|
|
2688
|
-
async list(input) {
|
|
2689
|
-
if (!await dirExists(input.rootDir)) return [];
|
|
2690
|
-
const subdirs = await listChildDirectories(input.rootDir);
|
|
2691
|
-
const results = [];
|
|
2692
|
-
for (const id of subdirs) {
|
|
2693
|
-
const directory = path.join(input.rootDir, id);
|
|
2694
|
-
const discovered = await discoverInstallation(id, directory);
|
|
2695
|
-
if (discovered) results.push(discovered);
|
|
2696
|
-
}
|
|
2697
|
-
return results;
|
|
2698
|
-
}
|
|
2699
|
-
};
|
|
2700
|
-
async function discoverInstallation(id, directory) {
|
|
2701
|
-
const versionsDir = path.join(directory, VERSIONS_DIR);
|
|
2702
|
-
const librariesDir = path.join(directory, LIBRARIES_DIR);
|
|
2703
|
-
const assetsDir = path.join(directory, ASSETS_DIR);
|
|
2704
|
-
const looksLikeInstall = await dirExists(versionsDir) && (await dirExists(librariesDir) || await dirExists(assetsDir));
|
|
2705
|
-
if (!looksLikeInstall) return null;
|
|
2706
|
-
const versionDirs = await listChildDirectories(versionsDir);
|
|
2707
|
-
const minecraftVersions = [];
|
|
2708
|
-
const loaders = [];
|
|
2709
|
-
for (const versionId of versionDirs) {
|
|
2710
|
-
const hint = inferLoaderFromVersionId(versionId);
|
|
2711
|
-
if (hint) {
|
|
2712
|
-
loaders.push(hint);
|
|
2713
|
-
if (hint.minecraftVersion && !minecraftVersions.includes(hint.minecraftVersion)) {
|
|
2714
|
-
minecraftVersions.push(hint.minecraftVersion);
|
|
2715
|
-
}
|
|
2716
|
-
} else {
|
|
2717
|
-
minecraftVersions.push(versionId);
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
|
-
const runtime = await discoverRuntime(directory);
|
|
2721
|
-
return { id, directory, minecraftVersions, loaders, ...runtime ? { runtime } : {} };
|
|
2722
|
-
}
|
|
2723
|
-
async function discoverRuntime(directory) {
|
|
2724
|
-
const runtimeDir = path.join(directory, RUNTIMES_DIR);
|
|
2725
|
-
if (!await dirExists(runtimeDir)) return void 0;
|
|
2726
|
-
let components;
|
|
2727
|
-
try {
|
|
2728
|
-
components = await listChildDirectories(runtimeDir);
|
|
2729
|
-
} catch {
|
|
2730
|
-
return void 0;
|
|
2731
|
-
}
|
|
2732
|
-
for (const component of components) {
|
|
2733
|
-
const root = path.join(runtimeDir, component);
|
|
2734
|
-
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");
|
|
2735
|
-
if (await fileExists(javaPath)) {
|
|
2736
|
-
return { component, javaPath };
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
return void 0;
|
|
2740
|
-
}
|
|
2741
|
-
function inferLoaderFromVersionId(versionId) {
|
|
2742
|
-
const fabricMatch = /^fabric-loader-([^-]+)-(.+)$/.exec(versionId);
|
|
2743
|
-
if (fabricMatch?.[1] && fabricMatch[2]) {
|
|
2744
|
-
return { type: Loaders.FABRIC, version: fabricMatch[1], minecraftVersion: fabricMatch[2] };
|
|
2745
|
-
}
|
|
2746
|
-
const forgeMatch = /^([^-]+)-forge-(.+)$/.exec(versionId);
|
|
2747
|
-
if (forgeMatch?.[1] && forgeMatch[2]) {
|
|
2748
|
-
return { type: Loaders.FORGE, minecraftVersion: forgeMatch[1], version: forgeMatch[2] };
|
|
2749
|
-
}
|
|
2750
|
-
return null;
|
|
2751
|
-
}
|
|
2752
|
-
|
|
2753
|
-
// src/update/runner.ts
|
|
2754
|
-
async function planUpdate(input) {
|
|
2755
|
-
return planInstall({
|
|
2756
|
-
target: input.target,
|
|
2757
|
-
http: input.http,
|
|
2758
|
-
cache: input.cache,
|
|
2759
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2760
|
-
});
|
|
2761
|
-
}
|
|
2762
|
-
async function runUpdate(input) {
|
|
2763
|
-
const report = await runInstall({
|
|
2764
|
-
plan: input.plan,
|
|
2765
|
-
http: input.http,
|
|
2766
|
-
cache: input.cache,
|
|
2767
|
-
spawner: input.spawner,
|
|
2768
|
-
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
2769
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2770
|
-
});
|
|
2771
|
-
return {
|
|
2772
|
-
targetId: report.targetId,
|
|
2773
|
-
bytesDownloaded: report.bytesDownloaded,
|
|
2774
|
-
actionsCompleted: report.actionsCompleted,
|
|
2775
|
-
actionsSkipped: report.actionsSkipped,
|
|
2776
|
-
durationMs: report.durationMs
|
|
2777
|
-
};
|
|
2778
|
-
}
|
|
2779
|
-
async function sha1OfFile(filePath) {
|
|
2780
|
-
const hash = crypto2.createHash("sha1");
|
|
2781
|
-
await new Promise((resolve, reject) => {
|
|
2782
|
-
const stream = createReadStream(filePath);
|
|
2783
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
2784
|
-
stream.on("end", resolve);
|
|
2785
|
-
stream.on("error", reject);
|
|
2786
|
-
});
|
|
2787
|
-
return hash.digest("hex");
|
|
2788
|
-
}
|
|
2789
|
-
|
|
2790
|
-
// src/verify/helpers.ts
|
|
2791
|
-
async function runVerification(input, check) {
|
|
2792
|
-
const startedAt = Date.now();
|
|
2793
|
-
const results = [];
|
|
2794
|
-
const record = (result) => {
|
|
2795
|
-
results.push(result);
|
|
2796
|
-
input.onEvent?.({ type: "verify:file-checked", file: result });
|
|
2797
|
-
};
|
|
2798
|
-
await check(record);
|
|
2799
|
-
return {
|
|
2800
|
-
targetId: input.targetId,
|
|
2801
|
-
kind: input.kind,
|
|
2802
|
-
isValid: results.every((r) => r.status === VerifyFileStatuses.OK),
|
|
2803
|
-
issues: results.filter((r) => r.status !== VerifyFileStatuses.OK),
|
|
2804
|
-
checkedFiles: results.length,
|
|
2805
|
-
durationMs: Date.now() - startedAt
|
|
2806
|
-
};
|
|
2807
|
-
}
|
|
2808
|
-
async function verifyHashedFile(input) {
|
|
2809
|
-
if (!await fileExists(input.path)) {
|
|
2810
|
-
return {
|
|
2811
|
-
path: input.path,
|
|
2812
|
-
category: input.category,
|
|
2813
|
-
status: VerifyFileStatuses.MISSING,
|
|
2814
|
-
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2815
|
-
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2816
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2817
|
-
};
|
|
2818
|
-
}
|
|
2819
|
-
if (input.expectedSize !== void 0) {
|
|
2820
|
-
const size = await fileSize(input.path);
|
|
2821
|
-
if (size !== input.expectedSize) {
|
|
2822
|
-
return {
|
|
2823
|
-
path: input.path,
|
|
2824
|
-
category: input.category,
|
|
2825
|
-
status: VerifyFileStatuses.WRONG_SIZE,
|
|
2826
|
-
expectedSize: input.expectedSize,
|
|
2827
|
-
actualSize: size,
|
|
2828
|
-
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2829
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2830
|
-
};
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2833
|
-
if (input.expectedSha1 !== void 0) {
|
|
2834
|
-
const actualSha1 = await sha1OfFile(input.path);
|
|
2835
|
-
if (actualSha1 !== input.expectedSha1) {
|
|
2836
|
-
return {
|
|
2837
|
-
path: input.path,
|
|
2838
|
-
category: input.category,
|
|
2839
|
-
status: VerifyFileStatuses.CORRUPT,
|
|
2840
|
-
expectedSha1: input.expectedSha1,
|
|
2841
|
-
actualSha1,
|
|
2842
|
-
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2843
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2844
|
-
};
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
return {
|
|
2848
|
-
path: input.path,
|
|
2849
|
-
category: input.category,
|
|
2850
|
-
status: VerifyFileStatuses.OK,
|
|
2851
|
-
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2852
|
-
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2853
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2854
|
-
};
|
|
2855
|
-
}
|
|
2856
|
-
async function verifyExistence(input) {
|
|
2857
|
-
if (await fileExists(input.path)) {
|
|
2858
|
-
return {
|
|
2859
|
-
path: input.path,
|
|
2860
|
-
category: input.category,
|
|
2861
|
-
status: VerifyFileStatuses.OK,
|
|
2862
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2863
|
-
};
|
|
2864
|
-
}
|
|
2865
|
-
return {
|
|
2866
|
-
path: input.path,
|
|
2867
|
-
category: input.category,
|
|
2868
|
-
status: VerifyFileStatuses.MISSING,
|
|
2869
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2870
|
-
};
|
|
2871
|
-
}
|
|
2872
|
-
async function findForgeVersionJsonPath(directory, minecraftVersion) {
|
|
2873
|
-
const versionsDir = targetPaths.versionsDir(directory);
|
|
2874
|
-
const dirs = await listChildDirectories(versionsDir);
|
|
2875
|
-
for (const id of dirs) {
|
|
2876
|
-
if (!id.startsWith(`${minecraftVersion}-forge-`)) continue;
|
|
2877
|
-
const jsonPath = targetPaths.versionJson(directory, id);
|
|
2878
|
-
if (!await fileExists(jsonPath)) {
|
|
2879
|
-
return jsonPath;
|
|
2880
|
-
}
|
|
2881
|
-
const parsed = await tryParseInheritsFrom(jsonPath);
|
|
2882
|
-
if (parsed === minecraftVersion) return jsonPath;
|
|
2883
|
-
}
|
|
2884
|
-
return null;
|
|
2885
|
-
}
|
|
2886
|
-
async function tryParseInheritsFrom(jsonPath) {
|
|
2887
|
-
try {
|
|
2888
|
-
const parsed = JSON.parse(await readText(jsonPath));
|
|
2889
|
-
return parsed.inheritsFrom;
|
|
2890
|
-
} catch {
|
|
2891
|
-
return void 0;
|
|
2892
|
-
}
|
|
2774
|
+
);
|
|
2893
2775
|
}
|
|
2894
2776
|
|
|
2895
|
-
// src/verify/
|
|
2896
|
-
async function
|
|
2897
|
-
if (input.target.loader.type !== Loaders.
|
|
2777
|
+
// src/verify/forge.ts
|
|
2778
|
+
async function verifyForge(input) {
|
|
2779
|
+
if (input.target.loader.type !== Loaders.FORGE) {
|
|
2898
2780
|
throw new MinecraftKitError(
|
|
2899
2781
|
"INVALID_INPUT",
|
|
2900
|
-
`verify.
|
|
2782
|
+
`verify.forge requires a Forge target (got ${input.target.loader.type})`
|
|
2901
2783
|
);
|
|
2902
2784
|
}
|
|
2903
|
-
const loader = input.target.loader;
|
|
2904
2785
|
return runVerification(
|
|
2905
2786
|
{
|
|
2906
2787
|
targetId: input.target.id,
|
|
2907
|
-
kind: VerificationKinds.
|
|
2788
|
+
kind: VerificationKinds.FORGE,
|
|
2908
2789
|
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2909
2790
|
},
|
|
2910
2791
|
async (record) => {
|
|
2792
|
+
const forgeVersionJsonPath = await findForgeVersionJsonPath(
|
|
2793
|
+
input.target.directory,
|
|
2794
|
+
input.target.minecraft.version
|
|
2795
|
+
);
|
|
2796
|
+
if (forgeVersionJsonPath === null) return;
|
|
2911
2797
|
record(
|
|
2912
2798
|
await verifyExistence({
|
|
2913
|
-
path:
|
|
2799
|
+
path: forgeVersionJsonPath,
|
|
2914
2800
|
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2915
2801
|
})
|
|
2916
2802
|
);
|
|
2917
|
-
|
|
2918
|
-
|
|
2803
|
+
if (!await fileExists(forgeVersionJsonPath)) return;
|
|
2804
|
+
let parsed;
|
|
2805
|
+
try {
|
|
2806
|
+
parsed = JSON.parse(await readText(forgeVersionJsonPath));
|
|
2807
|
+
} catch {
|
|
2808
|
+
record({
|
|
2809
|
+
path: forgeVersionJsonPath,
|
|
2810
|
+
category: VerifyFileCategories.LOADER_LIBRARY,
|
|
2811
|
+
status: VerifyFileStatuses.CORRUPT
|
|
2812
|
+
});
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
const forgeLibraries = planLibraryDownloads({
|
|
2816
|
+
libraries: parsed.libraries,
|
|
2919
2817
|
directory: input.target.directory,
|
|
2920
2818
|
system: input.target.runtime.system,
|
|
2921
2819
|
versionId: input.target.minecraft.version,
|
|
2922
|
-
category: "
|
|
2820
|
+
category: "forge-library"
|
|
2923
2821
|
});
|
|
2924
|
-
for (const action of
|
|
2822
|
+
for (const action of forgeLibraries.downloads) {
|
|
2925
2823
|
record(
|
|
2926
2824
|
await verifyHashedFile({
|
|
2927
2825
|
path: action.target,
|
|
@@ -2936,205 +2834,591 @@ async function verifyFabric(input) {
|
|
|
2936
2834
|
);
|
|
2937
2835
|
}
|
|
2938
2836
|
|
|
2939
|
-
// src/verify/
|
|
2940
|
-
async function
|
|
2837
|
+
// src/verify/minecraft.ts
|
|
2838
|
+
async function verifyMinecraft(input) {
|
|
2839
|
+
return runVerification(
|
|
2840
|
+
{
|
|
2841
|
+
targetId: input.target.id,
|
|
2842
|
+
kind: VerificationKinds.MINECRAFT,
|
|
2843
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2844
|
+
},
|
|
2845
|
+
async (record) => {
|
|
2846
|
+
const { directory, minecraft, runtime } = input.target;
|
|
2847
|
+
record(
|
|
2848
|
+
await verifyHashedFile({
|
|
2849
|
+
path: targetPaths.versionJar(directory, minecraft.version),
|
|
2850
|
+
expectedSha1: minecraft.manifest.downloads.client.sha1,
|
|
2851
|
+
expectedSize: minecraft.manifest.downloads.client.size,
|
|
2852
|
+
url: minecraft.manifest.downloads.client.url,
|
|
2853
|
+
category: VerifyFileCategories.CLIENT_JAR
|
|
2854
|
+
})
|
|
2855
|
+
);
|
|
2856
|
+
record(
|
|
2857
|
+
await verifyExistence({
|
|
2858
|
+
path: targetPaths.versionJson(directory, minecraft.version),
|
|
2859
|
+
category: VerifyFileCategories.CLIENT_JAR
|
|
2860
|
+
})
|
|
2861
|
+
);
|
|
2862
|
+
if (minecraft.manifest.logging?.client) {
|
|
2863
|
+
const logging = minecraft.manifest.logging.client;
|
|
2864
|
+
record(
|
|
2865
|
+
await verifyHashedFile({
|
|
2866
|
+
path: targetPaths.loggingConfig(directory, logging.file.id),
|
|
2867
|
+
expectedSha1: logging.file.sha1,
|
|
2868
|
+
expectedSize: logging.file.size,
|
|
2869
|
+
url: logging.file.url,
|
|
2870
|
+
category: VerifyFileCategories.LOGGING_CONFIG
|
|
2871
|
+
})
|
|
2872
|
+
);
|
|
2873
|
+
}
|
|
2874
|
+
const libraryPlan = planLibraryDownloads({
|
|
2875
|
+
libraries: minecraft.manifest.libraries,
|
|
2876
|
+
directory,
|
|
2877
|
+
system: runtime.system,
|
|
2878
|
+
versionId: minecraft.version,
|
|
2879
|
+
category: "library"
|
|
2880
|
+
});
|
|
2881
|
+
for (const action of libraryPlan.downloads) {
|
|
2882
|
+
record(
|
|
2883
|
+
await verifyHashedFile({
|
|
2884
|
+
path: action.target,
|
|
2885
|
+
expectedSha1: action.expectedSha1,
|
|
2886
|
+
expectedSize: action.expectedSize,
|
|
2887
|
+
url: action.url,
|
|
2888
|
+
category: VerifyFileCategories.LIBRARY
|
|
2889
|
+
})
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
const indexUrl = minecraft.manifest.assetIndex.url;
|
|
2893
|
+
const indexPath = targetPaths.assetIndex(directory, minecraft.manifest.assetIndex.id);
|
|
2894
|
+
record(
|
|
2895
|
+
await verifyHashedFile({
|
|
2896
|
+
path: indexPath,
|
|
2897
|
+
expectedSha1: minecraft.manifest.assetIndex.sha1,
|
|
2898
|
+
expectedSize: minecraft.manifest.assetIndex.size,
|
|
2899
|
+
url: indexUrl,
|
|
2900
|
+
category: VerifyFileCategories.ASSET_INDEX
|
|
2901
|
+
})
|
|
2902
|
+
);
|
|
2903
|
+
const indexDocument = await fetchJson(input.http, input.cache, {
|
|
2904
|
+
url: indexUrl,
|
|
2905
|
+
cacheKey: `asset-index:${minecraft.manifest.assetIndex.id}:${minecraft.manifest.assetIndex.sha1}`,
|
|
2906
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2907
|
+
});
|
|
2908
|
+
const seenAssetHashes = /* @__PURE__ */ new Set();
|
|
2909
|
+
for (const entry of Object.values(indexDocument.objects)) {
|
|
2910
|
+
if (seenAssetHashes.has(entry.hash)) continue;
|
|
2911
|
+
seenAssetHashes.add(entry.hash);
|
|
2912
|
+
record(
|
|
2913
|
+
await verifyHashedFile({
|
|
2914
|
+
path: targetPaths.assetObject(directory, entry.hash),
|
|
2915
|
+
expectedSha1: entry.hash,
|
|
2916
|
+
expectedSize: entry.size,
|
|
2917
|
+
category: VerifyFileCategories.ASSET
|
|
2918
|
+
})
|
|
2919
|
+
);
|
|
2920
|
+
}
|
|
2921
|
+
const nativesDir = targetPaths.nativesDir(directory, minecraft.version);
|
|
2922
|
+
if (!await fileExists(nativesDir)) {
|
|
2923
|
+
for (const extraction of libraryPlan.nativeExtractions) {
|
|
2924
|
+
record({
|
|
2925
|
+
path: extraction.source,
|
|
2926
|
+
category: VerifyFileCategories.NATIVE,
|
|
2927
|
+
status: VerifyFileStatuses.MISSING
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
async function verifyRuntime(input) {
|
|
2935
|
+
return runVerification(
|
|
2936
|
+
{
|
|
2937
|
+
targetId: input.target.id,
|
|
2938
|
+
kind: VerificationKinds.RUNTIME,
|
|
2939
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2940
|
+
},
|
|
2941
|
+
async (record) => {
|
|
2942
|
+
let manifest;
|
|
2943
|
+
try {
|
|
2944
|
+
manifest = await fetchJson(input.http, input.cache, {
|
|
2945
|
+
url: input.target.runtime.manifestUrl,
|
|
2946
|
+
cacheKey: `runtime-manifest:${input.target.runtime.component}:${input.target.runtime.platformKey}:${input.target.runtime.manifestSha1}`,
|
|
2947
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2948
|
+
});
|
|
2949
|
+
} catch {
|
|
2950
|
+
record({
|
|
2951
|
+
path: input.target.runtime.manifestUrl,
|
|
2952
|
+
category: VerifyFileCategories.RUNTIME_FILE,
|
|
2953
|
+
status: VerifyFileStatuses.MISSING
|
|
2954
|
+
});
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
const runtimeRoot = targetPaths.runtimeRoot(
|
|
2958
|
+
input.target.directory,
|
|
2959
|
+
input.target.runtime.component,
|
|
2960
|
+
input.target.runtime.installRoot
|
|
2961
|
+
);
|
|
2962
|
+
for (const [relative, entry] of Object.entries(manifest.files)) {
|
|
2963
|
+
if (entry.type !== "file") continue;
|
|
2964
|
+
record(
|
|
2965
|
+
await verifyHashedFile({
|
|
2966
|
+
path: path.join(runtimeRoot, relative),
|
|
2967
|
+
expectedSha1: entry.downloads.raw.sha1,
|
|
2968
|
+
expectedSize: entry.downloads.raw.size,
|
|
2969
|
+
url: entry.downloads.raw.url,
|
|
2970
|
+
category: VerifyFileCategories.RUNTIME_FILE
|
|
2971
|
+
})
|
|
2972
|
+
);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// src/repair/helpers.ts
|
|
2979
|
+
function asResultArray(from) {
|
|
2980
|
+
return Array.isArray(from) ? from : [from];
|
|
2981
|
+
}
|
|
2982
|
+
function buildIssueIndex(from) {
|
|
2983
|
+
const map = /* @__PURE__ */ new Map();
|
|
2984
|
+
for (const v of asResultArray(from)) {
|
|
2985
|
+
for (const issue of v.issues) {
|
|
2986
|
+
const set = map.get(issue.path);
|
|
2987
|
+
if (set) set.add(issue.category);
|
|
2988
|
+
else map.set(issue.path, /* @__PURE__ */ new Set([issue.category]));
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
return {
|
|
2992
|
+
has: (path16) => map.has(path16),
|
|
2993
|
+
hasNonNative: (path16) => {
|
|
2994
|
+
const cats = map.get(path16);
|
|
2995
|
+
if (!cats) return false;
|
|
2996
|
+
for (const c of cats) {
|
|
2997
|
+
if (c !== VerifyFileCategories.NATIVE) return true;
|
|
2998
|
+
}
|
|
2999
|
+
return false;
|
|
3000
|
+
},
|
|
3001
|
+
categoriesAt: (path16) => map.get(path16) ?? /* @__PURE__ */ new Set()
|
|
3002
|
+
};
|
|
3003
|
+
}
|
|
3004
|
+
function sumDownloadBytes(actions) {
|
|
3005
|
+
return actions.reduce((sum, action) => {
|
|
3006
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3007
|
+
return sum + (action.expectedSize ?? 0);
|
|
3008
|
+
}
|
|
3009
|
+
return sum;
|
|
3010
|
+
}, 0);
|
|
3011
|
+
}
|
|
3012
|
+
function buildRepairPlan(target, actions) {
|
|
3013
|
+
return {
|
|
3014
|
+
targetId: target.id,
|
|
3015
|
+
directory: target.directory,
|
|
3016
|
+
target,
|
|
3017
|
+
actions,
|
|
3018
|
+
totalActions: actions.length,
|
|
3019
|
+
totalBytes: sumDownloadBytes(actions)
|
|
3020
|
+
};
|
|
3021
|
+
}
|
|
3022
|
+
async function planAspectRepair(input, aspectFilter, postprocess) {
|
|
3023
|
+
const installPlan = await planInstall({
|
|
3024
|
+
target: input.target,
|
|
3025
|
+
http: input.http,
|
|
3026
|
+
cache: input.cache,
|
|
3027
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3028
|
+
});
|
|
3029
|
+
const issues = buildIssueIndex(input.from);
|
|
3030
|
+
const actions = selectRepairActions({
|
|
3031
|
+
target: input.target,
|
|
3032
|
+
installPlan,
|
|
3033
|
+
issues,
|
|
3034
|
+
aspectFilter
|
|
3035
|
+
});
|
|
3036
|
+
postprocess?.({ actions, installPlan, issues });
|
|
3037
|
+
return buildRepairPlan(input.target, actions);
|
|
3038
|
+
}
|
|
3039
|
+
function selectRepairActions(input) {
|
|
3040
|
+
const matching = [];
|
|
3041
|
+
for (const action of input.installPlan.actions) {
|
|
3042
|
+
if (!input.aspectFilter(action)) continue;
|
|
3043
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3044
|
+
if (input.issues.hasNonNative(action.target)) {
|
|
3045
|
+
matching.push(action);
|
|
3046
|
+
}
|
|
3047
|
+
} else if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3048
|
+
if (input.issues.has(action.path)) {
|
|
3049
|
+
matching.push(action);
|
|
3050
|
+
}
|
|
3051
|
+
} else if (action.kind === InstallActionKinds.EXTRACT_NATIVE) {
|
|
3052
|
+
if (input.issues.has(action.source)) {
|
|
3053
|
+
matching.push(action);
|
|
3054
|
+
}
|
|
3055
|
+
} else {
|
|
3056
|
+
matching.push(action);
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
return matching;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
// src/repair/fabric.ts
|
|
3063
|
+
async function planFabricRepair(input) {
|
|
3064
|
+
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
3065
|
+
throw new MinecraftKitError(
|
|
3066
|
+
"INVALID_INPUT",
|
|
3067
|
+
`repair.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
3068
|
+
);
|
|
3069
|
+
}
|
|
3070
|
+
const fabricJsonPath = targetPaths.versionJson(
|
|
3071
|
+
input.target.directory,
|
|
3072
|
+
input.target.loader.profile.id
|
|
3073
|
+
);
|
|
3074
|
+
return planAspectRepair(input, (action) => {
|
|
3075
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3076
|
+
return action.category === "fabric-library";
|
|
3077
|
+
}
|
|
3078
|
+
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3079
|
+
return action.path === fabricJsonPath;
|
|
3080
|
+
}
|
|
3081
|
+
return false;
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
// src/repair/forge.ts
|
|
3086
|
+
var FORGE_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3087
|
+
"forge-library",
|
|
3088
|
+
"forge-installer"
|
|
3089
|
+
]);
|
|
3090
|
+
async function planForgeRepair(input) {
|
|
2941
3091
|
if (input.target.loader.type !== Loaders.FORGE) {
|
|
2942
3092
|
throw new MinecraftKitError(
|
|
2943
3093
|
"INVALID_INPUT",
|
|
2944
|
-
`
|
|
3094
|
+
`repair.forge requires a Forge target (got ${input.target.loader.type})`
|
|
2945
3095
|
);
|
|
2946
3096
|
}
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
3097
|
+
const forgeJsonPath = targetPaths.versionJson(
|
|
3098
|
+
input.target.directory,
|
|
3099
|
+
input.target.loader.fullVersion
|
|
3100
|
+
);
|
|
3101
|
+
return planAspectRepair(
|
|
3102
|
+
input,
|
|
3103
|
+
(action) => {
|
|
3104
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3105
|
+
return FORGE_DOWNLOAD_CATEGORIES.has(action.category);
|
|
3106
|
+
}
|
|
3107
|
+
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3108
|
+
return action.path === forgeJsonPath;
|
|
3109
|
+
}
|
|
3110
|
+
return false;
|
|
2952
3111
|
},
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
);
|
|
2958
|
-
if (forgeVersionJsonPath === null) return;
|
|
2959
|
-
record(
|
|
2960
|
-
await verifyExistence({
|
|
2961
|
-
path: forgeVersionJsonPath,
|
|
2962
|
-
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2963
|
-
})
|
|
3112
|
+
({ actions, installPlan, issues }) => {
|
|
3113
|
+
if (!issues.has(forgeJsonPath)) return;
|
|
3114
|
+
const alreadyIncluded = new Set(
|
|
3115
|
+
actions.filter((a) => a.kind === InstallActionKinds.DOWNLOAD_FILE).map((a) => a.target)
|
|
2964
3116
|
);
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
path: forgeVersionJsonPath,
|
|
2972
|
-
category: VerifyFileCategories.LOADER_LIBRARY,
|
|
2973
|
-
status: VerifyFileStatuses.CORRUPT
|
|
2974
|
-
});
|
|
2975
|
-
return;
|
|
2976
|
-
}
|
|
2977
|
-
const forgeLibraries = planLibraryDownloads({
|
|
2978
|
-
libraries: parsed.libraries,
|
|
2979
|
-
directory: input.target.directory,
|
|
2980
|
-
system: input.target.runtime.system,
|
|
2981
|
-
versionId: input.target.minecraft.version,
|
|
2982
|
-
category: "forge-library"
|
|
2983
|
-
});
|
|
2984
|
-
for (const action of forgeLibraries.downloads) {
|
|
2985
|
-
record(
|
|
2986
|
-
await verifyHashedFile({
|
|
2987
|
-
path: action.target,
|
|
2988
|
-
expectedSha1: action.expectedSha1,
|
|
2989
|
-
expectedSize: action.expectedSize,
|
|
2990
|
-
...action.url ? { url: action.url } : {},
|
|
2991
|
-
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2992
|
-
})
|
|
2993
|
-
);
|
|
3117
|
+
for (const action of installPlan.actions) {
|
|
3118
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category === "forge-library" && !alreadyIncluded.has(action.target)) {
|
|
3119
|
+
actions.push(action);
|
|
3120
|
+
} else if (action.kind === InstallActionKinds.RUN_FORGE_PROCESSOR) {
|
|
3121
|
+
actions.push(action);
|
|
3122
|
+
}
|
|
2994
3123
|
}
|
|
2995
3124
|
}
|
|
2996
3125
|
);
|
|
2997
3126
|
}
|
|
2998
3127
|
|
|
2999
|
-
// src/
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3128
|
+
// src/repair/minecraft.ts
|
|
3129
|
+
var MINECRAFT_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3130
|
+
"client-jar",
|
|
3131
|
+
"library",
|
|
3132
|
+
"asset-index",
|
|
3133
|
+
"asset",
|
|
3134
|
+
"logging-config"
|
|
3135
|
+
]);
|
|
3136
|
+
async function planMinecraftRepair(input) {
|
|
3137
|
+
const vanillaJsonPath = targetPaths.versionJson(
|
|
3138
|
+
input.target.directory,
|
|
3139
|
+
input.target.minecraft.version
|
|
3140
|
+
);
|
|
3141
|
+
return planAspectRepair(input, (action) => {
|
|
3142
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3143
|
+
return MINECRAFT_DOWNLOAD_CATEGORIES.has(action.category);
|
|
3144
|
+
}
|
|
3145
|
+
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3146
|
+
return action.path === vanillaJsonPath;
|
|
3147
|
+
}
|
|
3148
|
+
if (action.kind === InstallActionKinds.EXTRACT_NATIVE) {
|
|
3149
|
+
return true;
|
|
3150
|
+
}
|
|
3151
|
+
return false;
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
// src/repair/runner.ts
|
|
3156
|
+
async function runRepair(input) {
|
|
3157
|
+
const report = await runInstall({
|
|
3158
|
+
plan: {
|
|
3159
|
+
...input.plan,
|
|
3160
|
+
totalActions: input.plan.actions.length,
|
|
3161
|
+
totalBytes: input.plan.totalBytes
|
|
3006
3162
|
},
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3163
|
+
http: input.http,
|
|
3164
|
+
cache: input.cache,
|
|
3165
|
+
spawner: input.spawner,
|
|
3166
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
3167
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
3168
|
+
});
|
|
3169
|
+
return {
|
|
3170
|
+
targetId: report.targetId,
|
|
3171
|
+
bytesDownloaded: report.bytesDownloaded,
|
|
3172
|
+
actionsCompleted: report.actionsCompleted,
|
|
3173
|
+
durationMs: report.durationMs
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
// src/repair/runtime.ts
|
|
3178
|
+
async function planRuntimeRepair(input) {
|
|
3179
|
+
return planAspectRepair(
|
|
3180
|
+
input,
|
|
3181
|
+
(action) => action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category === "runtime-file"
|
|
3182
|
+
);
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
// src/repair/all.ts
|
|
3186
|
+
async function repairAll(input) {
|
|
3187
|
+
const startedAt = Date.now();
|
|
3188
|
+
const ctx = {
|
|
3189
|
+
target: input.target,
|
|
3190
|
+
http: input.http,
|
|
3191
|
+
cache: input.cache,
|
|
3192
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3193
|
+
};
|
|
3194
|
+
const verifications = [];
|
|
3195
|
+
const mc = await verifyMinecraft(ctx);
|
|
3196
|
+
verifications.push(mc);
|
|
3197
|
+
const rt = await verifyRuntime(ctx);
|
|
3198
|
+
verifications.push(rt);
|
|
3199
|
+
if (input.target.loader.type === Loaders.FABRIC) {
|
|
3200
|
+
verifications.push(await verifyFabric(ctx));
|
|
3201
|
+
} else if (input.target.loader.type === Loaders.FORGE) {
|
|
3202
|
+
verifications.push(await verifyForge(ctx));
|
|
3203
|
+
}
|
|
3204
|
+
const repairs = /* @__PURE__ */ new Map();
|
|
3205
|
+
let bytesDownloaded = 0;
|
|
3206
|
+
for (const verification of verifications) {
|
|
3207
|
+
if (verification.isValid) continue;
|
|
3208
|
+
const planner = PLANNERS[verification.kind];
|
|
3209
|
+
if (!planner) continue;
|
|
3210
|
+
const plan = await planner({ ...ctx, from: verification });
|
|
3211
|
+
if (plan.totalActions === 0) continue;
|
|
3212
|
+
const report = await runRepair({
|
|
3213
|
+
plan,
|
|
3214
|
+
http: input.http,
|
|
3215
|
+
cache: input.cache,
|
|
3216
|
+
spawner: input.spawner,
|
|
3217
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
3218
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
3219
|
+
});
|
|
3220
|
+
repairs.set(verification.kind, report);
|
|
3221
|
+
bytesDownloaded += report.bytesDownloaded;
|
|
3222
|
+
}
|
|
3223
|
+
return {
|
|
3224
|
+
verifications,
|
|
3225
|
+
repairs,
|
|
3226
|
+
bytesDownloaded,
|
|
3227
|
+
durationMs: Date.now() - startedAt
|
|
3228
|
+
};
|
|
3229
|
+
}
|
|
3230
|
+
var PLANNERS = {
|
|
3231
|
+
minecraft: planMinecraftRepair,
|
|
3232
|
+
runtime: planRuntimeRepair,
|
|
3233
|
+
fabric: planFabricRepair,
|
|
3234
|
+
forge: planForgeRepair
|
|
3235
|
+
};
|
|
3236
|
+
|
|
3237
|
+
// src/types/runtime.ts
|
|
3238
|
+
var RuntimeComponents = {
|
|
3239
|
+
JRE_LEGACY: "jre-legacy"};
|
|
3240
|
+
var RuntimePreference = {
|
|
3241
|
+
/** Component declared by the Minecraft manifest. */
|
|
3242
|
+
RECOMMENDED: "recommended",
|
|
3243
|
+
/** Newest component available for the platform. */
|
|
3244
|
+
LATEST: "latest"
|
|
3245
|
+
};
|
|
3246
|
+
|
|
3247
|
+
// src/targets/index.ts
|
|
3248
|
+
var TargetsApi = class {
|
|
3249
|
+
constructor(ctx) {
|
|
3250
|
+
this.ctx = ctx;
|
|
3251
|
+
}
|
|
3252
|
+
ctx;
|
|
3253
|
+
/** The detected host system used by `resolve()` when no `system` is supplied. */
|
|
3254
|
+
get system() {
|
|
3255
|
+
return this.ctx.system;
|
|
3256
|
+
}
|
|
3257
|
+
/** Build a {@link Target} from already-resolved components. */
|
|
3258
|
+
create(input) {
|
|
3259
|
+
if (!input.id) {
|
|
3260
|
+
throw new MinecraftKitError("INVALID_INPUT", "Target id must be non-empty");
|
|
3261
|
+
}
|
|
3262
|
+
if (!input.directory) {
|
|
3263
|
+
throw new MinecraftKitError("INVALID_INPUT", "Target directory must be non-empty");
|
|
3264
|
+
}
|
|
3265
|
+
if (input.loader.minecraftVersion !== input.minecraft.version) {
|
|
3266
|
+
throw new MinecraftKitError(
|
|
3267
|
+
"INVALID_INPUT",
|
|
3268
|
+
`Loader Minecraft version (${input.loader.minecraftVersion}) does not match resolved Minecraft (${input.minecraft.version})`,
|
|
3269
|
+
{
|
|
3270
|
+
context: {
|
|
3271
|
+
loaderMinecraft: input.loader.minecraftVersion,
|
|
3272
|
+
minecraftVersion: input.minecraft.version
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3023
3275
|
);
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3276
|
+
}
|
|
3277
|
+
return {
|
|
3278
|
+
id: input.id,
|
|
3279
|
+
directory: input.directory,
|
|
3280
|
+
minecraft: input.minecraft,
|
|
3281
|
+
loader: input.loader,
|
|
3282
|
+
runtime: input.runtime
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
/** Sugar API: resolve every component then assemble a target. */
|
|
3286
|
+
async resolve(input) {
|
|
3287
|
+
const minecraft = await this.ctx.minecraft.resolve({
|
|
3288
|
+
version: input.minecraft.version,
|
|
3289
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3290
|
+
});
|
|
3291
|
+
const system = input.system ?? this.ctx.system;
|
|
3292
|
+
const componentOverride = input.runtime?.component;
|
|
3293
|
+
const runtimeComponent = componentOverride ?? minecraft.manifest.javaVersion?.component;
|
|
3294
|
+
const resolvedRuntime = await this.ctx.runtime.resolve({
|
|
3295
|
+
system,
|
|
3296
|
+
...runtimeComponent !== void 0 ? { component: runtimeComponent } : {},
|
|
3297
|
+
preference: input.runtime?.preference ?? RuntimePreference.RECOMMENDED,
|
|
3298
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3299
|
+
});
|
|
3300
|
+
const runtime = input.runtime?.installRoot !== void 0 ? { ...resolvedRuntime, installRoot: input.runtime.installRoot } : resolvedRuntime;
|
|
3301
|
+
let loader;
|
|
3302
|
+
if (input.loader.type === Loaders.VANILLA) {
|
|
3303
|
+
loader = {
|
|
3304
|
+
type: Loaders.VANILLA,
|
|
3305
|
+
minecraftVersion: minecraft.version,
|
|
3306
|
+
minecraft
|
|
3307
|
+
};
|
|
3308
|
+
} else if (input.loader.type === Loaders.FABRIC) {
|
|
3309
|
+
loader = await this.ctx.fabric.resolve({
|
|
3310
|
+
minecraftVersion: minecraft.version,
|
|
3311
|
+
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
3312
|
+
...input.loader.version !== void 0 ? { loaderVersion: input.loader.version } : {},
|
|
3313
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3042
3314
|
});
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
expectedSize: action.expectedSize,
|
|
3049
|
-
url: action.url,
|
|
3050
|
-
category: VerifyFileCategories.LIBRARY
|
|
3051
|
-
})
|
|
3052
|
-
);
|
|
3053
|
-
}
|
|
3054
|
-
const indexUrl = minecraft.manifest.assetIndex.url;
|
|
3055
|
-
const indexPath = targetPaths.assetIndex(directory, minecraft.manifest.assetIndex.id);
|
|
3056
|
-
record(
|
|
3057
|
-
await verifyHashedFile({
|
|
3058
|
-
path: indexPath,
|
|
3059
|
-
expectedSha1: minecraft.manifest.assetIndex.sha1,
|
|
3060
|
-
expectedSize: minecraft.manifest.assetIndex.size,
|
|
3061
|
-
url: indexUrl,
|
|
3062
|
-
category: VerifyFileCategories.ASSET_INDEX
|
|
3063
|
-
})
|
|
3064
|
-
);
|
|
3065
|
-
const indexDocument = await fetchJson(input.http, input.cache, {
|
|
3066
|
-
url: indexUrl,
|
|
3067
|
-
cacheKey: `asset-index:${minecraft.manifest.assetIndex.id}:${minecraft.manifest.assetIndex.sha1}`,
|
|
3315
|
+
} else {
|
|
3316
|
+
loader = await this.ctx.forge.resolve({
|
|
3317
|
+
minecraftVersion: minecraft.version,
|
|
3318
|
+
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
3319
|
+
...input.loader.version !== void 0 ? { forgeVersion: input.loader.version } : {},
|
|
3068
3320
|
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3069
3321
|
});
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3322
|
+
}
|
|
3323
|
+
return this.create({
|
|
3324
|
+
id: input.id,
|
|
3325
|
+
directory: input.directory,
|
|
3326
|
+
minecraft,
|
|
3327
|
+
loader,
|
|
3328
|
+
runtime
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
3331
|
+
/** Scan a root directory for Minecraft installations. Returns only what is on disk. */
|
|
3332
|
+
async list(input) {
|
|
3333
|
+
if (!await dirExists(input.rootDir)) return [];
|
|
3334
|
+
const subdirs = await listChildDirectories(input.rootDir);
|
|
3335
|
+
const results = [];
|
|
3336
|
+
for (const id of subdirs) {
|
|
3337
|
+
const directory = path.join(input.rootDir, id);
|
|
3338
|
+
const discovered = await discoverInstallation(id, directory);
|
|
3339
|
+
if (discovered) results.push(discovered);
|
|
3340
|
+
}
|
|
3341
|
+
return results;
|
|
3342
|
+
}
|
|
3343
|
+
};
|
|
3344
|
+
async function discoverInstallation(id, directory) {
|
|
3345
|
+
const versionsDir = path.join(directory, VERSIONS_DIR);
|
|
3346
|
+
const librariesDir = path.join(directory, LIBRARIES_DIR);
|
|
3347
|
+
const assetsDir = path.join(directory, ASSETS_DIR);
|
|
3348
|
+
const looksLikeInstall = await dirExists(versionsDir) && (await dirExists(librariesDir) || await dirExists(assetsDir));
|
|
3349
|
+
if (!looksLikeInstall) return null;
|
|
3350
|
+
const versionDirs = await listChildDirectories(versionsDir);
|
|
3351
|
+
const minecraftVersions = [];
|
|
3352
|
+
const loaders = [];
|
|
3353
|
+
for (const versionId of versionDirs) {
|
|
3354
|
+
const hint = inferLoaderFromVersionId(versionId);
|
|
3355
|
+
if (hint) {
|
|
3356
|
+
loaders.push(hint);
|
|
3357
|
+
if (hint.minecraftVersion && !minecraftVersions.includes(hint.minecraftVersion)) {
|
|
3358
|
+
minecraftVersions.push(hint.minecraftVersion);
|
|
3092
3359
|
}
|
|
3360
|
+
} else {
|
|
3361
|
+
minecraftVersions.push(versionId);
|
|
3093
3362
|
}
|
|
3094
|
-
|
|
3363
|
+
}
|
|
3364
|
+
const runtime = await discoverRuntime(directory);
|
|
3365
|
+
return { id, directory, minecraftVersions, loaders, ...runtime ? { runtime } : {} };
|
|
3095
3366
|
}
|
|
3096
|
-
async function
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
});
|
|
3111
|
-
} catch {
|
|
3112
|
-
record({
|
|
3113
|
-
path: input.target.runtime.manifestUrl,
|
|
3114
|
-
category: VerifyFileCategories.RUNTIME_FILE,
|
|
3115
|
-
status: VerifyFileStatuses.MISSING
|
|
3116
|
-
});
|
|
3117
|
-
return;
|
|
3118
|
-
}
|
|
3119
|
-
const runtimeRoot = targetPaths.runtimeRoot(
|
|
3120
|
-
input.target.directory,
|
|
3121
|
-
input.target.runtime.component,
|
|
3122
|
-
input.target.runtime.installRoot
|
|
3123
|
-
);
|
|
3124
|
-
for (const [relative, entry] of Object.entries(manifest.files)) {
|
|
3125
|
-
if (entry.type !== "file") continue;
|
|
3126
|
-
record(
|
|
3127
|
-
await verifyHashedFile({
|
|
3128
|
-
path: path.join(runtimeRoot, relative),
|
|
3129
|
-
expectedSha1: entry.downloads.raw.sha1,
|
|
3130
|
-
expectedSize: entry.downloads.raw.size,
|
|
3131
|
-
url: entry.downloads.raw.url,
|
|
3132
|
-
category: VerifyFileCategories.RUNTIME_FILE
|
|
3133
|
-
})
|
|
3134
|
-
);
|
|
3135
|
-
}
|
|
3367
|
+
async function discoverRuntime(directory) {
|
|
3368
|
+
const runtimeDir = path.join(directory, RUNTIMES_DIR);
|
|
3369
|
+
if (!await dirExists(runtimeDir)) return void 0;
|
|
3370
|
+
let components;
|
|
3371
|
+
try {
|
|
3372
|
+
components = await listChildDirectories(runtimeDir);
|
|
3373
|
+
} catch {
|
|
3374
|
+
return void 0;
|
|
3375
|
+
}
|
|
3376
|
+
for (const component of components) {
|
|
3377
|
+
const root = path.join(runtimeDir, component);
|
|
3378
|
+
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");
|
|
3379
|
+
if (await fileExists(javaPath)) {
|
|
3380
|
+
return { component, javaPath };
|
|
3136
3381
|
}
|
|
3137
|
-
|
|
3382
|
+
}
|
|
3383
|
+
return void 0;
|
|
3384
|
+
}
|
|
3385
|
+
function inferLoaderFromVersionId(versionId) {
|
|
3386
|
+
const fabricMatch = /^fabric-loader-([^-]+)-(.+)$/.exec(versionId);
|
|
3387
|
+
if (fabricMatch?.[1] && fabricMatch[2]) {
|
|
3388
|
+
return { type: Loaders.FABRIC, version: fabricMatch[1], minecraftVersion: fabricMatch[2] };
|
|
3389
|
+
}
|
|
3390
|
+
const forgeMatch = /^([^-]+)-forge-(.+)$/.exec(versionId);
|
|
3391
|
+
if (forgeMatch?.[1] && forgeMatch[2]) {
|
|
3392
|
+
return { type: Loaders.FORGE, minecraftVersion: forgeMatch[1], version: forgeMatch[2] };
|
|
3393
|
+
}
|
|
3394
|
+
return null;
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
// src/update/runner.ts
|
|
3398
|
+
async function planUpdate(input) {
|
|
3399
|
+
return planInstall({
|
|
3400
|
+
target: input.target,
|
|
3401
|
+
http: input.http,
|
|
3402
|
+
cache: input.cache,
|
|
3403
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
async function runUpdate(input) {
|
|
3407
|
+
const report = await runInstall({
|
|
3408
|
+
plan: input.plan,
|
|
3409
|
+
http: input.http,
|
|
3410
|
+
cache: input.cache,
|
|
3411
|
+
spawner: input.spawner,
|
|
3412
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
3413
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
3414
|
+
});
|
|
3415
|
+
return {
|
|
3416
|
+
targetId: report.targetId,
|
|
3417
|
+
bytesDownloaded: report.bytesDownloaded,
|
|
3418
|
+
actionsCompleted: report.actionsCompleted,
|
|
3419
|
+
actionsSkipped: report.actionsSkipped,
|
|
3420
|
+
durationMs: report.durationMs
|
|
3421
|
+
};
|
|
3138
3422
|
}
|
|
3139
3423
|
|
|
3140
3424
|
// src/versions/fabric.ts
|
|
@@ -3468,15 +3752,23 @@ function pickLatestAcrossComponents(entries) {
|
|
|
3468
3752
|
return { component: bestComponent, entry: bestEntry };
|
|
3469
3753
|
}
|
|
3470
3754
|
function toResolved(component, platformKey, entry, system) {
|
|
3755
|
+
const majorVersion = parseMajorVersion(entry.version.name);
|
|
3471
3756
|
return {
|
|
3472
3757
|
component,
|
|
3473
3758
|
platformKey,
|
|
3474
3759
|
versionName: entry.version.name,
|
|
3760
|
+
...majorVersion !== void 0 ? { majorVersion } : {},
|
|
3475
3761
|
system,
|
|
3476
3762
|
manifestUrl: entry.manifest.url,
|
|
3477
3763
|
manifestSha1: entry.manifest.sha1
|
|
3478
3764
|
};
|
|
3479
3765
|
}
|
|
3766
|
+
function parseMajorVersion(versionName) {
|
|
3767
|
+
const match = /^(\d+)/.exec(versionName);
|
|
3768
|
+
if (!match || !match[1]) return void 0;
|
|
3769
|
+
const parsed = Number.parseInt(match[1], 10);
|
|
3770
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3771
|
+
}
|
|
3480
3772
|
|
|
3481
3773
|
// src/kit.ts
|
|
3482
3774
|
var MinecraftKit = class {
|
|
@@ -3507,7 +3799,12 @@ var MinecraftKit = class {
|
|
|
3507
3799
|
...opts?.signal !== void 0 ? { signal: opts.signal } : {},
|
|
3508
3800
|
...opts?.onEvent !== void 0 ? { onEvent: opts.onEvent } : {}
|
|
3509
3801
|
});
|
|
3510
|
-
const
|
|
3802
|
+
const carryInstall = (opts) => ({
|
|
3803
|
+
...carry(opts),
|
|
3804
|
+
...opts?.pauseController !== void 0 ? { pauseController: opts.pauseController } : {},
|
|
3805
|
+
...opts?.actionCategories !== void 0 ? { actionCategories: opts.actionCategories } : {}
|
|
3806
|
+
});
|
|
3807
|
+
const runInstallPlan = (plan, opts) => runInstall({ plan, http, cache, spawner, ...carryInstall(opts) });
|
|
3511
3808
|
this.install = {
|
|
3512
3809
|
plan: (target, opts) => planInstall({ target, http, cache, ...carry(opts) }),
|
|
3513
3810
|
run: runInstallPlan,
|
|
@@ -3557,10 +3854,17 @@ var MinecraftKit = class {
|
|
|
3557
3854
|
runtime: {
|
|
3558
3855
|
plan: (target, opts) => planRuntimeRepair(repairArgs(target, opts)),
|
|
3559
3856
|
run: runRepairPlan
|
|
3560
|
-
}
|
|
3857
|
+
},
|
|
3858
|
+
all: (target, opts) => repairAll({
|
|
3859
|
+
target,
|
|
3860
|
+
http,
|
|
3861
|
+
cache,
|
|
3862
|
+
spawner,
|
|
3863
|
+
...carry(opts)
|
|
3864
|
+
})
|
|
3561
3865
|
};
|
|
3562
3866
|
this.launch = {
|
|
3563
|
-
compose: (target, opts) => composeLaunch({ target, options: opts }),
|
|
3867
|
+
compose: (target, opts) => composeLaunch({ target, options: opts, logger }),
|
|
3564
3868
|
run: (composition, opts) => runLaunch({
|
|
3565
3869
|
composition,
|
|
3566
3870
|
...opts !== void 0 ? { options: opts } : {},
|
|
@@ -5020,5 +5324,5 @@ async function bin() {
|
|
|
5020
5324
|
|
|
5021
5325
|
// src/cli/index.ts
|
|
5022
5326
|
void bin();
|
|
5023
|
-
//# sourceMappingURL=index.
|
|
5024
|
-
//# sourceMappingURL=index.
|
|
5327
|
+
//# sourceMappingURL=index.mjs.map
|
|
5328
|
+
//# sourceMappingURL=index.mjs.map
|