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