@loontail/minecraft-kit 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +1018 -715
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +1017 -713
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +1210 -663
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -220
- package/dist/index.d.ts +341 -220
- package/dist/index.mjs +1202 -662
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -9,7 +9,7 @@ var yauzl = require('yauzl');
|
|
|
9
9
|
var crypto2 = require('crypto');
|
|
10
10
|
var fs = require('fs/promises');
|
|
11
11
|
var stream = require('stream');
|
|
12
|
-
var
|
|
12
|
+
var async_hooks = require('async_hooks');
|
|
13
13
|
var buffer = require('buffer');
|
|
14
14
|
var child_process = require('child_process');
|
|
15
15
|
|
|
@@ -20,7 +20,6 @@ var path__default = /*#__PURE__*/_interopDefault(path);
|
|
|
20
20
|
var yauzl__default = /*#__PURE__*/_interopDefault(yauzl);
|
|
21
21
|
var crypto2__default = /*#__PURE__*/_interopDefault(crypto2);
|
|
22
22
|
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
23
|
-
var pLimit__default = /*#__PURE__*/_interopDefault(pLimit);
|
|
24
23
|
|
|
25
24
|
// src/types/logger.ts
|
|
26
25
|
var LogLevels = {
|
|
@@ -1123,6 +1122,12 @@ async function downloadFile(http, input) {
|
|
|
1123
1122
|
const sourceIterable = response.stream();
|
|
1124
1123
|
const counting = (async function* () {
|
|
1125
1124
|
for await (const chunk of sourceIterable) {
|
|
1125
|
+
if (input.pauseController?.paused) {
|
|
1126
|
+
await input.pauseController.waitWhilePaused();
|
|
1127
|
+
}
|
|
1128
|
+
if (input.signal?.aborted) {
|
|
1129
|
+
throw new MinecraftKitError("LAUNCH_ABORTED", "Download aborted by signal");
|
|
1130
|
+
}
|
|
1126
1131
|
bytesDownloaded += chunk.byteLength;
|
|
1127
1132
|
hash.update(chunk);
|
|
1128
1133
|
input.onEvent?.({
|
|
@@ -1451,7 +1456,8 @@ function substituteToken(raw, tokens) {
|
|
|
1451
1456
|
});
|
|
1452
1457
|
}
|
|
1453
1458
|
function stripLiteralPrefix(value) {
|
|
1454
|
-
|
|
1459
|
+
const stripped = value.startsWith("'") ? value.slice(1) : value;
|
|
1460
|
+
return stripped.endsWith("'") ? stripped.slice(0, -1) : stripped;
|
|
1455
1461
|
}
|
|
1456
1462
|
async function planRuntimeDownloads(input) {
|
|
1457
1463
|
const manifest = await fetchJson(input.http, input.cache, {
|
|
@@ -1574,6 +1580,123 @@ async function planInstall(input) {
|
|
|
1574
1580
|
totalBytes
|
|
1575
1581
|
};
|
|
1576
1582
|
}
|
|
1583
|
+
|
|
1584
|
+
// node_modules/yocto-queue/index.js
|
|
1585
|
+
var Node = class {
|
|
1586
|
+
value;
|
|
1587
|
+
next;
|
|
1588
|
+
constructor(value) {
|
|
1589
|
+
this.value = value;
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
var Queue = class {
|
|
1593
|
+
#head;
|
|
1594
|
+
#tail;
|
|
1595
|
+
#size;
|
|
1596
|
+
constructor() {
|
|
1597
|
+
this.clear();
|
|
1598
|
+
}
|
|
1599
|
+
enqueue(value) {
|
|
1600
|
+
const node = new Node(value);
|
|
1601
|
+
if (this.#head) {
|
|
1602
|
+
this.#tail.next = node;
|
|
1603
|
+
this.#tail = node;
|
|
1604
|
+
} else {
|
|
1605
|
+
this.#head = node;
|
|
1606
|
+
this.#tail = node;
|
|
1607
|
+
}
|
|
1608
|
+
this.#size++;
|
|
1609
|
+
}
|
|
1610
|
+
dequeue() {
|
|
1611
|
+
const current = this.#head;
|
|
1612
|
+
if (!current) {
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
this.#head = this.#head.next;
|
|
1616
|
+
this.#size--;
|
|
1617
|
+
if (!this.#head) {
|
|
1618
|
+
this.#tail = void 0;
|
|
1619
|
+
}
|
|
1620
|
+
return current.value;
|
|
1621
|
+
}
|
|
1622
|
+
peek() {
|
|
1623
|
+
if (!this.#head) {
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
return this.#head.value;
|
|
1627
|
+
}
|
|
1628
|
+
clear() {
|
|
1629
|
+
this.#head = void 0;
|
|
1630
|
+
this.#tail = void 0;
|
|
1631
|
+
this.#size = 0;
|
|
1632
|
+
}
|
|
1633
|
+
get size() {
|
|
1634
|
+
return this.#size;
|
|
1635
|
+
}
|
|
1636
|
+
*[Symbol.iterator]() {
|
|
1637
|
+
let current = this.#head;
|
|
1638
|
+
while (current) {
|
|
1639
|
+
yield current.value;
|
|
1640
|
+
current = current.next;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
*drain() {
|
|
1644
|
+
while (this.#head) {
|
|
1645
|
+
yield this.dequeue();
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
function pLimit(concurrency) {
|
|
1650
|
+
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
|
1651
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
1652
|
+
}
|
|
1653
|
+
const queue = new Queue();
|
|
1654
|
+
let activeCount = 0;
|
|
1655
|
+
const next = () => {
|
|
1656
|
+
activeCount--;
|
|
1657
|
+
if (queue.size > 0) {
|
|
1658
|
+
queue.dequeue()();
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
const run = async (function_, resolve, arguments_) => {
|
|
1662
|
+
activeCount++;
|
|
1663
|
+
const result = (async () => function_(...arguments_))();
|
|
1664
|
+
resolve(result);
|
|
1665
|
+
try {
|
|
1666
|
+
await result;
|
|
1667
|
+
} catch {
|
|
1668
|
+
}
|
|
1669
|
+
next();
|
|
1670
|
+
};
|
|
1671
|
+
const enqueue = (function_, resolve, arguments_) => {
|
|
1672
|
+
queue.enqueue(
|
|
1673
|
+
async_hooks.AsyncResource.bind(run.bind(void 0, function_, resolve, arguments_))
|
|
1674
|
+
);
|
|
1675
|
+
(async () => {
|
|
1676
|
+
await Promise.resolve();
|
|
1677
|
+
if (activeCount < concurrency && queue.size > 0) {
|
|
1678
|
+
queue.dequeue()();
|
|
1679
|
+
}
|
|
1680
|
+
})();
|
|
1681
|
+
};
|
|
1682
|
+
const generator = (function_, ...arguments_) => new Promise((resolve) => {
|
|
1683
|
+
enqueue(function_, resolve, arguments_);
|
|
1684
|
+
});
|
|
1685
|
+
Object.defineProperties(generator, {
|
|
1686
|
+
activeCount: {
|
|
1687
|
+
get: () => activeCount
|
|
1688
|
+
},
|
|
1689
|
+
pendingCount: {
|
|
1690
|
+
get: () => queue.size
|
|
1691
|
+
},
|
|
1692
|
+
clearQueue: {
|
|
1693
|
+
value() {
|
|
1694
|
+
queue.clear();
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
return generator;
|
|
1699
|
+
}
|
|
1577
1700
|
async function materializeRuntimeExtras(input) {
|
|
1578
1701
|
const root = targetPaths.runtimeRoot(
|
|
1579
1702
|
input.directory,
|
|
@@ -1638,6 +1761,16 @@ function errorMessage(error) {
|
|
|
1638
1761
|
}
|
|
1639
1762
|
|
|
1640
1763
|
// src/install/runner.ts
|
|
1764
|
+
var DOWNLOAD_GROUPS = [
|
|
1765
|
+
{ categories: ["runtime-file"], phase: InstallPhases.INSTALLING_RUNTIME },
|
|
1766
|
+
{ categories: ["client-jar"], phase: InstallPhases.DOWNLOADING_CLIENT_JAR },
|
|
1767
|
+
{ categories: ["library"], phase: InstallPhases.DOWNLOADING_LIBRARIES },
|
|
1768
|
+
{ categories: ["asset-index"], phase: InstallPhases.DOWNLOADING_ASSET_INDEX },
|
|
1769
|
+
{ categories: ["asset"], phase: InstallPhases.DOWNLOADING_ASSETS },
|
|
1770
|
+
{ categories: ["logging-config"], phase: InstallPhases.WRITING_FILES },
|
|
1771
|
+
{ categories: ["fabric-library"], phase: InstallPhases.INSTALLING_FABRIC },
|
|
1772
|
+
{ categories: ["forge-installer", "forge-library"], phase: InstallPhases.INSTALLING_FORGE }
|
|
1773
|
+
];
|
|
1641
1774
|
async function runInstall(input) {
|
|
1642
1775
|
const startedAt = Date.now();
|
|
1643
1776
|
let bytesDownloaded = 0;
|
|
@@ -1650,50 +1783,89 @@ async function runInstall(input) {
|
|
|
1650
1783
|
onEvent?.({ type: "install:phase-changed", phase, previous: currentPhase });
|
|
1651
1784
|
currentPhase = phase;
|
|
1652
1785
|
};
|
|
1653
|
-
const
|
|
1786
|
+
const checkpoint = async () => {
|
|
1787
|
+
if (input.signal?.aborted) {
|
|
1788
|
+
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1789
|
+
}
|
|
1790
|
+
await input.pauseController?.waitWhilePaused();
|
|
1791
|
+
if (input.signal?.aborted) {
|
|
1792
|
+
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
const categoryFilter = input.actionCategories;
|
|
1796
|
+
const downloads = input.plan.actions.filter(isDownload).filter((a) => categoryFilter ? categoryFilter.has(a.category) : true);
|
|
1654
1797
|
const natives = input.plan.actions.filter(isNative);
|
|
1655
1798
|
const writeActions = input.plan.actions.filter(isWrite);
|
|
1656
1799
|
const processors = input.plan.actions.filter(isProcessor);
|
|
1657
1800
|
enterPhase(InstallPhases.PLANNING);
|
|
1658
|
-
|
|
1659
|
-
const
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1801
|
+
const limit = pLimit(input.concurrency ?? DOWNLOAD_CONCURRENCY);
|
|
1802
|
+
for (const group of DOWNLOAD_GROUPS) {
|
|
1803
|
+
const groupActions = downloads.filter((action) => group.categories.includes(action.category));
|
|
1804
|
+
if (groupActions.length === 0) continue;
|
|
1805
|
+
await checkpoint();
|
|
1806
|
+
enterPhase(group.phase);
|
|
1807
|
+
await Promise.all(
|
|
1808
|
+
groupActions.map(
|
|
1809
|
+
(action) => limit(async () => {
|
|
1810
|
+
await checkpoint();
|
|
1811
|
+
const result = await downloadFile(input.http, {
|
|
1812
|
+
url: action.url,
|
|
1813
|
+
target: action.target,
|
|
1814
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
1815
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
1816
|
+
...action.category !== void 0 ? { category: action.category } : {},
|
|
1817
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1818
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1819
|
+
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1820
|
+
});
|
|
1821
|
+
bytesDownloaded += result.bytesDownloaded;
|
|
1822
|
+
if (result.skipped) actionsSkipped++;
|
|
1823
|
+
actionsCompleted++;
|
|
1824
|
+
})
|
|
1825
|
+
)
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
const ungrouped = downloads.filter(
|
|
1829
|
+
(action) => !DOWNLOAD_GROUPS.some((g) => g.categories.includes(action.category))
|
|
1680
1830
|
);
|
|
1831
|
+
if (ungrouped.length > 0) {
|
|
1832
|
+
await checkpoint();
|
|
1833
|
+
enterPhase(InstallPhases.DOWNLOADING_LIBRARIES);
|
|
1834
|
+
await Promise.all(
|
|
1835
|
+
ungrouped.map(
|
|
1836
|
+
(action) => limit(async () => {
|
|
1837
|
+
await checkpoint();
|
|
1838
|
+
const result = await downloadFile(input.http, {
|
|
1839
|
+
url: action.url,
|
|
1840
|
+
target: action.target,
|
|
1841
|
+
...action.expectedSha1 !== void 0 ? { expectedSha1: action.expectedSha1 } : {},
|
|
1842
|
+
...action.expectedSize !== void 0 ? { expectedSize: action.expectedSize } : {},
|
|
1843
|
+
...action.category !== void 0 ? { category: action.category } : {},
|
|
1844
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
1845
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {},
|
|
1846
|
+
...input.pauseController !== void 0 ? { pauseController: input.pauseController } : {}
|
|
1847
|
+
});
|
|
1848
|
+
bytesDownloaded += result.bytesDownloaded;
|
|
1849
|
+
if (result.skipped) actionsSkipped++;
|
|
1850
|
+
actionsCompleted++;
|
|
1851
|
+
})
|
|
1852
|
+
)
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1681
1855
|
if (writeActions.length > 0) {
|
|
1856
|
+
await checkpoint();
|
|
1682
1857
|
enterPhase(InstallPhases.WRITING_FILES);
|
|
1683
1858
|
for (const action of writeActions) {
|
|
1684
|
-
|
|
1685
|
-
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1686
|
-
}
|
|
1859
|
+
await checkpoint();
|
|
1687
1860
|
await atomicWrite(action.path, action.content);
|
|
1688
1861
|
actionsCompleted++;
|
|
1689
1862
|
}
|
|
1690
1863
|
}
|
|
1691
1864
|
if (natives.length > 0) {
|
|
1865
|
+
await checkpoint();
|
|
1692
1866
|
enterPhase(InstallPhases.EXTRACTING_NATIVES);
|
|
1693
1867
|
for (const action of natives) {
|
|
1694
|
-
|
|
1695
|
-
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1696
|
-
}
|
|
1868
|
+
await checkpoint();
|
|
1697
1869
|
const { fileCount } = await extractAllToDir(action.source, action.destination, {
|
|
1698
1870
|
excludePrefixes: action.exclude
|
|
1699
1871
|
});
|
|
@@ -1707,6 +1879,7 @@ async function runInstall(input) {
|
|
|
1707
1879
|
}
|
|
1708
1880
|
}
|
|
1709
1881
|
if (input.plan.target.runtime !== void 0) {
|
|
1882
|
+
await checkpoint();
|
|
1710
1883
|
enterPhase(InstallPhases.INSTALLING_RUNTIME);
|
|
1711
1884
|
const runtimePlan = await planRuntimeDownloads({
|
|
1712
1885
|
runtime: input.plan.target.runtime,
|
|
@@ -1722,6 +1895,7 @@ async function runInstall(input) {
|
|
|
1722
1895
|
});
|
|
1723
1896
|
}
|
|
1724
1897
|
if (processors.length > 0) {
|
|
1898
|
+
await checkpoint();
|
|
1725
1899
|
enterPhase(InstallPhases.RUNNING_FORGE_PROCESSORS);
|
|
1726
1900
|
if (input.plan.target.loader.type !== Loaders.FORGE) {
|
|
1727
1901
|
throw new MinecraftKitError(
|
|
@@ -1736,9 +1910,7 @@ async function runInstall(input) {
|
|
|
1736
1910
|
input.plan.target.runtime.installRoot
|
|
1737
1911
|
);
|
|
1738
1912
|
for (const action of processors) {
|
|
1739
|
-
|
|
1740
|
-
throw new MinecraftKitError("LAUNCH_ABORTED", "Install aborted by signal");
|
|
1741
|
-
}
|
|
1913
|
+
await checkpoint();
|
|
1742
1914
|
await runProcessor({
|
|
1743
1915
|
action,
|
|
1744
1916
|
javaPath,
|
|
@@ -1953,6 +2125,33 @@ function pickArguments(args, context) {
|
|
|
1953
2125
|
};
|
|
1954
2126
|
}
|
|
1955
2127
|
|
|
2128
|
+
// src/launch/jvm-compat.ts
|
|
2129
|
+
var FLAG_MIN_JAVA = [
|
|
2130
|
+
{ prefix: "--sun-misc-unsafe-memory-access", minJava: 23 },
|
|
2131
|
+
{ prefix: "--enable-native-access", minJava: 17 },
|
|
2132
|
+
{ prefix: "-XX:+UseCompactObjectHeaders", minJava: 24 },
|
|
2133
|
+
{ prefix: "-XX:+UseZGC", minJava: 15 }
|
|
2134
|
+
];
|
|
2135
|
+
function filterArgsForJava(input) {
|
|
2136
|
+
if (!Number.isFinite(input.javaMajor) || input.javaMajor <= 0) return input.args;
|
|
2137
|
+
const out = [];
|
|
2138
|
+
for (const arg of input.args) {
|
|
2139
|
+
const incompatible = FLAG_MIN_JAVA.find(
|
|
2140
|
+
({ prefix }) => arg === prefix || arg.startsWith(`${prefix}=`) || arg.startsWith(`${prefix} `)
|
|
2141
|
+
);
|
|
2142
|
+
if (incompatible && input.javaMajor < incompatible.minJava) {
|
|
2143
|
+
input.logger?.log(
|
|
2144
|
+
"warn",
|
|
2145
|
+
`Dropping JVM arg "${arg}" \u2014 requires Java ${incompatible.minJava}, runtime is Java ${input.javaMajor}`,
|
|
2146
|
+
{ flag: arg, minJava: incompatible.minJava, runtimeJava: input.javaMajor }
|
|
2147
|
+
);
|
|
2148
|
+
continue;
|
|
2149
|
+
}
|
|
2150
|
+
out.push(arg);
|
|
2151
|
+
}
|
|
2152
|
+
return out;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
1956
2155
|
// src/launch/placeholders.ts
|
|
1957
2156
|
function substituteArg(raw, values) {
|
|
1958
2157
|
return raw.replaceAll(/\$\{([a-zA-Z0-9_]+)\}/g, (match, key) => {
|
|
@@ -1992,7 +2191,13 @@ function composeArgs(input) {
|
|
|
1992
2191
|
const baseJvm = [...memoryArgs, ...BASE_JVM_ARGS, ...macosArgs];
|
|
1993
2192
|
const substitutedJvm = substituteArgs(rawJvm, input.placeholderValues);
|
|
1994
2193
|
const substitutedGame = substituteArgs(rawGame, input.placeholderValues);
|
|
1995
|
-
const
|
|
2194
|
+
const javaMajor = input.target.runtime.majorVersion;
|
|
2195
|
+
const filteredManifestJvm = javaMajor !== void 0 ? filterArgsForJava({
|
|
2196
|
+
args: substitutedJvm,
|
|
2197
|
+
javaMajor,
|
|
2198
|
+
logger: input.logger ?? silentLogger
|
|
2199
|
+
}) : substitutedJvm;
|
|
2200
|
+
const jvmArgs = [...baseJvm, ...filteredManifestJvm];
|
|
1996
2201
|
if (input.merged.logging?.client?.argument) {
|
|
1997
2202
|
const logging = input.merged.logging.client;
|
|
1998
2203
|
const loggingArg = substituteArgs([logging.argument], {
|
|
@@ -2122,8 +2327,28 @@ function mergeManifest(parent, child) {
|
|
|
2122
2327
|
};
|
|
2123
2328
|
return merged;
|
|
2124
2329
|
}
|
|
2330
|
+
function libraryDedupeKey(library) {
|
|
2331
|
+
if (!library.name) return null;
|
|
2332
|
+
try {
|
|
2333
|
+
const coord = parseMavenCoordinate(library.name);
|
|
2334
|
+
const classifier = coord.classifier ? `:${coord.classifier}` : "";
|
|
2335
|
+
return `${coord.group}:${coord.artifact}${classifier}`;
|
|
2336
|
+
} catch {
|
|
2337
|
+
return null;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2125
2340
|
function mergeLibraries(parent, child) {
|
|
2126
|
-
|
|
2341
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2342
|
+
const unkeyed = [];
|
|
2343
|
+
for (const lib of [...parent, ...child]) {
|
|
2344
|
+
const key = libraryDedupeKey(lib);
|
|
2345
|
+
if (key === null) {
|
|
2346
|
+
unkeyed.push(lib);
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
byKey.set(key, lib);
|
|
2350
|
+
}
|
|
2351
|
+
return [...byKey.values(), ...unkeyed];
|
|
2127
2352
|
}
|
|
2128
2353
|
function mergeArguments(parent, child) {
|
|
2129
2354
|
if (!parent && !child) return void 0;
|
|
@@ -2247,7 +2472,8 @@ async function composeLaunch(input) {
|
|
|
2247
2472
|
merged: resolved.merged,
|
|
2248
2473
|
options,
|
|
2249
2474
|
placeholderValues,
|
|
2250
|
-
features
|
|
2475
|
+
features,
|
|
2476
|
+
logger: input.logger ?? silentLogger
|
|
2251
2477
|
});
|
|
2252
2478
|
return {
|
|
2253
2479
|
targetId: target.id,
|
|
@@ -2429,193 +2655,553 @@ var VerifyFileCategories = {
|
|
|
2429
2655
|
RUNTIME_FILE: "runtime-file",
|
|
2430
2656
|
LOGGING_CONFIG: "logging-config"
|
|
2431
2657
|
};
|
|
2658
|
+
async function sha1OfFile(filePath) {
|
|
2659
|
+
const hash = crypto2__default.default.createHash("sha1");
|
|
2660
|
+
await new Promise((resolve, reject) => {
|
|
2661
|
+
const stream = fs$1.createReadStream(filePath);
|
|
2662
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
2663
|
+
stream.on("end", resolve);
|
|
2664
|
+
stream.on("error", reject);
|
|
2665
|
+
});
|
|
2666
|
+
return hash.digest("hex");
|
|
2667
|
+
}
|
|
2432
2668
|
|
|
2433
|
-
// src/
|
|
2434
|
-
function
|
|
2435
|
-
|
|
2669
|
+
// src/verify/helpers.ts
|
|
2670
|
+
async function runVerification(input, check) {
|
|
2671
|
+
const startedAt = Date.now();
|
|
2672
|
+
const results = [];
|
|
2673
|
+
const record = (result) => {
|
|
2674
|
+
results.push(result);
|
|
2675
|
+
input.onEvent?.({ type: "verify:file-checked", file: result });
|
|
2676
|
+
};
|
|
2677
|
+
await check(record);
|
|
2678
|
+
return {
|
|
2679
|
+
targetId: input.targetId,
|
|
2680
|
+
kind: input.kind,
|
|
2681
|
+
isValid: results.every((r) => r.status === VerifyFileStatuses.OK),
|
|
2682
|
+
issues: results.filter((r) => r.status !== VerifyFileStatuses.OK),
|
|
2683
|
+
checkedFiles: results.length,
|
|
2684
|
+
durationMs: Date.now() - startedAt
|
|
2685
|
+
};
|
|
2436
2686
|
}
|
|
2437
|
-
function
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2687
|
+
async function verifyHashedFile(input) {
|
|
2688
|
+
if (!await fileExists(input.path)) {
|
|
2689
|
+
return {
|
|
2690
|
+
path: input.path,
|
|
2691
|
+
category: input.category,
|
|
2692
|
+
status: VerifyFileStatuses.MISSING,
|
|
2693
|
+
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2694
|
+
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2695
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
if (input.expectedSize !== void 0) {
|
|
2699
|
+
const size = await fileSize(input.path);
|
|
2700
|
+
if (size !== input.expectedSize) {
|
|
2701
|
+
return {
|
|
2702
|
+
path: input.path,
|
|
2703
|
+
category: input.category,
|
|
2704
|
+
status: VerifyFileStatuses.WRONG_SIZE,
|
|
2705
|
+
expectedSize: input.expectedSize,
|
|
2706
|
+
actualSize: size,
|
|
2707
|
+
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2708
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
if (input.expectedSha1 !== void 0) {
|
|
2713
|
+
const actualSha1 = await sha1OfFile(input.path);
|
|
2714
|
+
if (actualSha1 !== input.expectedSha1) {
|
|
2715
|
+
return {
|
|
2716
|
+
path: input.path,
|
|
2717
|
+
category: input.category,
|
|
2718
|
+
status: VerifyFileStatuses.CORRUPT,
|
|
2719
|
+
expectedSha1: input.expectedSha1,
|
|
2720
|
+
actualSha1,
|
|
2721
|
+
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2722
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2723
|
+
};
|
|
2444
2724
|
}
|
|
2445
2725
|
}
|
|
2446
2726
|
return {
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
}
|
|
2454
|
-
return false;
|
|
2455
|
-
},
|
|
2456
|
-
categoriesAt: (path13) => map.get(path13) ?? /* @__PURE__ */ new Set()
|
|
2727
|
+
path: input.path,
|
|
2728
|
+
category: input.category,
|
|
2729
|
+
status: VerifyFileStatuses.OK,
|
|
2730
|
+
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2731
|
+
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2732
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2457
2733
|
};
|
|
2458
2734
|
}
|
|
2459
|
-
function
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
}
|
|
2467
|
-
|
|
2735
|
+
async function verifyExistence(input) {
|
|
2736
|
+
if (await fileExists(input.path)) {
|
|
2737
|
+
return {
|
|
2738
|
+
path: input.path,
|
|
2739
|
+
category: input.category,
|
|
2740
|
+
status: VerifyFileStatuses.OK,
|
|
2741
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2468
2744
|
return {
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
totalActions: actions.length,
|
|
2474
|
-
totalBytes: sumDownloadBytes(actions)
|
|
2745
|
+
path: input.path,
|
|
2746
|
+
category: input.category,
|
|
2747
|
+
status: VerifyFileStatuses.MISSING,
|
|
2748
|
+
...input.url !== void 0 ? { url: input.url } : {}
|
|
2475
2749
|
};
|
|
2476
2750
|
}
|
|
2477
|
-
async function
|
|
2478
|
-
const
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
const actions = selectRepairActions({
|
|
2486
|
-
target: input.target,
|
|
2487
|
-
installPlan,
|
|
2488
|
-
issues,
|
|
2489
|
-
aspectFilter
|
|
2490
|
-
});
|
|
2491
|
-
postprocess?.({ actions, installPlan, issues });
|
|
2492
|
-
return buildRepairPlan(input.target, actions);
|
|
2493
|
-
}
|
|
2494
|
-
function selectRepairActions(input) {
|
|
2495
|
-
const matching = [];
|
|
2496
|
-
for (const action of input.installPlan.actions) {
|
|
2497
|
-
if (!input.aspectFilter(action)) continue;
|
|
2498
|
-
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
2499
|
-
if (input.issues.hasNonNative(action.target)) {
|
|
2500
|
-
matching.push(action);
|
|
2501
|
-
}
|
|
2502
|
-
} else if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
2503
|
-
if (input.issues.has(action.path)) {
|
|
2504
|
-
matching.push(action);
|
|
2505
|
-
}
|
|
2506
|
-
} else if (action.kind === InstallActionKinds.EXTRACT_NATIVE) {
|
|
2507
|
-
if (input.issues.has(action.source)) {
|
|
2508
|
-
matching.push(action);
|
|
2509
|
-
}
|
|
2510
|
-
} else {
|
|
2511
|
-
matching.push(action);
|
|
2751
|
+
async function findForgeVersionJsonPath(directory, minecraftVersion) {
|
|
2752
|
+
const versionsDir = targetPaths.versionsDir(directory);
|
|
2753
|
+
const dirs = await listChildDirectories(versionsDir);
|
|
2754
|
+
for (const id of dirs) {
|
|
2755
|
+
if (!id.startsWith(`${minecraftVersion}-forge-`)) continue;
|
|
2756
|
+
const jsonPath = targetPaths.versionJson(directory, id);
|
|
2757
|
+
if (!await fileExists(jsonPath)) {
|
|
2758
|
+
return jsonPath;
|
|
2512
2759
|
}
|
|
2760
|
+
const parsed = await tryParseInheritsFrom(jsonPath);
|
|
2761
|
+
if (parsed === minecraftVersion) return jsonPath;
|
|
2762
|
+
}
|
|
2763
|
+
return null;
|
|
2764
|
+
}
|
|
2765
|
+
async function tryParseInheritsFrom(jsonPath) {
|
|
2766
|
+
try {
|
|
2767
|
+
const parsed = JSON.parse(await readText(jsonPath));
|
|
2768
|
+
return parsed.inheritsFrom;
|
|
2769
|
+
} catch {
|
|
2770
|
+
return void 0;
|
|
2513
2771
|
}
|
|
2514
|
-
return matching;
|
|
2515
2772
|
}
|
|
2516
2773
|
|
|
2517
|
-
// src/
|
|
2518
|
-
async function
|
|
2774
|
+
// src/verify/fabric.ts
|
|
2775
|
+
async function verifyFabric(input) {
|
|
2519
2776
|
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
2520
2777
|
throw new MinecraftKitError(
|
|
2521
2778
|
"INVALID_INPUT",
|
|
2522
|
-
`
|
|
2779
|
+
`verify.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
2523
2780
|
);
|
|
2524
2781
|
}
|
|
2525
|
-
const
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2782
|
+
const loader = input.target.loader;
|
|
2783
|
+
return runVerification(
|
|
2784
|
+
{
|
|
2785
|
+
targetId: input.target.id,
|
|
2786
|
+
kind: VerificationKinds.FABRIC,
|
|
2787
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2788
|
+
},
|
|
2789
|
+
async (record) => {
|
|
2790
|
+
record(
|
|
2791
|
+
await verifyExistence({
|
|
2792
|
+
path: targetPaths.versionJson(input.target.directory, loader.profile.id),
|
|
2793
|
+
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2794
|
+
})
|
|
2795
|
+
);
|
|
2796
|
+
const fabricLibraries = planLibraryDownloads({
|
|
2797
|
+
libraries: loader.profile.libraries,
|
|
2798
|
+
directory: input.target.directory,
|
|
2799
|
+
system: input.target.runtime.system,
|
|
2800
|
+
versionId: input.target.minecraft.version,
|
|
2801
|
+
category: "fabric-library"
|
|
2802
|
+
});
|
|
2803
|
+
for (const action of fabricLibraries.downloads) {
|
|
2804
|
+
record(
|
|
2805
|
+
await verifyHashedFile({
|
|
2806
|
+
path: action.target,
|
|
2807
|
+
expectedSha1: action.expectedSha1,
|
|
2808
|
+
expectedSize: action.expectedSize,
|
|
2809
|
+
...action.url ? { url: action.url } : {},
|
|
2810
|
+
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2811
|
+
})
|
|
2812
|
+
);
|
|
2813
|
+
}
|
|
2535
2814
|
}
|
|
2536
|
-
|
|
2537
|
-
});
|
|
2815
|
+
);
|
|
2538
2816
|
}
|
|
2539
2817
|
|
|
2540
|
-
// src/
|
|
2541
|
-
|
|
2542
|
-
"forge-library",
|
|
2543
|
-
"forge-installer"
|
|
2544
|
-
]);
|
|
2545
|
-
async function planForgeRepair(input) {
|
|
2818
|
+
// src/verify/forge.ts
|
|
2819
|
+
async function verifyForge(input) {
|
|
2546
2820
|
if (input.target.loader.type !== Loaders.FORGE) {
|
|
2547
2821
|
throw new MinecraftKitError(
|
|
2548
2822
|
"INVALID_INPUT",
|
|
2549
|
-
`
|
|
2823
|
+
`verify.forge requires a Forge target (got ${input.target.loader.type})`
|
|
2550
2824
|
);
|
|
2551
2825
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
input,
|
|
2558
|
-
(action) => {
|
|
2559
|
-
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
2560
|
-
return FORGE_DOWNLOAD_CATEGORIES.has(action.category);
|
|
2561
|
-
}
|
|
2562
|
-
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
2563
|
-
return action.path === forgeJsonPath;
|
|
2564
|
-
}
|
|
2565
|
-
return false;
|
|
2826
|
+
return runVerification(
|
|
2827
|
+
{
|
|
2828
|
+
targetId: input.target.id,
|
|
2829
|
+
kind: VerificationKinds.FORGE,
|
|
2830
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2566
2831
|
},
|
|
2567
|
-
(
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2832
|
+
async (record) => {
|
|
2833
|
+
const forgeVersionJsonPath = await findForgeVersionJsonPath(
|
|
2834
|
+
input.target.directory,
|
|
2835
|
+
input.target.minecraft.version
|
|
2571
2836
|
);
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
}
|
|
2837
|
+
if (forgeVersionJsonPath === null) return;
|
|
2838
|
+
record(
|
|
2839
|
+
await verifyExistence({
|
|
2840
|
+
path: forgeVersionJsonPath,
|
|
2841
|
+
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2842
|
+
})
|
|
2843
|
+
);
|
|
2844
|
+
if (!await fileExists(forgeVersionJsonPath)) return;
|
|
2845
|
+
let parsed;
|
|
2846
|
+
try {
|
|
2847
|
+
parsed = JSON.parse(await readText(forgeVersionJsonPath));
|
|
2848
|
+
} catch {
|
|
2849
|
+
record({
|
|
2850
|
+
path: forgeVersionJsonPath,
|
|
2851
|
+
category: VerifyFileCategories.LOADER_LIBRARY,
|
|
2852
|
+
status: VerifyFileStatuses.CORRUPT
|
|
2853
|
+
});
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
const forgeLibraries = planLibraryDownloads({
|
|
2857
|
+
libraries: parsed.libraries,
|
|
2858
|
+
directory: input.target.directory,
|
|
2859
|
+
system: input.target.runtime.system,
|
|
2860
|
+
versionId: input.target.minecraft.version,
|
|
2861
|
+
category: "forge-library"
|
|
2862
|
+
});
|
|
2863
|
+
for (const action of forgeLibraries.downloads) {
|
|
2864
|
+
record(
|
|
2865
|
+
await verifyHashedFile({
|
|
2866
|
+
path: action.target,
|
|
2867
|
+
expectedSha1: action.expectedSha1,
|
|
2868
|
+
expectedSize: action.expectedSize,
|
|
2869
|
+
...action.url ? { url: action.url } : {},
|
|
2870
|
+
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2871
|
+
})
|
|
2872
|
+
);
|
|
2578
2873
|
}
|
|
2579
2874
|
}
|
|
2580
2875
|
);
|
|
2581
2876
|
}
|
|
2582
2877
|
|
|
2583
|
-
// src/
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
async
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2878
|
+
// src/verify/minecraft.ts
|
|
2879
|
+
async function verifyMinecraft(input) {
|
|
2880
|
+
return runVerification(
|
|
2881
|
+
{
|
|
2882
|
+
targetId: input.target.id,
|
|
2883
|
+
kind: VerificationKinds.MINECRAFT,
|
|
2884
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2885
|
+
},
|
|
2886
|
+
async (record) => {
|
|
2887
|
+
const { directory, minecraft, runtime } = input.target;
|
|
2888
|
+
record(
|
|
2889
|
+
await verifyHashedFile({
|
|
2890
|
+
path: targetPaths.versionJar(directory, minecraft.version),
|
|
2891
|
+
expectedSha1: minecraft.manifest.downloads.client.sha1,
|
|
2892
|
+
expectedSize: minecraft.manifest.downloads.client.size,
|
|
2893
|
+
url: minecraft.manifest.downloads.client.url,
|
|
2894
|
+
category: VerifyFileCategories.CLIENT_JAR
|
|
2895
|
+
})
|
|
2896
|
+
);
|
|
2897
|
+
record(
|
|
2898
|
+
await verifyExistence({
|
|
2899
|
+
path: targetPaths.versionJson(directory, minecraft.version),
|
|
2900
|
+
category: VerifyFileCategories.CLIENT_JAR
|
|
2901
|
+
})
|
|
2902
|
+
);
|
|
2903
|
+
if (minecraft.manifest.logging?.client) {
|
|
2904
|
+
const logging = minecraft.manifest.logging.client;
|
|
2905
|
+
record(
|
|
2906
|
+
await verifyHashedFile({
|
|
2907
|
+
path: targetPaths.loggingConfig(directory, logging.file.id),
|
|
2908
|
+
expectedSha1: logging.file.sha1,
|
|
2909
|
+
expectedSize: logging.file.size,
|
|
2910
|
+
url: logging.file.url,
|
|
2911
|
+
category: VerifyFileCategories.LOGGING_CONFIG
|
|
2912
|
+
})
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
const libraryPlan = planLibraryDownloads({
|
|
2916
|
+
libraries: minecraft.manifest.libraries,
|
|
2917
|
+
directory,
|
|
2918
|
+
system: runtime.system,
|
|
2919
|
+
versionId: minecraft.version,
|
|
2920
|
+
category: "library"
|
|
2921
|
+
});
|
|
2922
|
+
for (const action of libraryPlan.downloads) {
|
|
2923
|
+
record(
|
|
2924
|
+
await verifyHashedFile({
|
|
2925
|
+
path: action.target,
|
|
2926
|
+
expectedSha1: action.expectedSha1,
|
|
2927
|
+
expectedSize: action.expectedSize,
|
|
2928
|
+
url: action.url,
|
|
2929
|
+
category: VerifyFileCategories.LIBRARY
|
|
2930
|
+
})
|
|
2931
|
+
);
|
|
2932
|
+
}
|
|
2933
|
+
const indexUrl = minecraft.manifest.assetIndex.url;
|
|
2934
|
+
const indexPath = targetPaths.assetIndex(directory, minecraft.manifest.assetIndex.id);
|
|
2935
|
+
record(
|
|
2936
|
+
await verifyHashedFile({
|
|
2937
|
+
path: indexPath,
|
|
2938
|
+
expectedSha1: minecraft.manifest.assetIndex.sha1,
|
|
2939
|
+
expectedSize: minecraft.manifest.assetIndex.size,
|
|
2940
|
+
url: indexUrl,
|
|
2941
|
+
category: VerifyFileCategories.ASSET_INDEX
|
|
2942
|
+
})
|
|
2943
|
+
);
|
|
2944
|
+
const indexDocument = await fetchJson(input.http, input.cache, {
|
|
2945
|
+
url: indexUrl,
|
|
2946
|
+
cacheKey: `asset-index:${minecraft.manifest.assetIndex.id}:${minecraft.manifest.assetIndex.sha1}`,
|
|
2947
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2948
|
+
});
|
|
2949
|
+
const seenAssetHashes = /* @__PURE__ */ new Set();
|
|
2950
|
+
for (const entry of Object.values(indexDocument.objects)) {
|
|
2951
|
+
if (seenAssetHashes.has(entry.hash)) continue;
|
|
2952
|
+
seenAssetHashes.add(entry.hash);
|
|
2953
|
+
record(
|
|
2954
|
+
await verifyHashedFile({
|
|
2955
|
+
path: targetPaths.assetObject(directory, entry.hash),
|
|
2956
|
+
expectedSha1: entry.hash,
|
|
2957
|
+
expectedSize: entry.size,
|
|
2958
|
+
category: VerifyFileCategories.ASSET
|
|
2959
|
+
})
|
|
2960
|
+
);
|
|
2961
|
+
}
|
|
2962
|
+
const nativesDir = targetPaths.nativesDir(directory, minecraft.version);
|
|
2963
|
+
if (!await fileExists(nativesDir)) {
|
|
2964
|
+
for (const extraction of libraryPlan.nativeExtractions) {
|
|
2965
|
+
record({
|
|
2966
|
+
path: extraction.source,
|
|
2967
|
+
category: VerifyFileCategories.NATIVE,
|
|
2968
|
+
status: VerifyFileStatuses.MISSING
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2605
2972
|
}
|
|
2606
|
-
|
|
2607
|
-
});
|
|
2973
|
+
);
|
|
2608
2974
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
...input.
|
|
2615
|
-
totalActions: input.plan.actions.length,
|
|
2616
|
-
totalBytes: input.plan.totalBytes
|
|
2975
|
+
async function verifyRuntime(input) {
|
|
2976
|
+
return runVerification(
|
|
2977
|
+
{
|
|
2978
|
+
targetId: input.target.id,
|
|
2979
|
+
kind: VerificationKinds.RUNTIME,
|
|
2980
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2617
2981
|
},
|
|
2618
|
-
|
|
2982
|
+
async (record) => {
|
|
2983
|
+
let manifest;
|
|
2984
|
+
try {
|
|
2985
|
+
manifest = await fetchJson(input.http, input.cache, {
|
|
2986
|
+
url: input.target.runtime.manifestUrl,
|
|
2987
|
+
cacheKey: `runtime-manifest:${input.target.runtime.component}:${input.target.runtime.platformKey}:${input.target.runtime.manifestSha1}`,
|
|
2988
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2989
|
+
});
|
|
2990
|
+
} catch {
|
|
2991
|
+
record({
|
|
2992
|
+
path: input.target.runtime.manifestUrl,
|
|
2993
|
+
category: VerifyFileCategories.RUNTIME_FILE,
|
|
2994
|
+
status: VerifyFileStatuses.MISSING
|
|
2995
|
+
});
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
const runtimeRoot = targetPaths.runtimeRoot(
|
|
2999
|
+
input.target.directory,
|
|
3000
|
+
input.target.runtime.component,
|
|
3001
|
+
input.target.runtime.installRoot
|
|
3002
|
+
);
|
|
3003
|
+
for (const [relative, entry] of Object.entries(manifest.files)) {
|
|
3004
|
+
if (entry.type !== "file") continue;
|
|
3005
|
+
record(
|
|
3006
|
+
await verifyHashedFile({
|
|
3007
|
+
path: path__default.default.join(runtimeRoot, relative),
|
|
3008
|
+
expectedSha1: entry.downloads.raw.sha1,
|
|
3009
|
+
expectedSize: entry.downloads.raw.size,
|
|
3010
|
+
url: entry.downloads.raw.url,
|
|
3011
|
+
category: VerifyFileCategories.RUNTIME_FILE
|
|
3012
|
+
})
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
);
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
// src/repair/helpers.ts
|
|
3020
|
+
function asResultArray(from) {
|
|
3021
|
+
return Array.isArray(from) ? from : [from];
|
|
3022
|
+
}
|
|
3023
|
+
function buildIssueIndex(from) {
|
|
3024
|
+
const map = /* @__PURE__ */ new Map();
|
|
3025
|
+
for (const v of asResultArray(from)) {
|
|
3026
|
+
for (const issue of v.issues) {
|
|
3027
|
+
const set = map.get(issue.path);
|
|
3028
|
+
if (set) set.add(issue.category);
|
|
3029
|
+
else map.set(issue.path, /* @__PURE__ */ new Set([issue.category]));
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
return {
|
|
3033
|
+
has: (path13) => map.has(path13),
|
|
3034
|
+
hasNonNative: (path13) => {
|
|
3035
|
+
const cats = map.get(path13);
|
|
3036
|
+
if (!cats) return false;
|
|
3037
|
+
for (const c of cats) {
|
|
3038
|
+
if (c !== VerifyFileCategories.NATIVE) return true;
|
|
3039
|
+
}
|
|
3040
|
+
return false;
|
|
3041
|
+
},
|
|
3042
|
+
categoriesAt: (path13) => map.get(path13) ?? /* @__PURE__ */ new Set()
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
function sumDownloadBytes(actions) {
|
|
3046
|
+
return actions.reduce((sum, action) => {
|
|
3047
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3048
|
+
return sum + (action.expectedSize ?? 0);
|
|
3049
|
+
}
|
|
3050
|
+
return sum;
|
|
3051
|
+
}, 0);
|
|
3052
|
+
}
|
|
3053
|
+
function buildRepairPlan(target, actions) {
|
|
3054
|
+
return {
|
|
3055
|
+
targetId: target.id,
|
|
3056
|
+
directory: target.directory,
|
|
3057
|
+
target,
|
|
3058
|
+
actions,
|
|
3059
|
+
totalActions: actions.length,
|
|
3060
|
+
totalBytes: sumDownloadBytes(actions)
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
async function planAspectRepair(input, aspectFilter, postprocess) {
|
|
3064
|
+
const installPlan = await planInstall({
|
|
3065
|
+
target: input.target,
|
|
3066
|
+
http: input.http,
|
|
3067
|
+
cache: input.cache,
|
|
3068
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3069
|
+
});
|
|
3070
|
+
const issues = buildIssueIndex(input.from);
|
|
3071
|
+
const actions = selectRepairActions({
|
|
3072
|
+
target: input.target,
|
|
3073
|
+
installPlan,
|
|
3074
|
+
issues,
|
|
3075
|
+
aspectFilter
|
|
3076
|
+
});
|
|
3077
|
+
postprocess?.({ actions, installPlan, issues });
|
|
3078
|
+
return buildRepairPlan(input.target, actions);
|
|
3079
|
+
}
|
|
3080
|
+
function selectRepairActions(input) {
|
|
3081
|
+
const matching = [];
|
|
3082
|
+
for (const action of input.installPlan.actions) {
|
|
3083
|
+
if (!input.aspectFilter(action)) continue;
|
|
3084
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3085
|
+
if (input.issues.hasNonNative(action.target)) {
|
|
3086
|
+
matching.push(action);
|
|
3087
|
+
}
|
|
3088
|
+
} else if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3089
|
+
if (input.issues.has(action.path)) {
|
|
3090
|
+
matching.push(action);
|
|
3091
|
+
}
|
|
3092
|
+
} else if (action.kind === InstallActionKinds.EXTRACT_NATIVE) {
|
|
3093
|
+
if (input.issues.has(action.source)) {
|
|
3094
|
+
matching.push(action);
|
|
3095
|
+
}
|
|
3096
|
+
} else {
|
|
3097
|
+
matching.push(action);
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
return matching;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
// src/repair/fabric.ts
|
|
3104
|
+
async function planFabricRepair(input) {
|
|
3105
|
+
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
3106
|
+
throw new MinecraftKitError(
|
|
3107
|
+
"INVALID_INPUT",
|
|
3108
|
+
`repair.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
3109
|
+
);
|
|
3110
|
+
}
|
|
3111
|
+
const fabricJsonPath = targetPaths.versionJson(
|
|
3112
|
+
input.target.directory,
|
|
3113
|
+
input.target.loader.profile.id
|
|
3114
|
+
);
|
|
3115
|
+
return planAspectRepair(input, (action) => {
|
|
3116
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3117
|
+
return action.category === "fabric-library";
|
|
3118
|
+
}
|
|
3119
|
+
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3120
|
+
return action.path === fabricJsonPath;
|
|
3121
|
+
}
|
|
3122
|
+
return false;
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
// src/repair/forge.ts
|
|
3127
|
+
var FORGE_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3128
|
+
"forge-library",
|
|
3129
|
+
"forge-installer"
|
|
3130
|
+
]);
|
|
3131
|
+
async function planForgeRepair(input) {
|
|
3132
|
+
if (input.target.loader.type !== Loaders.FORGE) {
|
|
3133
|
+
throw new MinecraftKitError(
|
|
3134
|
+
"INVALID_INPUT",
|
|
3135
|
+
`repair.forge requires a Forge target (got ${input.target.loader.type})`
|
|
3136
|
+
);
|
|
3137
|
+
}
|
|
3138
|
+
const forgeJsonPath = targetPaths.versionJson(
|
|
3139
|
+
input.target.directory,
|
|
3140
|
+
input.target.loader.fullVersion
|
|
3141
|
+
);
|
|
3142
|
+
return planAspectRepair(
|
|
3143
|
+
input,
|
|
3144
|
+
(action) => {
|
|
3145
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3146
|
+
return FORGE_DOWNLOAD_CATEGORIES.has(action.category);
|
|
3147
|
+
}
|
|
3148
|
+
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3149
|
+
return action.path === forgeJsonPath;
|
|
3150
|
+
}
|
|
3151
|
+
return false;
|
|
3152
|
+
},
|
|
3153
|
+
({ actions, installPlan, issues }) => {
|
|
3154
|
+
if (!issues.has(forgeJsonPath)) return;
|
|
3155
|
+
const alreadyIncluded = new Set(
|
|
3156
|
+
actions.filter((a) => a.kind === InstallActionKinds.DOWNLOAD_FILE).map((a) => a.target)
|
|
3157
|
+
);
|
|
3158
|
+
for (const action of installPlan.actions) {
|
|
3159
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE && action.category === "forge-library" && !alreadyIncluded.has(action.target)) {
|
|
3160
|
+
actions.push(action);
|
|
3161
|
+
} else if (action.kind === InstallActionKinds.RUN_FORGE_PROCESSOR) {
|
|
3162
|
+
actions.push(action);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
);
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
// src/repair/minecraft.ts
|
|
3170
|
+
var MINECRAFT_DOWNLOAD_CATEGORIES = /* @__PURE__ */ new Set([
|
|
3171
|
+
"client-jar",
|
|
3172
|
+
"library",
|
|
3173
|
+
"asset-index",
|
|
3174
|
+
"asset",
|
|
3175
|
+
"logging-config"
|
|
3176
|
+
]);
|
|
3177
|
+
async function planMinecraftRepair(input) {
|
|
3178
|
+
const vanillaJsonPath = targetPaths.versionJson(
|
|
3179
|
+
input.target.directory,
|
|
3180
|
+
input.target.minecraft.version
|
|
3181
|
+
);
|
|
3182
|
+
return planAspectRepair(input, (action) => {
|
|
3183
|
+
if (action.kind === InstallActionKinds.DOWNLOAD_FILE) {
|
|
3184
|
+
return MINECRAFT_DOWNLOAD_CATEGORIES.has(action.category);
|
|
3185
|
+
}
|
|
3186
|
+
if (action.kind === InstallActionKinds.WRITE_VERSION_JSON) {
|
|
3187
|
+
return action.path === vanillaJsonPath;
|
|
3188
|
+
}
|
|
3189
|
+
if (action.kind === InstallActionKinds.EXTRACT_NATIVE) {
|
|
3190
|
+
return true;
|
|
3191
|
+
}
|
|
3192
|
+
return false;
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
// src/repair/runner.ts
|
|
3197
|
+
async function runRepair(input) {
|
|
3198
|
+
const report = await runInstall({
|
|
3199
|
+
plan: {
|
|
3200
|
+
...input.plan,
|
|
3201
|
+
totalActions: input.plan.actions.length,
|
|
3202
|
+
totalBytes: input.plan.totalBytes
|
|
3203
|
+
},
|
|
3204
|
+
http: input.http,
|
|
2619
3205
|
cache: input.cache,
|
|
2620
3206
|
spawner: input.spawner,
|
|
2621
3207
|
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
@@ -2637,6 +3223,58 @@ async function planRuntimeRepair(input) {
|
|
|
2637
3223
|
);
|
|
2638
3224
|
}
|
|
2639
3225
|
|
|
3226
|
+
// src/repair/all.ts
|
|
3227
|
+
async function repairAll(input) {
|
|
3228
|
+
const startedAt = Date.now();
|
|
3229
|
+
const ctx = {
|
|
3230
|
+
target: input.target,
|
|
3231
|
+
http: input.http,
|
|
3232
|
+
cache: input.cache,
|
|
3233
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3234
|
+
};
|
|
3235
|
+
const verifications = [];
|
|
3236
|
+
const mc = await verifyMinecraft(ctx);
|
|
3237
|
+
verifications.push(mc);
|
|
3238
|
+
const rt = await verifyRuntime(ctx);
|
|
3239
|
+
verifications.push(rt);
|
|
3240
|
+
if (input.target.loader.type === Loaders.FABRIC) {
|
|
3241
|
+
verifications.push(await verifyFabric(ctx));
|
|
3242
|
+
} else if (input.target.loader.type === Loaders.FORGE) {
|
|
3243
|
+
verifications.push(await verifyForge(ctx));
|
|
3244
|
+
}
|
|
3245
|
+
const repairs = /* @__PURE__ */ new Map();
|
|
3246
|
+
let bytesDownloaded = 0;
|
|
3247
|
+
for (const verification of verifications) {
|
|
3248
|
+
if (verification.isValid) continue;
|
|
3249
|
+
const planner = PLANNERS[verification.kind];
|
|
3250
|
+
if (!planner) continue;
|
|
3251
|
+
const plan = await planner({ ...ctx, from: verification });
|
|
3252
|
+
if (plan.totalActions === 0) continue;
|
|
3253
|
+
const report = await runRepair({
|
|
3254
|
+
plan,
|
|
3255
|
+
http: input.http,
|
|
3256
|
+
cache: input.cache,
|
|
3257
|
+
spawner: input.spawner,
|
|
3258
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
3259
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
3260
|
+
});
|
|
3261
|
+
repairs.set(verification.kind, report);
|
|
3262
|
+
bytesDownloaded += report.bytesDownloaded;
|
|
3263
|
+
}
|
|
3264
|
+
return {
|
|
3265
|
+
verifications,
|
|
3266
|
+
repairs,
|
|
3267
|
+
bytesDownloaded,
|
|
3268
|
+
durationMs: Date.now() - startedAt
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
var PLANNERS = {
|
|
3272
|
+
minecraft: planMinecraftRepair,
|
|
3273
|
+
runtime: planRuntimeRepair,
|
|
3274
|
+
fabric: planFabricRepair,
|
|
3275
|
+
forge: planForgeRepair
|
|
3276
|
+
};
|
|
3277
|
+
|
|
2640
3278
|
// src/types/runtime.ts
|
|
2641
3279
|
var RuntimeComponents = {
|
|
2642
3280
|
JRE_LEGACY: "jre-legacy",
|
|
@@ -2711,485 +3349,125 @@ var TargetsApi = class {
|
|
|
2711
3349
|
const runtime = input.runtime?.installRoot !== void 0 ? { ...resolvedRuntime, installRoot: input.runtime.installRoot } : resolvedRuntime;
|
|
2712
3350
|
let loader;
|
|
2713
3351
|
if (input.loader.type === Loaders.VANILLA) {
|
|
2714
|
-
loader = {
|
|
2715
|
-
type: Loaders.VANILLA,
|
|
2716
|
-
minecraftVersion: minecraft.version,
|
|
2717
|
-
minecraft
|
|
2718
|
-
};
|
|
2719
|
-
} else if (input.loader.type === Loaders.FABRIC) {
|
|
2720
|
-
loader = await this.ctx.fabric.resolve({
|
|
2721
|
-
minecraftVersion: minecraft.version,
|
|
2722
|
-
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
2723
|
-
...input.loader.version !== void 0 ? { loaderVersion: input.loader.version } : {},
|
|
2724
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2725
|
-
});
|
|
2726
|
-
} else {
|
|
2727
|
-
loader = await this.ctx.forge.resolve({
|
|
2728
|
-
minecraftVersion: minecraft.version,
|
|
2729
|
-
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
2730
|
-
...input.loader.version !== void 0 ? { forgeVersion: input.loader.version } : {},
|
|
2731
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2732
|
-
});
|
|
2733
|
-
}
|
|
2734
|
-
return this.create({
|
|
2735
|
-
id: input.id,
|
|
2736
|
-
directory: input.directory,
|
|
2737
|
-
minecraft,
|
|
2738
|
-
loader,
|
|
2739
|
-
runtime
|
|
2740
|
-
});
|
|
2741
|
-
}
|
|
2742
|
-
/** Scan a root directory for Minecraft installations. Returns only what is on disk. */
|
|
2743
|
-
async list(input) {
|
|
2744
|
-
if (!await dirExists(input.rootDir)) return [];
|
|
2745
|
-
const subdirs = await listChildDirectories(input.rootDir);
|
|
2746
|
-
const results = [];
|
|
2747
|
-
for (const id of subdirs) {
|
|
2748
|
-
const directory = path__default.default.join(input.rootDir, id);
|
|
2749
|
-
const discovered = await discoverInstallation(id, directory);
|
|
2750
|
-
if (discovered) results.push(discovered);
|
|
2751
|
-
}
|
|
2752
|
-
return results;
|
|
2753
|
-
}
|
|
2754
|
-
};
|
|
2755
|
-
async function discoverInstallation(id, directory) {
|
|
2756
|
-
const versionsDir = path__default.default.join(directory, VERSIONS_DIR);
|
|
2757
|
-
const librariesDir = path__default.default.join(directory, LIBRARIES_DIR);
|
|
2758
|
-
const assetsDir = path__default.default.join(directory, ASSETS_DIR);
|
|
2759
|
-
const looksLikeInstall = await dirExists(versionsDir) && (await dirExists(librariesDir) || await dirExists(assetsDir));
|
|
2760
|
-
if (!looksLikeInstall) return null;
|
|
2761
|
-
const versionDirs = await listChildDirectories(versionsDir);
|
|
2762
|
-
const minecraftVersions = [];
|
|
2763
|
-
const loaders = [];
|
|
2764
|
-
for (const versionId of versionDirs) {
|
|
2765
|
-
const hint = inferLoaderFromVersionId(versionId);
|
|
2766
|
-
if (hint) {
|
|
2767
|
-
loaders.push(hint);
|
|
2768
|
-
if (hint.minecraftVersion && !minecraftVersions.includes(hint.minecraftVersion)) {
|
|
2769
|
-
minecraftVersions.push(hint.minecraftVersion);
|
|
2770
|
-
}
|
|
2771
|
-
} else {
|
|
2772
|
-
minecraftVersions.push(versionId);
|
|
2773
|
-
}
|
|
2774
|
-
}
|
|
2775
|
-
const runtime = await discoverRuntime(directory);
|
|
2776
|
-
return { id, directory, minecraftVersions, loaders, ...runtime ? { runtime } : {} };
|
|
2777
|
-
}
|
|
2778
|
-
async function discoverRuntime(directory) {
|
|
2779
|
-
const runtimeDir = path__default.default.join(directory, RUNTIMES_DIR);
|
|
2780
|
-
if (!await dirExists(runtimeDir)) return void 0;
|
|
2781
|
-
let components;
|
|
2782
|
-
try {
|
|
2783
|
-
components = await listChildDirectories(runtimeDir);
|
|
2784
|
-
} catch {
|
|
2785
|
-
return void 0;
|
|
2786
|
-
}
|
|
2787
|
-
for (const component of components) {
|
|
2788
|
-
const root = path__default.default.join(runtimeDir, component);
|
|
2789
|
-
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");
|
|
2790
|
-
if (await fileExists(javaPath)) {
|
|
2791
|
-
return { component, javaPath };
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
return void 0;
|
|
2795
|
-
}
|
|
2796
|
-
function inferLoaderFromVersionId(versionId) {
|
|
2797
|
-
const fabricMatch = /^fabric-loader-([^-]+)-(.+)$/.exec(versionId);
|
|
2798
|
-
if (fabricMatch?.[1] && fabricMatch[2]) {
|
|
2799
|
-
return { type: Loaders.FABRIC, version: fabricMatch[1], minecraftVersion: fabricMatch[2] };
|
|
2800
|
-
}
|
|
2801
|
-
const forgeMatch = /^([^-]+)-forge-(.+)$/.exec(versionId);
|
|
2802
|
-
if (forgeMatch?.[1] && forgeMatch[2]) {
|
|
2803
|
-
return { type: Loaders.FORGE, minecraftVersion: forgeMatch[1], version: forgeMatch[2] };
|
|
2804
|
-
}
|
|
2805
|
-
return null;
|
|
2806
|
-
}
|
|
2807
|
-
|
|
2808
|
-
// src/update/runner.ts
|
|
2809
|
-
async function planUpdate(input) {
|
|
2810
|
-
return planInstall({
|
|
2811
|
-
target: input.target,
|
|
2812
|
-
http: input.http,
|
|
2813
|
-
cache: input.cache,
|
|
2814
|
-
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
2815
|
-
});
|
|
2816
|
-
}
|
|
2817
|
-
async function runUpdate(input) {
|
|
2818
|
-
const report = await runInstall({
|
|
2819
|
-
plan: input.plan,
|
|
2820
|
-
http: input.http,
|
|
2821
|
-
cache: input.cache,
|
|
2822
|
-
spawner: input.spawner,
|
|
2823
|
-
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
2824
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2825
|
-
});
|
|
2826
|
-
return {
|
|
2827
|
-
targetId: report.targetId,
|
|
2828
|
-
bytesDownloaded: report.bytesDownloaded,
|
|
2829
|
-
actionsCompleted: report.actionsCompleted,
|
|
2830
|
-
actionsSkipped: report.actionsSkipped,
|
|
2831
|
-
durationMs: report.durationMs
|
|
2832
|
-
};
|
|
2833
|
-
}
|
|
2834
|
-
async function sha1OfFile(filePath) {
|
|
2835
|
-
const hash = crypto2__default.default.createHash("sha1");
|
|
2836
|
-
await new Promise((resolve, reject) => {
|
|
2837
|
-
const stream = fs$1.createReadStream(filePath);
|
|
2838
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
2839
|
-
stream.on("end", resolve);
|
|
2840
|
-
stream.on("error", reject);
|
|
2841
|
-
});
|
|
2842
|
-
return hash.digest("hex");
|
|
2843
|
-
}
|
|
2844
|
-
|
|
2845
|
-
// src/verify/helpers.ts
|
|
2846
|
-
async function runVerification(input, check) {
|
|
2847
|
-
const startedAt = Date.now();
|
|
2848
|
-
const results = [];
|
|
2849
|
-
const record = (result) => {
|
|
2850
|
-
results.push(result);
|
|
2851
|
-
input.onEvent?.({ type: "verify:file-checked", file: result });
|
|
2852
|
-
};
|
|
2853
|
-
await check(record);
|
|
2854
|
-
return {
|
|
2855
|
-
targetId: input.targetId,
|
|
2856
|
-
kind: input.kind,
|
|
2857
|
-
isValid: results.every((r) => r.status === VerifyFileStatuses.OK),
|
|
2858
|
-
issues: results.filter((r) => r.status !== VerifyFileStatuses.OK),
|
|
2859
|
-
checkedFiles: results.length,
|
|
2860
|
-
durationMs: Date.now() - startedAt
|
|
2861
|
-
};
|
|
2862
|
-
}
|
|
2863
|
-
async function verifyHashedFile(input) {
|
|
2864
|
-
if (!await fileExists(input.path)) {
|
|
2865
|
-
return {
|
|
2866
|
-
path: input.path,
|
|
2867
|
-
category: input.category,
|
|
2868
|
-
status: VerifyFileStatuses.MISSING,
|
|
2869
|
-
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2870
|
-
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2871
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2872
|
-
};
|
|
2873
|
-
}
|
|
2874
|
-
if (input.expectedSize !== void 0) {
|
|
2875
|
-
const size = await fileSize(input.path);
|
|
2876
|
-
if (size !== input.expectedSize) {
|
|
2877
|
-
return {
|
|
2878
|
-
path: input.path,
|
|
2879
|
-
category: input.category,
|
|
2880
|
-
status: VerifyFileStatuses.WRONG_SIZE,
|
|
2881
|
-
expectedSize: input.expectedSize,
|
|
2882
|
-
actualSize: size,
|
|
2883
|
-
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2884
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2885
|
-
};
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
if (input.expectedSha1 !== void 0) {
|
|
2889
|
-
const actualSha1 = await sha1OfFile(input.path);
|
|
2890
|
-
if (actualSha1 !== input.expectedSha1) {
|
|
2891
|
-
return {
|
|
2892
|
-
path: input.path,
|
|
2893
|
-
category: input.category,
|
|
2894
|
-
status: VerifyFileStatuses.CORRUPT,
|
|
2895
|
-
expectedSha1: input.expectedSha1,
|
|
2896
|
-
actualSha1,
|
|
2897
|
-
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2898
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2899
|
-
};
|
|
2900
|
-
}
|
|
2901
|
-
}
|
|
2902
|
-
return {
|
|
2903
|
-
path: input.path,
|
|
2904
|
-
category: input.category,
|
|
2905
|
-
status: VerifyFileStatuses.OK,
|
|
2906
|
-
...input.expectedSha1 !== void 0 ? { expectedSha1: input.expectedSha1 } : {},
|
|
2907
|
-
...input.expectedSize !== void 0 ? { expectedSize: input.expectedSize } : {},
|
|
2908
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2909
|
-
};
|
|
2910
|
-
}
|
|
2911
|
-
async function verifyExistence(input) {
|
|
2912
|
-
if (await fileExists(input.path)) {
|
|
2913
|
-
return {
|
|
2914
|
-
path: input.path,
|
|
2915
|
-
category: input.category,
|
|
2916
|
-
status: VerifyFileStatuses.OK,
|
|
2917
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2918
|
-
};
|
|
2919
|
-
}
|
|
2920
|
-
return {
|
|
2921
|
-
path: input.path,
|
|
2922
|
-
category: input.category,
|
|
2923
|
-
status: VerifyFileStatuses.MISSING,
|
|
2924
|
-
...input.url !== void 0 ? { url: input.url } : {}
|
|
2925
|
-
};
|
|
2926
|
-
}
|
|
2927
|
-
async function findForgeVersionJsonPath(directory, minecraftVersion) {
|
|
2928
|
-
const versionsDir = targetPaths.versionsDir(directory);
|
|
2929
|
-
const dirs = await listChildDirectories(versionsDir);
|
|
2930
|
-
for (const id of dirs) {
|
|
2931
|
-
if (!id.startsWith(`${minecraftVersion}-forge-`)) continue;
|
|
2932
|
-
const jsonPath = targetPaths.versionJson(directory, id);
|
|
2933
|
-
if (!await fileExists(jsonPath)) {
|
|
2934
|
-
return jsonPath;
|
|
2935
|
-
}
|
|
2936
|
-
const parsed = await tryParseInheritsFrom(jsonPath);
|
|
2937
|
-
if (parsed === minecraftVersion) return jsonPath;
|
|
2938
|
-
}
|
|
2939
|
-
return null;
|
|
2940
|
-
}
|
|
2941
|
-
async function tryParseInheritsFrom(jsonPath) {
|
|
2942
|
-
try {
|
|
2943
|
-
const parsed = JSON.parse(await readText(jsonPath));
|
|
2944
|
-
return parsed.inheritsFrom;
|
|
2945
|
-
} catch {
|
|
2946
|
-
return void 0;
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
|
|
2950
|
-
// src/verify/fabric.ts
|
|
2951
|
-
async function verifyFabric(input) {
|
|
2952
|
-
if (input.target.loader.type !== Loaders.FABRIC) {
|
|
2953
|
-
throw new MinecraftKitError(
|
|
2954
|
-
"INVALID_INPUT",
|
|
2955
|
-
`verify.fabric requires a Fabric target (got ${input.target.loader.type})`
|
|
2956
|
-
);
|
|
2957
|
-
}
|
|
2958
|
-
const loader = input.target.loader;
|
|
2959
|
-
return runVerification(
|
|
2960
|
-
{
|
|
2961
|
-
targetId: input.target.id,
|
|
2962
|
-
kind: VerificationKinds.FABRIC,
|
|
2963
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
2964
|
-
},
|
|
2965
|
-
async (record) => {
|
|
2966
|
-
record(
|
|
2967
|
-
await verifyExistence({
|
|
2968
|
-
path: targetPaths.versionJson(input.target.directory, loader.profile.id),
|
|
2969
|
-
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2970
|
-
})
|
|
2971
|
-
);
|
|
2972
|
-
const fabricLibraries = planLibraryDownloads({
|
|
2973
|
-
libraries: loader.profile.libraries,
|
|
2974
|
-
directory: input.target.directory,
|
|
2975
|
-
system: input.target.runtime.system,
|
|
2976
|
-
versionId: input.target.minecraft.version,
|
|
2977
|
-
category: "fabric-library"
|
|
2978
|
-
});
|
|
2979
|
-
for (const action of fabricLibraries.downloads) {
|
|
2980
|
-
record(
|
|
2981
|
-
await verifyHashedFile({
|
|
2982
|
-
path: action.target,
|
|
2983
|
-
expectedSha1: action.expectedSha1,
|
|
2984
|
-
expectedSize: action.expectedSize,
|
|
2985
|
-
...action.url ? { url: action.url } : {},
|
|
2986
|
-
category: VerifyFileCategories.LOADER_LIBRARY
|
|
2987
|
-
})
|
|
2988
|
-
);
|
|
2989
|
-
}
|
|
2990
|
-
}
|
|
2991
|
-
);
|
|
2992
|
-
}
|
|
2993
|
-
|
|
2994
|
-
// src/verify/forge.ts
|
|
2995
|
-
async function verifyForge(input) {
|
|
2996
|
-
if (input.target.loader.type !== Loaders.FORGE) {
|
|
2997
|
-
throw new MinecraftKitError(
|
|
2998
|
-
"INVALID_INPUT",
|
|
2999
|
-
`verify.forge requires a Forge target (got ${input.target.loader.type})`
|
|
3000
|
-
);
|
|
3001
|
-
}
|
|
3002
|
-
return runVerification(
|
|
3003
|
-
{
|
|
3004
|
-
targetId: input.target.id,
|
|
3005
|
-
kind: VerificationKinds.FORGE,
|
|
3006
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
3007
|
-
},
|
|
3008
|
-
async (record) => {
|
|
3009
|
-
const forgeVersionJsonPath = await findForgeVersionJsonPath(
|
|
3010
|
-
input.target.directory,
|
|
3011
|
-
input.target.minecraft.version
|
|
3012
|
-
);
|
|
3013
|
-
if (forgeVersionJsonPath === null) return;
|
|
3014
|
-
record(
|
|
3015
|
-
await verifyExistence({
|
|
3016
|
-
path: forgeVersionJsonPath,
|
|
3017
|
-
category: VerifyFileCategories.LOADER_LIBRARY
|
|
3018
|
-
})
|
|
3019
|
-
);
|
|
3020
|
-
if (!await fileExists(forgeVersionJsonPath)) return;
|
|
3021
|
-
let parsed;
|
|
3022
|
-
try {
|
|
3023
|
-
parsed = JSON.parse(await readText(forgeVersionJsonPath));
|
|
3024
|
-
} catch {
|
|
3025
|
-
record({
|
|
3026
|
-
path: forgeVersionJsonPath,
|
|
3027
|
-
category: VerifyFileCategories.LOADER_LIBRARY,
|
|
3028
|
-
status: VerifyFileStatuses.CORRUPT
|
|
3029
|
-
});
|
|
3030
|
-
return;
|
|
3031
|
-
}
|
|
3032
|
-
const forgeLibraries = planLibraryDownloads({
|
|
3033
|
-
libraries: parsed.libraries,
|
|
3034
|
-
directory: input.target.directory,
|
|
3035
|
-
system: input.target.runtime.system,
|
|
3036
|
-
versionId: input.target.minecraft.version,
|
|
3037
|
-
category: "forge-library"
|
|
3038
|
-
});
|
|
3039
|
-
for (const action of forgeLibraries.downloads) {
|
|
3040
|
-
record(
|
|
3041
|
-
await verifyHashedFile({
|
|
3042
|
-
path: action.target,
|
|
3043
|
-
expectedSha1: action.expectedSha1,
|
|
3044
|
-
expectedSize: action.expectedSize,
|
|
3045
|
-
...action.url ? { url: action.url } : {},
|
|
3046
|
-
category: VerifyFileCategories.LOADER_LIBRARY
|
|
3047
|
-
})
|
|
3048
|
-
);
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
);
|
|
3052
|
-
}
|
|
3053
|
-
|
|
3054
|
-
// src/verify/minecraft.ts
|
|
3055
|
-
async function verifyMinecraft(input) {
|
|
3056
|
-
return runVerification(
|
|
3057
|
-
{
|
|
3058
|
-
targetId: input.target.id,
|
|
3059
|
-
kind: VerificationKinds.MINECRAFT,
|
|
3060
|
-
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
3061
|
-
},
|
|
3062
|
-
async (record) => {
|
|
3063
|
-
const { directory, minecraft, runtime } = input.target;
|
|
3064
|
-
record(
|
|
3065
|
-
await verifyHashedFile({
|
|
3066
|
-
path: targetPaths.versionJar(directory, minecraft.version),
|
|
3067
|
-
expectedSha1: minecraft.manifest.downloads.client.sha1,
|
|
3068
|
-
expectedSize: minecraft.manifest.downloads.client.size,
|
|
3069
|
-
url: minecraft.manifest.downloads.client.url,
|
|
3070
|
-
category: VerifyFileCategories.CLIENT_JAR
|
|
3071
|
-
})
|
|
3072
|
-
);
|
|
3073
|
-
record(
|
|
3074
|
-
await verifyExistence({
|
|
3075
|
-
path: targetPaths.versionJson(directory, minecraft.version),
|
|
3076
|
-
category: VerifyFileCategories.CLIENT_JAR
|
|
3077
|
-
})
|
|
3078
|
-
);
|
|
3079
|
-
if (minecraft.manifest.logging?.client) {
|
|
3080
|
-
const logging = minecraft.manifest.logging.client;
|
|
3081
|
-
record(
|
|
3082
|
-
await verifyHashedFile({
|
|
3083
|
-
path: targetPaths.loggingConfig(directory, logging.file.id),
|
|
3084
|
-
expectedSha1: logging.file.sha1,
|
|
3085
|
-
expectedSize: logging.file.size,
|
|
3086
|
-
url: logging.file.url,
|
|
3087
|
-
category: VerifyFileCategories.LOGGING_CONFIG
|
|
3088
|
-
})
|
|
3089
|
-
);
|
|
3090
|
-
}
|
|
3091
|
-
const libraryPlan = planLibraryDownloads({
|
|
3092
|
-
libraries: minecraft.manifest.libraries,
|
|
3093
|
-
directory,
|
|
3094
|
-
system: runtime.system,
|
|
3095
|
-
versionId: minecraft.version,
|
|
3096
|
-
category: "library"
|
|
3352
|
+
loader = {
|
|
3353
|
+
type: Loaders.VANILLA,
|
|
3354
|
+
minecraftVersion: minecraft.version,
|
|
3355
|
+
minecraft
|
|
3356
|
+
};
|
|
3357
|
+
} else if (input.loader.type === Loaders.FABRIC) {
|
|
3358
|
+
loader = await this.ctx.fabric.resolve({
|
|
3359
|
+
minecraftVersion: minecraft.version,
|
|
3360
|
+
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
3361
|
+
...input.loader.version !== void 0 ? { loaderVersion: input.loader.version } : {},
|
|
3362
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3097
3363
|
});
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
expectedSize: action.expectedSize,
|
|
3104
|
-
url: action.url,
|
|
3105
|
-
category: VerifyFileCategories.LIBRARY
|
|
3106
|
-
})
|
|
3107
|
-
);
|
|
3108
|
-
}
|
|
3109
|
-
const indexUrl = minecraft.manifest.assetIndex.url;
|
|
3110
|
-
const indexPath = targetPaths.assetIndex(directory, minecraft.manifest.assetIndex.id);
|
|
3111
|
-
record(
|
|
3112
|
-
await verifyHashedFile({
|
|
3113
|
-
path: indexPath,
|
|
3114
|
-
expectedSha1: minecraft.manifest.assetIndex.sha1,
|
|
3115
|
-
expectedSize: minecraft.manifest.assetIndex.size,
|
|
3116
|
-
url: indexUrl,
|
|
3117
|
-
category: VerifyFileCategories.ASSET_INDEX
|
|
3118
|
-
})
|
|
3119
|
-
);
|
|
3120
|
-
const indexDocument = await fetchJson(input.http, input.cache, {
|
|
3121
|
-
url: indexUrl,
|
|
3122
|
-
cacheKey: `asset-index:${minecraft.manifest.assetIndex.id}:${minecraft.manifest.assetIndex.sha1}`,
|
|
3364
|
+
} else {
|
|
3365
|
+
loader = await this.ctx.forge.resolve({
|
|
3366
|
+
minecraftVersion: minecraft.version,
|
|
3367
|
+
...input.loader.preference !== void 0 ? { preference: input.loader.preference } : {},
|
|
3368
|
+
...input.loader.version !== void 0 ? { forgeVersion: input.loader.version } : {},
|
|
3123
3369
|
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3124
3370
|
});
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3371
|
+
}
|
|
3372
|
+
return this.create({
|
|
3373
|
+
id: input.id,
|
|
3374
|
+
directory: input.directory,
|
|
3375
|
+
minecraft,
|
|
3376
|
+
loader,
|
|
3377
|
+
runtime
|
|
3378
|
+
});
|
|
3379
|
+
}
|
|
3380
|
+
/** Scan a root directory for Minecraft installations. Returns only what is on disk. */
|
|
3381
|
+
async list(input) {
|
|
3382
|
+
if (!await dirExists(input.rootDir)) return [];
|
|
3383
|
+
const subdirs = await listChildDirectories(input.rootDir);
|
|
3384
|
+
const results = [];
|
|
3385
|
+
for (const id of subdirs) {
|
|
3386
|
+
const directory = path__default.default.join(input.rootDir, id);
|
|
3387
|
+
const discovered = await discoverInstallation(id, directory);
|
|
3388
|
+
if (discovered) results.push(discovered);
|
|
3389
|
+
}
|
|
3390
|
+
return results;
|
|
3391
|
+
}
|
|
3392
|
+
};
|
|
3393
|
+
async function discoverInstallation(id, directory) {
|
|
3394
|
+
const versionsDir = path__default.default.join(directory, VERSIONS_DIR);
|
|
3395
|
+
const librariesDir = path__default.default.join(directory, LIBRARIES_DIR);
|
|
3396
|
+
const assetsDir = path__default.default.join(directory, ASSETS_DIR);
|
|
3397
|
+
const looksLikeInstall = await dirExists(versionsDir) && (await dirExists(librariesDir) || await dirExists(assetsDir));
|
|
3398
|
+
if (!looksLikeInstall) return null;
|
|
3399
|
+
const versionDirs = await listChildDirectories(versionsDir);
|
|
3400
|
+
const minecraftVersions = [];
|
|
3401
|
+
const loaders = [];
|
|
3402
|
+
for (const versionId of versionDirs) {
|
|
3403
|
+
const hint = inferLoaderFromVersionId(versionId);
|
|
3404
|
+
if (hint) {
|
|
3405
|
+
loaders.push(hint);
|
|
3406
|
+
if (hint.minecraftVersion && !minecraftVersions.includes(hint.minecraftVersion)) {
|
|
3407
|
+
minecraftVersions.push(hint.minecraftVersion);
|
|
3147
3408
|
}
|
|
3409
|
+
} else {
|
|
3410
|
+
minecraftVersions.push(versionId);
|
|
3148
3411
|
}
|
|
3149
|
-
|
|
3412
|
+
}
|
|
3413
|
+
const runtime = await discoverRuntime(directory);
|
|
3414
|
+
return { id, directory, minecraftVersions, loaders, ...runtime ? { runtime } : {} };
|
|
3150
3415
|
}
|
|
3151
|
-
async function
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
});
|
|
3166
|
-
} catch {
|
|
3167
|
-
record({
|
|
3168
|
-
path: input.target.runtime.manifestUrl,
|
|
3169
|
-
category: VerifyFileCategories.RUNTIME_FILE,
|
|
3170
|
-
status: VerifyFileStatuses.MISSING
|
|
3171
|
-
});
|
|
3172
|
-
return;
|
|
3173
|
-
}
|
|
3174
|
-
const runtimeRoot = targetPaths.runtimeRoot(
|
|
3175
|
-
input.target.directory,
|
|
3176
|
-
input.target.runtime.component,
|
|
3177
|
-
input.target.runtime.installRoot
|
|
3178
|
-
);
|
|
3179
|
-
for (const [relative, entry] of Object.entries(manifest.files)) {
|
|
3180
|
-
if (entry.type !== "file") continue;
|
|
3181
|
-
record(
|
|
3182
|
-
await verifyHashedFile({
|
|
3183
|
-
path: path__default.default.join(runtimeRoot, relative),
|
|
3184
|
-
expectedSha1: entry.downloads.raw.sha1,
|
|
3185
|
-
expectedSize: entry.downloads.raw.size,
|
|
3186
|
-
url: entry.downloads.raw.url,
|
|
3187
|
-
category: VerifyFileCategories.RUNTIME_FILE
|
|
3188
|
-
})
|
|
3189
|
-
);
|
|
3190
|
-
}
|
|
3416
|
+
async function discoverRuntime(directory) {
|
|
3417
|
+
const runtimeDir = path__default.default.join(directory, RUNTIMES_DIR);
|
|
3418
|
+
if (!await dirExists(runtimeDir)) return void 0;
|
|
3419
|
+
let components;
|
|
3420
|
+
try {
|
|
3421
|
+
components = await listChildDirectories(runtimeDir);
|
|
3422
|
+
} catch {
|
|
3423
|
+
return void 0;
|
|
3424
|
+
}
|
|
3425
|
+
for (const component of components) {
|
|
3426
|
+
const root = path__default.default.join(runtimeDir, component);
|
|
3427
|
+
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");
|
|
3428
|
+
if (await fileExists(javaPath)) {
|
|
3429
|
+
return { component, javaPath };
|
|
3191
3430
|
}
|
|
3192
|
-
|
|
3431
|
+
}
|
|
3432
|
+
return void 0;
|
|
3433
|
+
}
|
|
3434
|
+
function inferLoaderFromVersionId(versionId) {
|
|
3435
|
+
const fabricMatch = /^fabric-loader-([^-]+)-(.+)$/.exec(versionId);
|
|
3436
|
+
if (fabricMatch?.[1] && fabricMatch[2]) {
|
|
3437
|
+
return { type: Loaders.FABRIC, version: fabricMatch[1], minecraftVersion: fabricMatch[2] };
|
|
3438
|
+
}
|
|
3439
|
+
const forgeMatch = /^([^-]+)-forge-(.+)$/.exec(versionId);
|
|
3440
|
+
if (forgeMatch?.[1] && forgeMatch[2]) {
|
|
3441
|
+
return { type: Loaders.FORGE, minecraftVersion: forgeMatch[1], version: forgeMatch[2] };
|
|
3442
|
+
}
|
|
3443
|
+
return null;
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
// src/update/runner.ts
|
|
3447
|
+
async function planUpdate(input) {
|
|
3448
|
+
return planInstall({
|
|
3449
|
+
target: input.target,
|
|
3450
|
+
http: input.http,
|
|
3451
|
+
cache: input.cache,
|
|
3452
|
+
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
async function runUpdate(input) {
|
|
3456
|
+
const report = await runInstall({
|
|
3457
|
+
plan: input.plan,
|
|
3458
|
+
http: input.http,
|
|
3459
|
+
cache: input.cache,
|
|
3460
|
+
spawner: input.spawner,
|
|
3461
|
+
...input.signal !== void 0 ? { signal: input.signal } : {},
|
|
3462
|
+
...input.onEvent !== void 0 ? { onEvent: input.onEvent } : {}
|
|
3463
|
+
});
|
|
3464
|
+
return {
|
|
3465
|
+
targetId: report.targetId,
|
|
3466
|
+
bytesDownloaded: report.bytesDownloaded,
|
|
3467
|
+
actionsCompleted: report.actionsCompleted,
|
|
3468
|
+
actionsSkipped: report.actionsSkipped,
|
|
3469
|
+
durationMs: report.durationMs
|
|
3470
|
+
};
|
|
3193
3471
|
}
|
|
3194
3472
|
|
|
3195
3473
|
// src/versions/fabric.ts
|
|
@@ -3523,15 +3801,23 @@ function pickLatestAcrossComponents(entries) {
|
|
|
3523
3801
|
return { component: bestComponent, entry: bestEntry };
|
|
3524
3802
|
}
|
|
3525
3803
|
function toResolved(component, platformKey, entry, system) {
|
|
3804
|
+
const majorVersion = parseMajorVersion(entry.version.name);
|
|
3526
3805
|
return {
|
|
3527
3806
|
component,
|
|
3528
3807
|
platformKey,
|
|
3529
3808
|
versionName: entry.version.name,
|
|
3809
|
+
...majorVersion !== void 0 ? { majorVersion } : {},
|
|
3530
3810
|
system,
|
|
3531
3811
|
manifestUrl: entry.manifest.url,
|
|
3532
3812
|
manifestSha1: entry.manifest.sha1
|
|
3533
3813
|
};
|
|
3534
3814
|
}
|
|
3815
|
+
function parseMajorVersion(versionName) {
|
|
3816
|
+
const match = /^(\d+)/.exec(versionName);
|
|
3817
|
+
if (!match || !match[1]) return void 0;
|
|
3818
|
+
const parsed = Number.parseInt(match[1], 10);
|
|
3819
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3820
|
+
}
|
|
3535
3821
|
|
|
3536
3822
|
// src/kit.ts
|
|
3537
3823
|
var MinecraftKit = class {
|
|
@@ -3562,7 +3848,12 @@ var MinecraftKit = class {
|
|
|
3562
3848
|
...opts?.signal !== void 0 ? { signal: opts.signal } : {},
|
|
3563
3849
|
...opts?.onEvent !== void 0 ? { onEvent: opts.onEvent } : {}
|
|
3564
3850
|
});
|
|
3565
|
-
const
|
|
3851
|
+
const carryInstall = (opts) => ({
|
|
3852
|
+
...carry(opts),
|
|
3853
|
+
...opts?.pauseController !== void 0 ? { pauseController: opts.pauseController } : {},
|
|
3854
|
+
...opts?.actionCategories !== void 0 ? { actionCategories: opts.actionCategories } : {}
|
|
3855
|
+
});
|
|
3856
|
+
const runInstallPlan = (plan, opts) => runInstall({ plan, http, cache, spawner, ...carryInstall(opts) });
|
|
3566
3857
|
this.install = {
|
|
3567
3858
|
plan: (target, opts) => planInstall({ target, http, cache, ...carry(opts) }),
|
|
3568
3859
|
run: runInstallPlan,
|
|
@@ -3612,10 +3903,17 @@ var MinecraftKit = class {
|
|
|
3612
3903
|
runtime: {
|
|
3613
3904
|
plan: (target, opts) => planRuntimeRepair(repairArgs(target, opts)),
|
|
3614
3905
|
run: runRepairPlan
|
|
3615
|
-
}
|
|
3906
|
+
},
|
|
3907
|
+
all: (target, opts) => repairAll({
|
|
3908
|
+
target,
|
|
3909
|
+
http,
|
|
3910
|
+
cache,
|
|
3911
|
+
spawner,
|
|
3912
|
+
...carry(opts)
|
|
3913
|
+
})
|
|
3616
3914
|
};
|
|
3617
3915
|
this.launch = {
|
|
3618
|
-
compose: (target, opts) => composeLaunch({ target, options: opts }),
|
|
3916
|
+
compose: (target, opts) => composeLaunch({ target, options: opts, logger }),
|
|
3619
3917
|
run: (composition, opts) => runLaunch({
|
|
3620
3918
|
composition,
|
|
3621
3919
|
...opts !== void 0 ? { options: opts } : {},
|
|
@@ -3625,6 +3923,247 @@ var MinecraftKit = class {
|
|
|
3625
3923
|
}
|
|
3626
3924
|
};
|
|
3627
3925
|
|
|
3926
|
+
// src/core/pause-controller.ts
|
|
3927
|
+
var PauseController = class {
|
|
3928
|
+
#paused = false;
|
|
3929
|
+
#waiters = [];
|
|
3930
|
+
get paused() {
|
|
3931
|
+
return this.#paused;
|
|
3932
|
+
}
|
|
3933
|
+
pause() {
|
|
3934
|
+
this.#paused = true;
|
|
3935
|
+
}
|
|
3936
|
+
resume() {
|
|
3937
|
+
this.#paused = false;
|
|
3938
|
+
const list = this.#waiters;
|
|
3939
|
+
this.#waiters = [];
|
|
3940
|
+
for (const resolve of list) resolve();
|
|
3941
|
+
}
|
|
3942
|
+
waitWhilePaused() {
|
|
3943
|
+
if (!this.#paused) return Promise.resolve();
|
|
3944
|
+
return new Promise((resolve) => this.#waiters.push(resolve));
|
|
3945
|
+
}
|
|
3946
|
+
};
|
|
3947
|
+
|
|
3948
|
+
// src/install/progress-tracker.ts
|
|
3949
|
+
var InstallStages = {
|
|
3950
|
+
PREPARE: "prepare",
|
|
3951
|
+
RUNTIME: "runtime",
|
|
3952
|
+
MINECRAFT: "minecraft",
|
|
3953
|
+
LOADER: "loader",
|
|
3954
|
+
FINALIZE: "finalize"
|
|
3955
|
+
};
|
|
3956
|
+
var STAGE_FOR_CATEGORY = {
|
|
3957
|
+
"runtime-file": InstallStages.RUNTIME,
|
|
3958
|
+
"client-jar": InstallStages.MINECRAFT,
|
|
3959
|
+
library: InstallStages.MINECRAFT,
|
|
3960
|
+
"asset-index": InstallStages.MINECRAFT,
|
|
3961
|
+
asset: InstallStages.MINECRAFT,
|
|
3962
|
+
"logging-config": InstallStages.MINECRAFT,
|
|
3963
|
+
"fabric-library": InstallStages.LOADER,
|
|
3964
|
+
"forge-library": InstallStages.LOADER,
|
|
3965
|
+
"forge-installer": InstallStages.LOADER
|
|
3966
|
+
};
|
|
3967
|
+
var STAGE_FOR_PHASE = {
|
|
3968
|
+
[InstallPhases.PLANNING]: InstallStages.PREPARE,
|
|
3969
|
+
[InstallPhases.DOWNLOADING_VERSION_MANIFEST]: InstallStages.PREPARE,
|
|
3970
|
+
[InstallPhases.INSTALLING_RUNTIME]: InstallStages.RUNTIME,
|
|
3971
|
+
[InstallPhases.DOWNLOADING_CLIENT_JAR]: InstallStages.MINECRAFT,
|
|
3972
|
+
[InstallPhases.DOWNLOADING_LIBRARIES]: InstallStages.MINECRAFT,
|
|
3973
|
+
[InstallPhases.DOWNLOADING_ASSET_INDEX]: InstallStages.MINECRAFT,
|
|
3974
|
+
[InstallPhases.DOWNLOADING_ASSETS]: InstallStages.MINECRAFT,
|
|
3975
|
+
[InstallPhases.EXTRACTING_NATIVES]: InstallStages.MINECRAFT,
|
|
3976
|
+
[InstallPhases.WRITING_FILES]: InstallStages.MINECRAFT,
|
|
3977
|
+
[InstallPhases.INSTALLING_FABRIC]: InstallStages.LOADER,
|
|
3978
|
+
[InstallPhases.INSTALLING_FORGE]: InstallStages.LOADER,
|
|
3979
|
+
[InstallPhases.RUNNING_FORGE_PROCESSORS]: InstallStages.LOADER,
|
|
3980
|
+
[InstallPhases.COMPLETED]: InstallStages.FINALIZE
|
|
3981
|
+
};
|
|
3982
|
+
var ALL_STAGES = [
|
|
3983
|
+
InstallStages.PREPARE,
|
|
3984
|
+
InstallStages.RUNTIME,
|
|
3985
|
+
InstallStages.MINECRAFT,
|
|
3986
|
+
InstallStages.LOADER,
|
|
3987
|
+
InstallStages.FINALIZE
|
|
3988
|
+
];
|
|
3989
|
+
function createInstallProgressTracker(plan, options = {}) {
|
|
3990
|
+
const throttleMs = options.throttleMs ?? 100;
|
|
3991
|
+
const stageOfTarget = /* @__PURE__ */ new Map();
|
|
3992
|
+
const expectedSizeOf = /* @__PURE__ */ new Map();
|
|
3993
|
+
const stageTotals = {
|
|
3994
|
+
prepare: 0,
|
|
3995
|
+
runtime: 0,
|
|
3996
|
+
minecraft: 0,
|
|
3997
|
+
loader: 0,
|
|
3998
|
+
finalize: 0
|
|
3999
|
+
};
|
|
4000
|
+
let overallTotal = 0;
|
|
4001
|
+
for (const action of plan.actions) {
|
|
4002
|
+
if (action.kind !== "download-file") continue;
|
|
4003
|
+
const stage = STAGE_FOR_CATEGORY[action.category] ?? InstallStages.MINECRAFT;
|
|
4004
|
+
stageOfTarget.set(action.target, stage);
|
|
4005
|
+
const size = action.expectedSize ?? 0;
|
|
4006
|
+
expectedSizeOf.set(action.target, size);
|
|
4007
|
+
stageTotals[stage] += size;
|
|
4008
|
+
overallTotal += size;
|
|
4009
|
+
}
|
|
4010
|
+
const stageDone = {
|
|
4011
|
+
prepare: 0,
|
|
4012
|
+
runtime: 0,
|
|
4013
|
+
minecraft: 0,
|
|
4014
|
+
loader: 0,
|
|
4015
|
+
finalize: 0
|
|
4016
|
+
};
|
|
4017
|
+
const stageInFlight = {
|
|
4018
|
+
prepare: 0,
|
|
4019
|
+
runtime: 0,
|
|
4020
|
+
minecraft: 0,
|
|
4021
|
+
loader: 0,
|
|
4022
|
+
finalize: 0
|
|
4023
|
+
};
|
|
4024
|
+
let totalDone = 0;
|
|
4025
|
+
let totalInFlight = 0;
|
|
4026
|
+
const inFlightByTarget = /* @__PURE__ */ new Map();
|
|
4027
|
+
let currentStage = InstallStages.PREPARE;
|
|
4028
|
+
let currentFile;
|
|
4029
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4030
|
+
let lastPushAt = 0;
|
|
4031
|
+
let pending = false;
|
|
4032
|
+
let pendingTimer = null;
|
|
4033
|
+
let finished = false;
|
|
4034
|
+
const snapshot = () => {
|
|
4035
|
+
const stageTotal = stageTotals[currentStage];
|
|
4036
|
+
const stageBytes = stageDone[currentStage] + stageInFlight[currentStage];
|
|
4037
|
+
const overallBytes = totalDone + totalInFlight;
|
|
4038
|
+
return {
|
|
4039
|
+
stage: currentStage,
|
|
4040
|
+
stagePercent: stageTotal > 0 ? clamp(stageBytes / stageTotal * 100) : 0,
|
|
4041
|
+
overallPercent: overallTotal > 0 ? clamp(overallBytes / overallTotal * 100) : 0,
|
|
4042
|
+
bytesDownloaded: overallBytes,
|
|
4043
|
+
totalBytes: stageTotal,
|
|
4044
|
+
...currentFile !== void 0 ? { currentFile } : {}
|
|
4045
|
+
};
|
|
4046
|
+
};
|
|
4047
|
+
const clearTimer = () => {
|
|
4048
|
+
if (pendingTimer) {
|
|
4049
|
+
clearTimeout(pendingTimer);
|
|
4050
|
+
pendingTimer = null;
|
|
4051
|
+
}
|
|
4052
|
+
};
|
|
4053
|
+
const push = () => {
|
|
4054
|
+
pending = false;
|
|
4055
|
+
clearTimer();
|
|
4056
|
+
lastPushAt = Date.now();
|
|
4057
|
+
const snap = snapshot();
|
|
4058
|
+
for (const listener of listeners) listener(snap);
|
|
4059
|
+
};
|
|
4060
|
+
const schedulePush = () => {
|
|
4061
|
+
if (finished) return;
|
|
4062
|
+
const elapsed = Date.now() - lastPushAt;
|
|
4063
|
+
if (elapsed >= throttleMs) {
|
|
4064
|
+
push();
|
|
4065
|
+
return;
|
|
4066
|
+
}
|
|
4067
|
+
if (pending) return;
|
|
4068
|
+
pending = true;
|
|
4069
|
+
pendingTimer = setTimeout(push, throttleMs - elapsed);
|
|
4070
|
+
};
|
|
4071
|
+
const onEvent = (event) => {
|
|
4072
|
+
switch (event.type) {
|
|
4073
|
+
case "install:phase-changed": {
|
|
4074
|
+
const next = STAGE_FOR_PHASE[event.phase];
|
|
4075
|
+
if (next && next !== currentStage) {
|
|
4076
|
+
currentStage = next;
|
|
4077
|
+
currentFile = void 0;
|
|
4078
|
+
push();
|
|
4079
|
+
}
|
|
4080
|
+
return;
|
|
4081
|
+
}
|
|
4082
|
+
case "download:started": {
|
|
4083
|
+
const stage = stageOfTarget.get(event.file.target) ?? currentStage;
|
|
4084
|
+
inFlightByTarget.set(event.file.target, { stage, bytes: 0 });
|
|
4085
|
+
currentFile = event.file.target;
|
|
4086
|
+
schedulePush();
|
|
4087
|
+
return;
|
|
4088
|
+
}
|
|
4089
|
+
case "download:progress": {
|
|
4090
|
+
const entry = inFlightByTarget.get(event.file.target);
|
|
4091
|
+
if (entry) {
|
|
4092
|
+
const delta = event.bytesDownloaded - entry.bytes;
|
|
4093
|
+
if (delta !== 0) {
|
|
4094
|
+
entry.bytes = event.bytesDownloaded;
|
|
4095
|
+
stageInFlight[entry.stage] += delta;
|
|
4096
|
+
totalInFlight += delta;
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
currentFile = event.file.target;
|
|
4100
|
+
schedulePush();
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
case "download:skipped": {
|
|
4104
|
+
const stage = stageOfTarget.get(event.file.target);
|
|
4105
|
+
if (stage) {
|
|
4106
|
+
const size = expectedSizeOf.get(event.file.target) ?? 0;
|
|
4107
|
+
stageDone[stage] += size;
|
|
4108
|
+
totalDone += size;
|
|
4109
|
+
schedulePush();
|
|
4110
|
+
}
|
|
4111
|
+
return;
|
|
4112
|
+
}
|
|
4113
|
+
case "download:completed": {
|
|
4114
|
+
const entry = inFlightByTarget.get(event.file.target);
|
|
4115
|
+
if (entry) {
|
|
4116
|
+
const finalBytes = event.bytes ?? entry.bytes;
|
|
4117
|
+
stageInFlight[entry.stage] -= entry.bytes;
|
|
4118
|
+
totalInFlight -= entry.bytes;
|
|
4119
|
+
stageDone[entry.stage] += finalBytes;
|
|
4120
|
+
totalDone += finalBytes;
|
|
4121
|
+
inFlightByTarget.delete(event.file.target);
|
|
4122
|
+
} else {
|
|
4123
|
+
const stage = stageOfTarget.get(event.file.target);
|
|
4124
|
+
if (stage) {
|
|
4125
|
+
const bytes = event.bytes ?? expectedSizeOf.get(event.file.target) ?? 0;
|
|
4126
|
+
stageDone[stage] += bytes;
|
|
4127
|
+
totalDone += bytes;
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
4130
|
+
schedulePush();
|
|
4131
|
+
return;
|
|
4132
|
+
}
|
|
4133
|
+
default:
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
};
|
|
4137
|
+
return {
|
|
4138
|
+
onEvent,
|
|
4139
|
+
snapshot,
|
|
4140
|
+
subscribe(listener) {
|
|
4141
|
+
listeners.add(listener);
|
|
4142
|
+
listener(snapshot());
|
|
4143
|
+
return () => listeners.delete(listener);
|
|
4144
|
+
},
|
|
4145
|
+
finish() {
|
|
4146
|
+
finished = true;
|
|
4147
|
+
clearTimer();
|
|
4148
|
+
currentStage = InstallStages.FINALIZE;
|
|
4149
|
+
currentFile = void 0;
|
|
4150
|
+
totalDone = overallTotal;
|
|
4151
|
+
totalInFlight = 0;
|
|
4152
|
+
for (const stage of ALL_STAGES) {
|
|
4153
|
+
stageDone[stage] = stageTotals[stage];
|
|
4154
|
+
stageInFlight[stage] = 0;
|
|
4155
|
+
}
|
|
4156
|
+
const snap = snapshot();
|
|
4157
|
+
for (const listener of listeners) listener(snap);
|
|
4158
|
+
}
|
|
4159
|
+
};
|
|
4160
|
+
}
|
|
4161
|
+
function clamp(value) {
|
|
4162
|
+
if (value <= 0) return 0;
|
|
4163
|
+
if (value >= 100) return 100;
|
|
4164
|
+
return value;
|
|
4165
|
+
}
|
|
4166
|
+
|
|
3628
4167
|
// src/types/events.ts
|
|
3629
4168
|
var EventTypes = {
|
|
3630
4169
|
INSTALL_PHASE_CHANGED: "install:phase-changed",
|
|
@@ -3749,6 +4288,7 @@ exports.HTTP_RETRY_MAX = HTTP_RETRY_MAX;
|
|
|
3749
4288
|
exports.HTTP_TIMEOUT_MS = HTTP_TIMEOUT_MS;
|
|
3750
4289
|
exports.InstallActionKinds = InstallActionKinds;
|
|
3751
4290
|
exports.InstallPhases = InstallPhases;
|
|
4291
|
+
exports.InstallStages = InstallStages;
|
|
3752
4292
|
exports.JAVA_EXECUTABLE = JAVA_EXECUTABLE;
|
|
3753
4293
|
exports.LAUNCH_PLACEHOLDERS = LAUNCH_PLACEHOLDERS;
|
|
3754
4294
|
exports.LEGACY_JVM_ARGS = LEGACY_JVM_ARGS;
|
|
@@ -3767,6 +4307,7 @@ exports.NODE_ARCH_TO_MOJANG_ARCH = NODE_ARCH_TO_MOJANG_ARCH;
|
|
|
3767
4307
|
exports.NODE_PLATFORM_TO_MOJANG_OS = NODE_PLATFORM_TO_MOJANG_OS;
|
|
3768
4308
|
exports.OperatingSystems = OperatingSystems;
|
|
3769
4309
|
exports.PROGRESS_EVENT_INTERVAL_MS = PROGRESS_EVENT_INTERVAL_MS;
|
|
4310
|
+
exports.PauseController = PauseController;
|
|
3770
4311
|
exports.RUNTIMES_DIR = RUNTIMES_DIR;
|
|
3771
4312
|
exports.RUNTIME_PLATFORM_KEYS = RUNTIME_PLATFORM_KEYS;
|
|
3772
4313
|
exports.RepairPhases = RepairPhases;
|
|
@@ -3782,20 +4323,26 @@ exports.VerifyFileCategories = VerifyFileCategories;
|
|
|
3782
4323
|
exports.VerifyFileStatuses = VerifyFileStatuses;
|
|
3783
4324
|
exports.VersionPreference = VersionPreference;
|
|
3784
4325
|
exports.consoleLogger = consoleLogger;
|
|
4326
|
+
exports.createInstallProgressTracker = createInstallProgressTracker;
|
|
3785
4327
|
exports.createMemoryCache = createMemoryCache;
|
|
3786
4328
|
exports.detectSystem = detectSystem;
|
|
3787
4329
|
exports.isErrorCode = isErrorCode;
|
|
3788
4330
|
exports.isMinecraftKitError = isMinecraftKitError;
|
|
3789
4331
|
exports.offlineUuidFor = offlineUuidFor;
|
|
4332
|
+
exports.parseMajorVersion = parseMajorVersion;
|
|
4333
|
+
exports.pickClientJarVersionId = pickClientJarVersionId;
|
|
3790
4334
|
exports.planFabricRepair = planFabricRepair;
|
|
3791
4335
|
exports.planForgeRepair = planForgeRepair;
|
|
3792
4336
|
exports.planMinecraftRepair = planMinecraftRepair;
|
|
3793
4337
|
exports.planRuntimeInstall = planRuntimeInstall;
|
|
3794
4338
|
exports.planRuntimeRepair = planRuntimeRepair;
|
|
3795
4339
|
exports.planStandaloneRuntimeInstall = planStandaloneRuntimeInstall;
|
|
4340
|
+
exports.repairAll = repairAll;
|
|
4341
|
+
exports.resolveLaunchVersion = resolveLaunchVersion;
|
|
3796
4342
|
exports.runRepair = runRepair;
|
|
3797
4343
|
exports.silentLogger = silentLogger;
|
|
3798
4344
|
exports.stripUuidDashes = stripUuidDashes;
|
|
4345
|
+
exports.targetPaths = targetPaths;
|
|
3799
4346
|
exports.verifyFabric = verifyFabric;
|
|
3800
4347
|
exports.verifyForge = verifyForge;
|
|
3801
4348
|
exports.verifyMinecraft = verifyMinecraft;
|