Compare commits

...
Author SHA1 Message Date
Glavo fbcc17472f [release/3.16] Add version entries for 26.3 snapshots and release candidates (#6879)
https://github.com/HMCL-dev/HMCL/pull/6849
https://github.com/HMCL-dev/HMCL/pull/6877
2026-09-16 21:48:50 +08:00
Glavo 7b85f0dfce [release/3.16] 优化加载模组列表的性能 (#6878)
https://github.com/HMCL-dev/HMCL/pull/6834
https://github.com/HMCL-dev/HMCL/pull/6837
2026-09-16 21:45:50 +08:00
GlavoandApostrophe b8a830dd40 [release/3.16] 解决导出 Modrinth 整合包因大文件导致的 Out Of Memory (#6876)
https://github.com/HMCL-dev/HMCL/pull/6631

Co-authored-by: Apostrophe <[email protected]>
2026-09-16 21:35:00 +08:00
GlavoandToobLac ce368caa67 [release/3.16] [Bugfix] 修复无法读取 zip 后缀名的模组的问题 (#6875)
https://github.com/HMCL-dev/HMCL/pull/6712

Co-authored-by: ToobLac <[email protected]>
2026-09-16 21:29:04 +08:00
Glavo 850ad82aa1 [release/3.16] fix: LegacyFabric 实例图标异常 (#6874)
https://github.com/HMCL-dev/HMCL/pull/6784
2026-09-16 21:22:58 +08:00
Glavo 552276e673 [release/3.16] 优化 SVG 图像支持 (#6873)
https://github.com/HMCL-dev/HMCL/pull/6613
https://github.com/HMCL-dev/HMCL/pull/6789
https://github.com/HMCL-dev/HMCL/pull/6790
2026-09-16 21:19:57 +08:00
Glavo e79855685e [release/3.16] 更新模组数据 (2026-07-21) (#6872)
https://github.com/HMCL-dev/HMCL/pull/6543
2026-09-16 21:14:22 +08:00
Glavo ad5afe7e06 [release/3.16] Add support for Minecraft 26.3-snapshot-10+ to unofficial supported platforms (#6871)
https://github.com/HMCL-dev/HMCL/pull/6870
2026-09-16 21:05:11 +08:00
19 changed files with 5896 additions and 910 deletions

No files matched your search

@@ -47,6 +47,7 @@ public enum VersionIconType {
case FORGE -> VersionIconType.FORGE;
case NEO_FORGE -> VersionIconType.NEO_FORGE;
case FABRIC -> VersionIconType.FABRIC;
case LEGACY_FABRIC -> VersionIconType.LEGACY_FABRIC;
case QUILT -> VersionIconType.QUILT;
case LITE_LOADER -> VersionIconType.CHICKEN;
case CLEANROOM -> VersionIconType.CLEANROOM;
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.image.apng.chunks.PngFrameControl;
import org.jackhuang.hmcl.ui.image.apng.error.PngException;
import org.jackhuang.hmcl.ui.image.apng.error.PngIntegrityException;
import org.jackhuang.hmcl.ui.image.internal.AnimationImageImpl;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
@@ -50,13 +51,16 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* @author Glavo
*/
/// Provides image-format detection and loaders for image formats not handled
/// directly by JavaFX.
///
/// @author Glavo
@NotNullByDefault
public final class ImageUtils {
// ImageLoaders
@@ -81,38 +85,37 @@ public final class ImageUtils {
LoaderParameters parameters = new LoaderParameters();
parameters.autoStartAnimations = false;
SVGImage image;
Supplier<Image> supplier = () -> {
SVGImage image = SVGLoader.load(content, parameters);
var snapshotParameters = new SnapshotParameters();
snapshotParameters.setFill(Color.TRANSPARENT);
if (requestedWidth <= 0. || requestedHeight <= 0.) {
return image.toImage(snapshotParameters);
}
double scaleX = requestedWidth / image.getScaledWidth();
double scaleY = requestedHeight / image.getScaledHeight();
if (preserveRatio || scaleX == scaleY) {
double scale = Math.min(scaleX, scaleY);
return image.scale(scale).toImage(snapshotParameters);
} else {
// FIXME: Use DEFAULT_SVG_SNAPSHOT_PARAMS
return image.toImageScaled(ScaleQuality.RENDER_QUALITY, scaleX, scaleY);
}
};
@Nullable Image image;
if (Platform.isFxApplicationThread()) {
image = SVGLoader.load(content, parameters);
image = supplier.get();
} else {
// TODO: Currently, SVGLoader.load(...) requires the javafx.swing module if it operates on a non-JavaFX thread.
image = CompletableFuture.supplyAsync(
() -> SVGLoader.load(content, parameters),
Schedulers.javafx()
).get();
image = CompletableFuture.supplyAsync(supplier, Schedulers.javafx()).get();
}
if (image == null)
throw new IOException("Failed to load SVG image");
var snapshotParameters = new SnapshotParameters();
snapshotParameters.setFill(Color.TRANSPARENT);
if (requestedWidth <= 0. || requestedHeight <= 0.) {
return image.toImage(snapshotParameters);
}
double scaleX = requestedWidth / image.getScaledWidth();
double scaleY = requestedHeight / image.getScaledHeight();
if (preserveRatio || scaleX == scaleY) {
double scale = Math.min(scaleX, scaleY);
return image.scale(scale).toImage(snapshotParameters);
} else {
// FIXME: Use DEFAULT_SVG_SNAPSHOT_PARAMS
return image.toImageScaled(ScaleQuality.RENDER_QUALITY, scaleX, scaleY);
}
return image;
};
public static final ImageLoader APNG = (input, requestedWidth, requestedHeight, preserveRatio, smooth) -> {
@@ -198,6 +201,7 @@ public final class ImageUtils {
// ------
/// Maximum number of leading bytes inspected when selecting an image loader.
public static final int HEADER_BUFFER_SIZE = 1024;
private static final byte[] RIFF_HEADER = {'R', 'I', 'F', 'F'};
@@ -209,12 +213,272 @@ public final class ImageUtils {
&& Arrays.equals(headerBuffer, 8, 12, WEBP_HEADER, 0, 4);
}
private static final byte[] SVG_HEADER = "<svg".getBytes(StandardCharsets.US_ASCII);
/// ASCII local name of an SVG document element.
private static final String SVG_ELEMENT_NAME = "svg";
// This is currently a simple check, more complex checks can be considered in the future
/// ASCII prefix of an XML comment.
private static final String XML_COMMENT_START = "<!--";
/// ASCII suffix of an XML comment.
private static final String XML_COMMENT_END = "-->";
/// ASCII prefix of an XML processing instruction.
private static final String XML_PROCESSING_INSTRUCTION_START = "<?";
/// ASCII suffix of an XML processing instruction.
private static final String XML_PROCESSING_INSTRUCTION_END = "?>";
/// Case-sensitive ASCII prefix of an XML document type declaration.
private static final String XML_DOCTYPE_START = "<!DOCTYPE";
/// Returns whether the supplied prefix identifies an SVG XML document.
///
/// An optional UTF-8 byte-order mark, XML whitespace, comments, processing
/// instructions, and a document type declaration may precede the document
/// element. The method identifies the document element only; it does not
/// validate the remainder of the XML document. XML markup must use its
/// ASCII byte representation; UTF-16 and UTF-32 prefixes are not recognized.
///
/// @param headerBuffer leading bytes of the candidate image
/// @return whether the first XML document element is `svg`
public static boolean isSVG(byte[] headerBuffer) {
return headerBuffer.length > SVG_HEADER.length
&& Arrays.equals(headerBuffer, 0, SVG_HEADER.length, SVG_HEADER, 0, SVG_HEADER.length);
int offset = hasUtf8ByteOrderMark(headerBuffer) ? 3 : 0;
while (true) {
offset = skipXmlWhitespace(headerBuffer, offset);
if (isSvgElementStart(headerBuffer, offset))
return true;
if (matchesAscii(headerBuffer, offset, XML_COMMENT_START)) {
offset = indexAfterAscii(
headerBuffer,
offset + XML_COMMENT_START.length(),
XML_COMMENT_END
);
} else if (matchesAscii(headerBuffer, offset, XML_PROCESSING_INSTRUCTION_START)) {
offset = indexAfterAscii(
headerBuffer,
offset + XML_PROCESSING_INSTRUCTION_START.length(),
XML_PROCESSING_INSTRUCTION_END
);
} else if (isDoctypeStart(headerBuffer, offset)) {
offset = skipDoctype(headerBuffer, offset);
} else {
return false;
}
if (offset < 0)
return false;
}
}
/// Returns whether the byte array starts with a UTF-8 byte-order mark.
///
/// @param data bytes to inspect
/// @return whether `data` begins with `EF BB BF`
private static boolean hasUtf8ByteOrderMark(byte[] data) {
return svgScanLimit(data) >= 3
&& data[0] == (byte) 0xef
&& data[1] == (byte) 0xbb
&& data[2] == (byte) 0xbf;
}
/// Returns the bounded length available to SVG detection.
///
/// @param data source bytes
/// @return the lesser of the array length and [#HEADER_BUFFER_SIZE]
private static int svgScanLimit(byte[] data) {
return Math.min(data.length, HEADER_BUFFER_SIZE);
}
/// Skips XML whitespace from an array offset.
///
/// @param data source bytes
/// @param start inclusive offset at which to begin
/// @return the first offset that does not contain XML whitespace
private static int skipXmlWhitespace(byte[] data, int start) {
int limit = svgScanLimit(data);
while (start < limit && isXmlWhitespace(data[start]))
start++;
return start;
}
/// Returns whether an SVG document-element qualified name begins at an array offset.
///
/// @param data source bytes
/// @param start offset of a possible element opening delimiter
/// @return whether the unprefixed or prefixed element has the local name `svg`
private static boolean isSvgElementStart(byte[] data, int start) {
int limit = svgScanLimit(data);
if (start < 0 || start >= limit || data[start] != '<')
return false;
int nameStart = start + 1;
int localNameStart = nameStart;
boolean hasPrefix = false;
for (int index = nameStart; index < limit; index++) {
byte value = data[index];
if (value == '>' || isXmlWhitespace(value)
|| value == '/' && index + 1 < limit && data[index + 1] == '>') {
return index - localNameStart == SVG_ELEMENT_NAME.length()
&& matchesAscii(data, localNameStart, SVG_ELEMENT_NAME);
}
if (value == ':') {
if (hasPrefix || index == nameStart)
return false;
hasPrefix = true;
localNameStart = index + 1;
continue;
}
boolean nameStartCharacter = index == nameStart || index == localNameStart;
if (nameStartCharacter
? !isAsciiXmlNameStart(value)
: !isAsciiXmlNamePart(value)) {
return false;
}
}
return false;
}
/// Returns whether a byte can begin an ASCII XML name component.
///
/// @param value byte to inspect
/// @return whether the byte is an ASCII letter or underscore
private static boolean isAsciiXmlNameStart(byte value) {
return value >= 'A' && value <= 'Z'
|| value >= 'a' && value <= 'z'
|| value == '_';
}
/// Returns whether a byte can continue an ASCII XML name component.
///
/// @param value byte to inspect
/// @return whether the byte is accepted after the first name character
private static boolean isAsciiXmlNamePart(byte value) {
return isAsciiXmlNameStart(value)
|| value >= '0' && value <= '9'
|| value == '-'
|| value == '.';
}
/// Returns whether an XML document type declaration begins at an offset.
///
/// @param data source bytes
/// @param start offset of a possible declaration
/// @return whether the case-sensitive keyword is followed by XML whitespace
private static boolean isDoctypeStart(byte[] data, int start) {
int keywordEnd = start + XML_DOCTYPE_START.length();
return matchesAscii(data, start, XML_DOCTYPE_START)
&& keywordEnd < svgScanLimit(data)
&& isXmlWhitespace(data[keywordEnd]);
}
/// Skips an XML document type declaration, including its internal subset.
///
/// Quoted values, comments, and processing instructions are ignored while
/// locating the declaration-closing `>`.
///
/// @param data source bytes
/// @param start offset of the `<!DOCTYPE` prefix
/// @return the offset after the declaration, or `-1` if it is incomplete
private static int skipDoctype(byte[] data, int start) {
int internalSubsetDepth = 0;
byte quote = 0;
int limit = svgScanLimit(data);
for (int index = start + XML_DOCTYPE_START.length(); index < limit; index++) {
byte value = data[index];
if (quote != 0) {
if (value == quote)
quote = 0;
continue;
}
if (value == '\'' || value == '"') {
quote = value;
continue;
}
if (matchesAscii(data, index, XML_COMMENT_START)) {
int commentEnd = indexAfterAscii(
data,
index + XML_COMMENT_START.length(),
XML_COMMENT_END
);
if (commentEnd < 0)
return -1;
index = commentEnd - 1;
continue;
}
if (matchesAscii(data, index, XML_PROCESSING_INSTRUCTION_START)) {
int instructionEnd = indexAfterAscii(
data,
index + XML_PROCESSING_INSTRUCTION_START.length(),
XML_PROCESSING_INSTRUCTION_END
);
if (instructionEnd < 0)
return -1;
index = instructionEnd - 1;
continue;
}
if (value == '[') {
internalSubsetDepth++;
} else if (value == ']' && internalSubsetDepth > 0) {
internalSubsetDepth--;
} else if (value == '>' && internalSubsetDepth == 0) {
return index + 1;
}
}
return -1;
}
/// Finds an ASCII sequence in a byte array.
///
/// @param data source bytes
/// @param start inclusive offset at which to begin searching
/// @param asciiValue ASCII sequence to find
/// @return the offset after the first match, or `-1` if no match exists
private static int indexAfterAscii(byte[] data, int start, String asciiValue) {
int lastStart = svgScanLimit(data) - asciiValue.length();
for (int index = start; index <= lastStart; index++) {
if (matchesAscii(data, index, asciiValue))
return index + asciiValue.length();
}
return -1;
}
/// Compares an ASCII string with bytes at an array offset.
///
/// @param data source bytes
/// @param start offset at which to compare
/// @param asciiValue ASCII sequence to compare
/// @return whether the complete sequence matches
private static boolean matchesAscii(byte[] data, int start, String asciiValue) {
if (start < 0 || start > svgScanLimit(data) - asciiValue.length())
return false;
for (int index = 0; index < asciiValue.length(); index++) {
if (Byte.toUnsignedInt(data[start + index]) != asciiValue.charAt(index))
return false;
}
return true;
}
/// Returns whether a byte is XML whitespace.
///
/// @param value byte to inspect
/// @return whether the byte is space, tab, carriage return, or line feed
private static boolean isXmlWhitespace(byte value) {
return value == ' ' || value == '\t' || value == '\r' || value == '\n';
}
private static final byte[] PNG_HEADER = {
@@ -249,7 +513,7 @@ public final class ImageUtils {
ByteBuffer buffer = ByteBuffer.wrap(headerBuffer, 8, headerBuffer.length - 8);
PngChunkHeader header;
@Nullable PngChunkHeader header;
while ((header = PngChunkHeader.readHeader(buffer)) != null) {
// https://wiki.mozilla.org/APNG_Specification#Structure
// To be recognized as an APNG, an `acTL` chunk must appear in the stream before any `IDAT` chunks.
@@ -436,7 +700,7 @@ public final class ImageUtils {
}
}
PngAnimationControl animationControl = sequence.getAnimationControl();
@Nullable PngAnimationControl animationControl = sequence.getAnimationControl();
int cycleCount;
if (animationControl != null) {
cycleCount = animationControl.numPlays();
@@ -465,6 +729,7 @@ public final class ImageUtils {
return argb;
}
/// Prevents instantiation.
private ImageUtils() {
}
}
@@ -172,7 +172,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
public void add() {
FileChooser chooser = new FileChooser();
chooser.setTitle(i18n("mods.add.title"));
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.litemod"));
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));
List<Path> res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage()));
if (res == null) return;
File diff suppressed because it is too large. Load diff
+140 -36
View File
@@ -152,7 +152,7 @@ scientists-dream;145;;Scientific Dreams;;
abandend;146;;AbandEnd;;
tech-expansion;147;;Tech Expansion;;
minefanstasyworld;148;;我的幻想世界;MineFantasyWorld;
slightly-vanilla-flavoured;149;;Slightly Vanilla Flavoured;;
slightly-vanilla-flavoured;149;;Slightly Flavoured / Slightly Vanilla Flavoured;;
all-the-mods;150;;All the Mods;;ATM1
crackpack-3;151;;Crackpack 3;;
valhelsia-3;152;;Valhelsia 3;;
@@ -213,7 +213,7 @@ better-minecraft-plus;207;;Better Minecraft [PLUS];;
parasites-by-forge-labs;208;;Parasites by Forge Labs;;
create-live;209;;Create Live;;
airaka-tech;211;;艾瑞卡科技;Airaka Tech;
;212;;TeaCon;;
;212;;TeaCon整合包;TeaCon;
originssmp;213;;起源SMP;OriginsSMP;
create-live-2;214;;Create Live 2;;
origin-smp-copy;215;;Origin - SMP;;
@@ -252,7 +252,7 @@ better-minecraft-otv-edition;249;;Better Minecraft (OfflineTV Edition);;
evolution-reset;250;;进化:重启;Evolution: Reset;ER
the-nuclear-wasteland;251;;核荒原;The Nuclear Wasteland;
blessed-or-cursed-expedition-to-infinite-force;252;;祝福或诅咒:无限力量之征;Blessed or Cursed: Expedition to Infinite Force;BoC
;253;;边界生存;Border Survival;BSV
;253;;边界生存:存世;Border Survival;BSV
medieval-mc-forge-mmc1;255;;Medieval Minecraft;;MMC
heavens-of-sorcery;256;;巫术天堂;Heavens of Sorcery;
herodotus;257;;希罗多德;Herodotus-Authentic Edition:Official;HDS
@@ -498,7 +498,7 @@ other-body;504;;Other Body;;
;506;;莱姆星云;Lime Nebula;LN
void-genesis;507;;Void Genesis;;
create-flavored;508;;Create Flavored;;
terrafirmagreg-vintage;509;;TerraFirmaGreg;;TFG
terrafirmagreg-vintage;509;;群峦格雷;TerraFirmaGreg;TFG
create-astral;510;;机械动力:星辰;Create: Astral;
;511;;起源创造;CreateOrigin;CO
random-item-skyblock-recondite-i;512;;Random Item Island - Recondite I;;
@@ -516,7 +516,7 @@ pick-the-stars;523;;摘星;Pick The Stars;PTS
earth-technology;524;;Earth Technology;;
;525;;核乐不为;;ZMC
ftb-stoneblock-3;526;;FTB StoneBlock 3;;
create-mod-plus;527;;Create +;;
create-mod-plus;527;;Create+;;
multiplayer-optimization;528;;RawDiamondMC的优化包;RawDiamondMC's optimization pack;MPO
chroma-endless;529;;Chroma Endless;;
ragnamod-vi-in-the-sky;530;;Ragnamod VI In The Sky;;
@@ -548,7 +548,7 @@ gregic-skies;555;;Gregic Skies;;
;557;;齿轮颂歌:暮光;Gear Carol;
pluma;558;;Pluma, a Journey to the Future!;;Pluma
all-the-mods-7-to-the-sky;559;;All the Mods 7 - To the Sky;;ATM7S
;560;;月见苍穹传说;TskimiCanopyCraft;TSCC
;560;;月见苍穹传说;TsukiCupolaCraft;TSCC
;561;;最后的战役:劫后余生;The Last Stand:Aftermath;LSA/TLS
;562;;小猪配平;Peppa Pig's Burning Broom;2P2B
zerblands-remastered;563;;Zerblands-Remastered;;
@@ -672,7 +672,7 @@ a-new-technological-era;681;;A New Technological Era;;ANTE
musketeer;683;;Musketeer - One for All;;
;684;;匠造之传;The Legend of Tinker;TLT
;685;;我的骑士:怪物大乱斗;;
;686;;塔可努的原生优化整合包;TechNoob's Vanilla Boost Pack;TNVBP
;686;;塔可努的原生优化整合包;TNF-Vanilla / TechNoob's Vanilla Boost Pack;TNVBP
;687;;灵茗屿空岛;LMY SKY;
;688;;红石生电优化;Redstone Survival Optimization;RSO
;689;;恐惧深渊;Abyss of Fear;AoF
@@ -734,7 +734,7 @@ life-in-the-village-3;740;;Life in the village 3;;LitV3
cook-in-skyblock;746;;天空厨房;Cook in skyland;CiS
;747;;被恐惧支配 1;Dominated by Fear 1;DoF
new-vanilla-newhome;748;;新的原版;New Vanilla;NVA
;749;;峦屿梦星群;Terrafirma Rescue Unofficial;TFRU
tfru;749;;峦屿梦星群;Terrafirma Rescue Unofficial;TFRU
stoneopolis;750;;Stoneopolis;;
ftb-skies-expert;751;;FTB Skies Expert;;
wmshd-pack-what-mojang-should-have-done;752;;What Mojang Should Have Done;;WMSHD
@@ -806,7 +806,6 @@ la;818;;胶兽大冒险;Latex Advanture;LA
mystics-monstrosity;819;;Mystic's Monstrosity;;
;820;;更完美的MC;More Perfect Minecraft;MPMC
;821;;动画世界:烂柯人;AnimateCraft: Lankeren;
;822;;New Create;;NC
land-of-memories;823;;回忆之地;;LOM
;824;;异世界冒险2;;
ragnamod-vii;825;;Ragnamod VII;;
@@ -866,7 +865,7 @@ create-vanilla-exploration;879;;Vanilla Create Exploration;;
progress;880;;Progress: Create;;
aeon-automata;881;;Aeon Automata;;AA
;882;;被恐惧支配2:收容失效;Dominated by Fear 2 Containment Breach;DoF2
tensioncraft;883;;剑拔弩张之时;;TC
;883;;剑拔弩张之时;;TC
project-architect-2;884;;Project Architect 2;;
;885;;接入铁路网络:我的世界铁路;MTR Railway Network Access;RNA-MTR
create-mekanized;886;;Create: Mekanized;;
@@ -923,24 +922,23 @@ setgamedifficulty-reika;936;;SetGameDifficulty Reika;;
epic-journey-gtceu;937;;Epic Journey GTCEu;;
terrafirmacraft-leap-the-top;938;;飞渡群峦;TerraFirmaCraft:Leap The Top;TFC:LTT
gregtech-skybound;939;;Gregtech: Skybound;;
simple-tfc;940;;简单群峦;Simple TFC;
simple-tfc;940;;简单群峦;Simple TFC;SPTFC
ftb-presents-direwolf20-s14;941;;FTB Presents Direwolf20 1.21;;
final-judgment-save-the-world;942;;Final Judgment:Save the world;;
;943;;极端世界:重置;Extreme World Reset;EWR
explore-everything;944;;空岛:探索;Explore Everything;SEE
epic-journey-gtceu-conquerors-edition;945;;Epic Journey GTCEu - Conqueror's Edition;;
the-undead-blooms;946;;亡灵绽放 | 真菌孢子启示录 |;The Undead Blooms | Fungal Spore Apocalypse |;
;947;;星地征程;Starland Journey;SJ
;948;;希望与绝望-惊变100天;Hope and Despair;had
;947;;星地征程;Starland Journey re;SJRE
;948;;希望与绝望-惊变100天;Hope and Despair;HAD
;949;;梦篡光阴;Dreams Distort Overture;DDO
;950;;最后的光芒;The Last Light;TLL
endil-expert-edition;951;;Endil's Expert Edition;;EEE
peace-of-mind;952;;Peace of Mind;;
distantly-optimized;953;;Distantly Optimized;;DO
farming-experience;954;;Farming Experience;;
hungry-for-tech-2;955;;Hungry For Tech 2;;
hungry-for-tech-2;955;;渴望科技2;Hungry For Tech 2;
remain;957;;Remain;;
;958;;末日狂徒;;
gregtania6-skyblock;959;;Gregtania6 Skyblock;;
harmonious-engineering;960;;Harmonious Engineering;;
valley-of-farming-and-ranching;961;;农牧物语;Valley of Farming and Ranching;
@@ -985,7 +983,7 @@ industrial-machines;1000;;Industrial Machines;;
auto-terrafirmacraft-tfc-create;1001;;Auto-TerraFirmaCraft (TFC + Create);;ATFC
;1002;;时间之书:星的日记;The Book of Time: A Star's Journal;TASL
;1003;;特摄世界;Tokusatsu World;TW
journey-of-reincarnation;1004;;转世之旅;Tensura: Journey of Reincarnation;
;1004;;转世之旅;Tensura: Journey of Reincarnation;
;1005;;冒险与休闲;Adventure and Leisure;AL
enigmatica10;1006;;Enigmatica 10;;E10
mc-chocolate-edition;1007;;Chocolate Edition Minecraft;;
@@ -1002,7 +1000,7 @@ kumo-o-tagayasu;1017;;耕云钓月;Kumo O Tagayasu;KOTS
;1018;;无限战争4;Infinity War4;INF4
;1019;;群峦传说:工业长路;;
;1020;;自然冒险 新生;Nature Adventure Newborn;NAB
hmbo;1021;;羽舲的基础优化;Hanekmio's Basis Optimization;
asig;1021;;浅忆基础整合;Asai Integrated Modpack;
create-star-valley;1022;;Create: Gears of History;;
;1023;;阿尔法计划;AlphaPlan;AP
spell-dimension;1024;;咒次元;Spell Dimension;SD
@@ -1015,7 +1013,7 @@ terminal;1030;;终末;Terminal;
isolated-crystal-3;1031;;异次元水晶 3;Isolated Crystal 3;
;1032;;机械动力:无限总是小于零;Create:Infinity Always Smaller Than Zero;CIASZ
;1033;;踪灭I-起源;;
;1034;;踪灭II;;
;1034;;踪灭II;Endtime II;
rabbits-dawn-craft;1035;;某兔子的破晓之界;A Certain Rabbit's DawnCraft - Dawn of the Deep Soul;RDC
;1036;;铁砧天空;;ASKY
grand-architect-journey;1037;;伟大工程巡礼;Grand Architecture Journey;GAJ
@@ -1075,7 +1073,7 @@ roguelike-adventures-and-dungeons-3;1090;;冒险与地牢3;Roguelike Adventures
wheelchair-gregtech;1093;;Wheelchair Gregtech;;
egged-up;1094;;Egged Up;;
;1095;;香草纪元:食旅纪行;VanillaEra: FaresChron;VEFC
;1096;;愚者;The Fool;TF
the-fool;1096;;愚者;The Fool;TF
;1097;;时间之书2:星梦指南;BookofTime II Stardream Compass;BKSC
;1098;;群峦之上:无垠伊土;EndlessFirma;EnF
;1099;;血月低语:暗夜惊魂;Bloodmoon's Whisper;BW
@@ -1119,11 +1117,11 @@ life-in-the-village-4;1136;;Life in the village 4;;LITV4
real-island-2-modular-craft;1137;;真实空岛2:模块化之旅;Real Island 2 - Modular Craft;RI2
;1138;;奥术咖啡馆;Arcane Cofferia;
unyielding;1139;;Unyielding;;
;1140;;剑与王国;;SNK
;1140;;剑与王国;;sword_and_kingdom
the-fungal-infection-coop;1141;;The Fungal Infection | Co-Op Base Defense;;
mc-eternal-2;1142;;永恒的MC 2;MC Eternal 2;MCE2
solo-leveling-reawakening;1143;;Solo Leveling - Reawakening;;
create-ultimate-selection;1144;;Create: Ultimate Selection - QUESTS NOW!;;
create-ultimate-selection;1144;;Create: Ultimate Selection;;
;1145;;元素觉醒;Elemental Awakening;
endless-horror-the-ultimate-horror-modpack;1146;;Endless Horror - The ULTIMATE Horror Experience;;
;1147;;锻造之旅;;
@@ -1145,13 +1143,13 @@ blue-archive-x-mineraft;1163;;我的世界x蔚蓝档案;BlueArchive x Minecraft;
;1164;;Fresh & Smooth;;
;1165;;机动重启工程:风之旅;The Caravan of Winds;TCW
;1166;;Furry Fandom Club Boss Rush Official Offline;;FFCBR
immersive-ultra;1167;;极致沉浸;Immersive Ultra;
;1167;;极致沉浸;Immersive Ultra;
;1168;;Polymania!;;
survivearchive;1169;;SurviveArchive - A Blue Archive modpack;;
beloong;1170;;化龍;BeLoong;
;1171;;畅游自然;;SiF
;1171;;畅游自然;;SiN
;1172;;退休工艺;Retirement Craft;RmC
;1173;;溯光暗潮:云影;NS's Random Gunfight;NRG
;1173;;溯光暗潮;TRACING LIGHT · DARK TIDE;NRG
;1174;;雪狐的基础整合;SF Integrated Modpack;SFI
;1175;;硬核惊变;Hardcore Surprise;HS
society-sunlit-valley;1176;;Society: Sunlit Valley;;
@@ -1179,7 +1177,7 @@ repalmon;1198;;Re帕鲁梦;RePalmon;RPLM
structural-ingenuity;1199;;结构型奇思妙想;Structural Ingenuity;
arcaneaxis-ars;1200;;秘法象限;ArcaneAxis;ARS
gregtech-easy;1201;;格雷科技简单版;GregTech Easy;GTE
;1202;;机境交响;Creation Exploring the Road of;CETR
create-exploring-the-path-of;1202;;机境交响;Creation Exploring the Road of;CETR
multiblockful-skies;1203;;Multiblockful Skies;;
;1204;;刀剑物语;SwordStory;SS
;1205;;无垠征途;The Unbounded Expedition;TUE
@@ -1194,13 +1192,13 @@ farmy-skyblock;1213;;Farmy Skyblock;;
bloody-skyblock-modpack;1214;;Bloody Skyblock;;
starages;1215;;Star Space Ages;;
create-spectral-cuisine;1216;;Create: Spectral Cuisine;;
project-infinity-0-1;1217;;Project Infinity 0.1;;
project-infinity-0-1;1217;;无限计划;Project Infinity 0.1;
create-live-6;1218;;Create Live 6;;
;1219;;全面包围-硬核惊变;Full encirclement;FLET
;1220;;科波菲尔;Copperfield;CPF
;1221;;试用核心/I.O.P的水槽基础整合;Trial Essential;
tnp-limitless-8;1222;;TNP Limitless 8;;
;1223;;重度机械症;Mechanomania;
mechanomania;1223;;重度机械症;Mechanomania;
;1224;;寻此苦旅3;Per Aspera3;PA3
calamity-rpg-iii-wounds-of-the-world;1225;;Calamity RPG III : Wounds of the World;;
;1226;;烛枫公会优化整合包;;ZFGH
@@ -1221,17 +1219,17 @@ erstwhile-journey-2;1240;;往昔之旅2;Erstwhile Journey 2;EJ2
;1241;;诡厄:使徒;;GRAE
;1242;;时间之书 : 星之旅行-重制版;BookofTime StarTravel;BKSL
cook-in-skyland-elegant-feast;1243;;天空厨房:典雅盛宴;Cook in Skyland Elegant Feast;CiS2
ftb-stoneblock-4;1244;;FTB StoneBlock 4;;SB4
ftb-stoneblock-4;1244;;石头世界4;FTB StoneBlock 4;SB4
techevdiscovery;1245;;TechEv || Discovery;;
g3-project;1246;;G3 project;;
;1247;;新州;Xin Zhou;XZ
;1247;;新州;Xinzhou;XZ
emc-and-fe-plus3;1248;;EMC & FE +++;;E&F2
terracraft-confluence;1249;;泰拉工艺:汇流;TerraCraft:Confluence;TCC
;1250;;探索领域;Explore Realm;ER
beyond-cosmo;1251;;Beyond Cosmo;;
;1252;;闲居物语;Travel Architecture Dreams;TAD
;1252;;闲居物语;Travel Serere Homestead;TSH
high-altitude-alchemy;1253;;High-Altitude Alchemy;;
;1254;;机械动力: 界位工程师;Create Boundary Engineer;CBE
create-boundary-engineer;1254;;机械动力: 界位工程师;Create Boundary Engineer;CBE
skyopolis-evolved;1255;;Skyopolis: Evolved;;
;1256;;IFR的1.12.2优化整合包;IFR's Forgified Optimization;IFO
;1257;;无穷匠艺;;
@@ -1262,7 +1260,7 @@ mythcrafts;1281;;MYTHCRAFT 5;;MC
;1282;;牧场物语;Harvest Moon;
;1283;;逃逸:寄生末日;SRP: Escape Parasite Doom;SRP:epd
;1284;;Ice and M;;IAM
;1285;;秋水;autumn floods;AF
autumn-floods;1285;;秋水;autumn floods;AF
;1286;;V2O5;;
;1287;;地牢冒险者;Dungeon Adventurer;DA
ultramodern-optimization;1289;;超现代优化;Ultramodern optimization;UO
@@ -1273,14 +1271,14 @@ pandemonium-nexus-anarchic-aeon;1293;;万象枢纽:无序纪元;Pandemonium Ne
;1294;;菌染亵渎;Fungal Infection Blasphemy;FIB
;1295;;Ice and M:Sky;;IAMS
;1296;;TUFF PERFORMANCE;;
;1297;;灾厄巫咒;;
;1297;;灾厄巫咒;Curlamoety;
;1298;;子弹呼啸 : 重制;Bullet Whizz : Remake;BWR
mybiomesobeautiful;1299;;My Biomes Is So Beautiful;;
create-capitalist;1300;;Create Capitalist;;
diving-beyond;1301;;Diving Beyond;;
;1302;;奇星·奥术;Mystic Star_Arcane;
guild-adventures;1303;;Guild Adventures;;
;1304;;艾斯爱慕:新视野;Spore and M:New Horizons;SMNH
;1304;;艾斯爱慕:无限构件:现代工业;Spore and M:Infinity Mechanism:Modern Industrialization;SMIM
otherworld-dnd;1305;;Otherworld [Dungeons & Dragons];;
;1306;;奇幻起源;Fantasy Origins;
projects;1307;;ProjectS;;
@@ -1318,7 +1316,7 @@ farming-crossing-6-dinner-party;1334;;Farming Crossing 6: Dinner Party!;;
;1339;;机械动力:枪火不断;Create: Gunfire Never Stops;CGNS
;1340;;逆转未来;Battle For The Future;BFF
day-of-mind;1341;;DayOfMind;;DOM
;1342;;机械动力:探险生存 2;Create Adventure Survival 2;
create-adventure-survival-ii;1342;;机械动力:探险生存 2;Create Adventure Survival II;
;1343;;云游四海;WAYFARING;WF
;1344;;阿卡迪亚的天启;The Apocalypse of Arcadia;TAOA
;1345;;纯净原版・焕新;Pure Vanilla Renewal;PVR
@@ -1327,4 +1325,110 @@ day-of-mind;1341;;DayOfMind;;DOM
dragns-o-suite-pack;1348;;DragN's O-Suite Pack;;
;1349;;机械动力:工业与牧歌;Create: Industry and Pastoral;CIAP
;1350;;灾厄纪行;;
beebeeblock-the-skyhive;1351;;蜂蜂空坊;Beebeeblock: The Skyhive;BTS
beebeeblock-the-skyhive;1351;;蜂蜂空坊;Beebeeblock: The Skyhive;BBB
the-last-world-shines;1352;;末世生辉;The Last World Shines;TLWS
;1353;;MineOptimiz;;
journey-to-infinity;1354;;Journey To Infinity;;
;1355;;混元登仙;;HATI
flying-path;1356;;飞舞之路;Flying Path;
create-craft-quiet;1357;;机械动力:匠途闲境;Create:Craft&Quiet;ccq
sky-new-horizons;1358;;Sky New Horizons;;SkyNH
;1359;;天启之路;The Path Of Revelation;TPOR
mana-and-mechanisms;1360;;Mana & Mechanisms;;
;1361;;胶兽异界之旅Ⅱ;;
mechanical-dragon;1362;;龙之机械;Dragon create;DC
;1363;;香草精华;;VE
submerged-2;1364;;Seaopolis Submerged 2;;
;1365;;天煞孤星;A STAR DOOMED TO LONELINESS;ALONG
;1366;;全亡灵启示录;All Undead Apocalypse;AUA
;1367;;空流整合包;SkyFlow;
;1368;;飞升者的陨落;Fall of Ascension;FOA
;1369;;万华工艺录;KaleidoCraftia;KlC
;1370;;多方块笙息空岛;;DSK
etbw2-demo;1371;;探索光明的世界2;Explore the Bright World 2;EtBW2
stellarisworkshop;1372;;孤星工坊;StellarisWorkshop;GTSW
enders-journey-beyond-the-end;1373;;Ender's Journey - Beyond The End;;
mysticalblockreloaded;1374;;Mystical Block Reloaded;;
cozy-mizuno-minecraft;1375;;A Cozy Mizuno World;;
;1376;;新生异土;Tensura Neo Otherworld;
extraordinary-energy-modern;1377;;Extraordinary Energy : Modern;;
;1378;;铁砧工艺-七天创世;;
avaritia-project;1379;;Avaritia Project;;
after-the-nukes;1380;;After The Nukes;;
abyssal-ascent;1381;;Abyssal Ascent;;
desolate-planet;1382;;Desolate Planet;;
;1383;;德雷马尔:神化;Drehmal: Apotheosis;DHA
project-arc-light-the-hanging-pavilion;1384;;弧光计划:空中楼阁;Project Arc Light: The Hanging Pavilion;PAHP
;1385;;文明:创世;Civilization Isolaria;
blockitecture;1386;;方块建筑学;Blockitecture (Block Architecture);
;1388;;曙光空岛;;
;1389;;千界一梦;The Oneiropolis;TOP
;1390;;乌托星岛·创世旅途 3.0;EpicUraNepAdventure 3.0;EUNA3
itaj;1391;;It Takes a Journey;;ITAJ
taltale-pack;1392;;Taltale Pack;;
waccyys-fantasia;1393;;waccyy's Fantasia;;
all-the-mods-11;1394;;All The Mods 11;;ATM11
create-live-7;1395;;Create Live 7;;
;1396;;锻造之旅—无尽征途;;wjzt
;1397;;魔法现代化工业笙息空岛;;MXSK
nuclear-tech-integrated;1398;;核科技:集成;Nuclear Tech: Integrated;
crashed;1399;;Crashed;;
;1400;;格雷科技:万子科技;Greg Tech : Wanzi Technology;GTWT
moddedtogether;1401;;Modded Together;;
;1402;;附魔工程;;
;1403;;机械动力:航空学新视野2.0;Create Aero: New Horizons2.0;CANH
;1404;;Better with Datapack;;
restlesshorizons;1405;;Restless Horizons;;RH
;1406;;虚空行者;Void Wayfarer;VW
;1407;;模块化整合包:农业;Modular Modpack: Agriculture;MMA
the-song-of-crafting;1408;;合成颂歌;The Song of Crafting;
awakening;1409;;AMP - Awakening Classic;;
;1410;;末世再现;;CATT
;1411;;死亡突围:朝花暮拾;DeadoutRe:Duskora;DRD
craftoria-2;1412;;Craftoria 2;;
;1413;;这是一个水槽包;This is the sink pack;
;1414;;绝境中的生存;Survival in Desperation;
;1415;;等价无尽机械;PeojectE Avaritia Create;
;1416;;破灭与重生:破晓之途;Ruins and Rebirth:Path to Daybreak;RRPD
;1417;;最后那些天-低性能优化版;Those Last Day Boost;
;1418;;涅槃;Nirvana;UPN
enigmatic-skies;1419;;Enigmatic Skies;;
;1420;;Spark+ 原版红石增强优化;Spark+ Vanilla Redstone Optimization;SVRO
;1421;;混沌之地;TheChaoticnia;
;1422;;消逝的曙光;Dusk of Dawn;DOW
cook-in-skyland-recaked;1423;;天空厨房:重制版;Cook in Skyland Recaked;CiSR
rlcraft-fanmade;1424;;RLCraft Fanmade;;
unaltered;1425;;Unaltered+;;
sendims-banforges;1426;;千界万锻;Myriad Dimensional Forge;SDBF
;1427;;超级优化;Super Optimized;SO
society-sunlit-cobblemon;1428;;Society: Sunlit Cobblemon;;
create-aeronautica;1429;;Create Aeronautica;;
;1430;;群星入侵;Astrincursion;
;1431;;涟漪之篇;As Written By Whom;AWBW
starbuncles-gift;1432;;星宝石兽的礼物;starbuncles gift;
;1433;;Ember Peril: Silent Expanse;;EPSE
sky-bees-reborn;1434;;Sky Bees Reborn;;
who-am-i-not-me;1435;;我非我;Who Am I - Not Me;
all-the-hacks;1436;;All The Hacks;;ATH
;1437;;新:模拟大都市;;NSUK
;1438;;异界工匠;;
;1439;;工业升级指导版;Industrial upgrade 101;IU101
;1440;;假面骑士全明星编年史;;KRASC
;1441;;极速优化;Ultra Fast Optimization;UFO
new-age-science-and-technology;1442;;新时代科技;New Age Science and Technology;NAST
;1443;;屎生万物;EverythingFromPoop;EFP
;1444;;天顶征途;Zenith Conquest;
;1445;;瓶中伊甸;Eden in the Flask;EITF
aircrashed-engineer-ii;1446;;坠机工程师2;Aircrashed Engineer II;ACEII
star-avaritia;1447;;寰宇星光;Star Avaritia;SA
astrids-fnaf-pack;1448;;Astrid's FNAF Pack;;
hp-create-world-of-gears;1449;;HP - Create World of Gears;;
the-cursed-wanderer-reborn;1450;;咒游诸界:重生;The Cursed Wanderer: Reborn;TCWR
;1451;;机械之旅;Mechanical Create;
;1452;;探索MC小马的世界;Explore MLP World;EPW
tech101;1453;;科技入门学;Tech101;
endfield-new-horizons;1454;;终末地工业:新视野;Endfield:New Horizons;efnh
dungeon-train;1455;;地牢列车;Dungeon train;
project-arc-light-dimensional-rift;1456;;弧光计划:维度裂隙;Project Arc Light - Dimensional Rift;PA2
;1457;;恶意世界;HostileCraft;HC
adj;1458;;A Distant Journey;;ADJ
+571
View File
@@ -1045,6 +1045,138 @@
}
}
},
"org.lwjgl:lwjgl:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl/3.4.3/lwjgl-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.3/lwjgl-3.4.3-natives-linux-arm64.jar",
"sha1": "101c753b129dbdeed565798d259b2b9544ef3878",
"size": 126343
}
}
},
"org.lwjgl:lwjgl-jemalloc:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-jemalloc:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-jemalloc/3.4.3/lwjgl-jemalloc-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.3/lwjgl-jemalloc-3.4.3-natives-linux-arm64.jar",
"sha1": "b558d38d8eaa32831bb66835aae163428fe69646",
"size": 235928
}
}
},
"org.lwjgl:lwjgl-openal:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-openal:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-openal/3.4.3/lwjgl-openal-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.3/lwjgl-openal-3.4.3-natives-linux-arm64.jar",
"sha1": "f2083556a7d8a02e4abeb2b933642250ef06c61d",
"size": 866036
}
}
},
"org.lwjgl:lwjgl-opengl:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-opengl:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-opengl/3.4.3/lwjgl-opengl-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.3/lwjgl-opengl-3.4.3-natives-linux-arm64.jar",
"sha1": "84f229d793388be57ae5555b0cfee09b5990fa20",
"size": 80850
}
}
},
"org.lwjgl:lwjgl-glfw:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-glfw:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-glfw/3.4.3/lwjgl-glfw-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.3/lwjgl-glfw-3.4.3-natives-linux-arm64.jar",
"sha1": "e2f84903e71f7f2c5fe11a0f6de9576da270c503",
"size": 144552
}
}
},
"org.lwjgl:lwjgl-stb:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-stb:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-stb/3.4.3/lwjgl-stb-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.3/lwjgl-stb-3.4.3-natives-linux-arm64.jar",
"sha1": "74f1cd22ccf4fd7432b982372b8a34c93cd9aacf",
"size": 258996
}
}
},
"org.lwjgl:lwjgl-tinyfd:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-tinyfd:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-tinyfd/3.4.3/lwjgl-tinyfd-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.3/lwjgl-tinyfd-3.4.3-natives-linux-arm64.jar",
"sha1": "97296e394d3d7dfebcc68754ab8273d44d8e9602",
"size": 45345
}
}
},
"org.lwjgl:lwjgl-freetype:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-freetype:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-freetype/3.4.3/lwjgl-freetype-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.3/lwjgl-freetype-3.4.3-natives-linux-arm64.jar",
"sha1": "6babd5ddf9b4850e91a952d494cfd22b895ce200",
"size": 1300898
}
}
},
"org.lwjgl:lwjgl-shaderc:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-shaderc:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-shaderc/3.4.3/lwjgl-shaderc-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.3/lwjgl-shaderc-3.4.3-natives-linux-arm64.jar",
"sha1": "f17eb9b12086809a31e61734dabc1dea80f6b7b2",
"size": 3540777
}
}
},
"org.lwjgl:lwjgl-spvc:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-spvc:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-spvc/3.4.3/lwjgl-spvc-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.3/lwjgl-spvc-3.4.3-natives-linux-arm64.jar",
"sha1": "85d2636eb1368a3864fb10faec02a4269020b31e",
"size": 1166740
}
}
},
"org.lwjgl:lwjgl-vma:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-vma:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-vma/3.4.3/lwjgl-vma-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.3/lwjgl-vma-3.4.3-natives-linux-arm64.jar",
"sha1": "0a9e20e7d8ae4d0106cd23ae58a1b72ca151c49f",
"size": 60863
}
}
},
"org.lwjgl:lwjgl-sdl:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-sdl:3.4.3:natives-linux-arm64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-sdl/3.4.3/lwjgl-sdl-3.4.3-natives-linux-arm64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.3/lwjgl-sdl-3.4.3-natives-linux-arm64.jar",
"sha1": "577e41b6d0006f3d3f841c14abe19736e695a760",
"size": 1460461
}
}
},
"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives": {
"name": "org.glavo.hmcl:lwjgl2-natives:2.9.3",
"downloads": {
@@ -2956,6 +3088,181 @@
}
},
"org.lwjgl:lwjgl-sdl:3.4.2:natives-linux": null,
"org.lwjgl:lwjgl:3.4.3": {
"name": "org.lwjgl:lwjgl:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.1/lwjgl-3.4.1.jar",
"sha1": "6105690714874b995aa7812e4f7732a21109276c",
"size": 1157957
}
}
},
"org.lwjgl:lwjgl:3.4.3:natives-linux": {
"name": "org.glavo.hmcl:lwjgl3-natives:3.4.1-rc1",
"downloads": {
"classifiers": {
"linux-loongarch64": {
"path": "org/glavo/hmcl/lwjgl3-natives/3.4.1-rc1/lwjgl3-natives-3.4.1-rc1-linux-loongarch64.jar",
"url": "https://repo1.maven.org/maven2/org/glavo/hmcl/lwjgl3-natives/3.4.1-rc1-linux-loongarch64/lwjgl3-natives-3.4.1-rc1-linux-loongarch64.jar",
"sha1": "3fed872fc2f9929d2be25389dd312dfb48e9f89e",
"size": 22854580
}
}
},
"extract": {
"exclude": [
"META-INF/"
]
},
"natives": {
"linux": "linux-loongarch64"
}
},
"org.lwjgl:lwjgl-jemalloc:3.4.3": {
"name": "org.lwjgl:lwjgl-jemalloc:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.1/lwjgl-jemalloc-3.4.1.jar",
"sha1": "16b663092854fc6adfe0a941e61cae2bb2e437b6",
"size": 47872
}
}
},
"org.lwjgl:lwjgl-jemalloc:3.4.3:natives-linux": null,
"org.lwjgl:lwjgl-openal:3.4.3": {
"name": "org.lwjgl:lwjgl-openal:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.1/lwjgl-openal-3.4.1.jar",
"sha1": "8508bd60589de0539aa465b78fcf838efd07ae9a",
"size": 153919
}
}
},
"org.lwjgl:lwjgl-openal:3.4.3:natives-linux": null,
"org.lwjgl:lwjgl-opengl:3.4.3": {
"name": "org.lwjgl:lwjgl-opengl:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.1/lwjgl-opengl-3.4.1.jar",
"sha1": "830412cab823c029cda6b9729f9d2ed36cf79959",
"size": 937660
}
}
},
"org.lwjgl:lwjgl-opengl:3.4.3:natives-linux": null,
"org.lwjgl:lwjgl-glfw:3.4.3": {
"name": "org.lwjgl:lwjgl-glfw:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.1/lwjgl-glfw-3.4.1.jar",
"sha1": "a782b1ddd175c9107bf300fd92920579ea65e2a4",
"size": 151893
}
}
},
"org.lwjgl:lwjgl-glfw:3.4.3:natives-linux": null,
"org.lwjgl:lwjgl-stb:3.4.3": {
"name": "org.lwjgl:lwjgl-stb:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.1/lwjgl-stb-3.4.1.jar",
"sha1": "f077c3dafc31924fbe8acb0b913f08977ba27f8e",
"size": 142927
}
}
},
"org.lwjgl:lwjgl-stb:3.4.3:natives-linux": null,
"org.lwjgl:lwjgl-tinyfd:3.4.3": {
"name": "org.lwjgl:lwjgl-tinyfd:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.1/lwjgl-tinyfd-3.4.1.jar",
"sha1": "562697d40dbd2ed0424bf24dcf65d51170f81adf",
"size": 15931
}
}
},
"org.lwjgl:lwjgl-tinyfd:3.4.3:natives-linux": null,
"org.lwjgl:lwjgl-freetype:3.4.3": {
"name": "org.lwjgl:lwjgl-freetype:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.1/lwjgl-freetype-3.4.1.jar",
"sha1": "caa75a37e857efc9b0686e434d708d6420c5b6ea",
"size": 464342
}
}
},
"org.lwjgl:lwjgl-freetype:3.4.3:natives-linux": null,
"org.lwjgl:lwjgl-shaderc:3.4.3": {
"name": "org.lwjgl:lwjgl-shaderc:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-shaderc/3.4.1/lwjgl-shaderc-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.1/lwjgl-shaderc-3.4.1.jar",
"sha1": "5c02c88f0d879529014c78463b76f43fdecfd272",
"size": 153051
}
}
},
"org.lwjgl:lwjgl-shaderc:3.4.3:natives-linux": null,
"org.lwjgl:lwjgl-spvc:3.4.3": {
"name": "org.lwjgl:lwjgl-spvc:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-spvc/3.4.1/lwjgl-spvc-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.1/lwjgl-spvc-3.4.1.jar",
"sha1": "188553592e97b5ab2dba54a1b403ee80b48dad4c",
"size": 148285
}
}
},
"org.lwjgl:lwjgl-spvc:3.4.3:natives-linux": null,
"org.lwjgl:lwjgl-vma:3.4.3": {
"name": "org.lwjgl:lwjgl-vma:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-vma/3.4.1/lwjgl-vma-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.1/lwjgl-vma-3.4.1.jar",
"sha1": "2c54ce10154feb1ede4d23c5947438cdbfdcc003",
"size": 109209
}
}
},
"org.lwjgl:lwjgl-vma:3.4.3:natives-linux": null,
"org.lwjgl:lwjgl-vulkan:3.4.3": {
"name": "org.lwjgl:lwjgl-vulkan:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-vulkan/3.4.1/lwjgl-vulkan-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vulkan/3.4.1/lwjgl-vulkan-3.4.1.jar",
"sha1": "d0601fdc38890755e5638048c14cc0f7b990964b",
"size": 8356700
}
}
},
"org.lwjgl:lwjgl-sdl:3.4.3": {
"name": "org.lwjgl:lwjgl-sdl:3.4.1",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
"sha1": "ec28db6e949478044c649fea25eae43b1b161971",
"size": 983498
}
}
},
"org.lwjgl:lwjgl-sdl:3.4.3:natives-linux": null,
"net.java.dev.jna:jna:5.8.0": {
"name": "net.java.dev.jna:jna:5.13.0",
"downloads": {
@@ -4857,6 +5164,138 @@
}
}
},
"org.lwjgl:lwjgl:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl/3.4.3/lwjgl-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.3/lwjgl-3.4.3-natives-linux-riscv64.jar",
"sha1": "38374c04f498a9b214f7cb28cfd3fcc9737b8065",
"size": 127129
}
}
},
"org.lwjgl:lwjgl-jemalloc:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-jemalloc:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-jemalloc/3.4.3/lwjgl-jemalloc-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.3/lwjgl-jemalloc-3.4.3-natives-linux-riscv64.jar",
"sha1": "79b62b3efdcdba28d2f89e6a7d99d88cf3cda48f",
"size": 248363
}
}
},
"org.lwjgl:lwjgl-openal:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-openal:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-openal/3.4.3/lwjgl-openal-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.3/lwjgl-openal-3.4.3-natives-linux-riscv64.jar",
"sha1": "f4d3d1050f244b3a7fe9ddc02e94fbe0c507fc4d",
"size": 938685
}
}
},
"org.lwjgl:lwjgl-opengl:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-opengl:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-opengl/3.4.3/lwjgl-opengl-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.3/lwjgl-opengl-3.4.3-natives-linux-riscv64.jar",
"sha1": "56ecca3a33c7966f3d678737757f5feb3efd03c2",
"size": 79430
}
}
},
"org.lwjgl:lwjgl-glfw:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-glfw:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-glfw/3.4.3/lwjgl-glfw-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.3/lwjgl-glfw-3.4.3-natives-linux-riscv64.jar",
"sha1": "7f88d98db5c2fdd33063b5810df8a9809fd0983b",
"size": 141071
}
}
},
"org.lwjgl:lwjgl-stb:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-stb:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-stb/3.4.3/lwjgl-stb-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.3/lwjgl-stb-3.4.3-natives-linux-riscv64.jar",
"sha1": "8f0aeb06e1d532fd9411d08dcff6c0c3572d71a6",
"size": 268102
}
}
},
"org.lwjgl:lwjgl-tinyfd:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-tinyfd:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-tinyfd/3.4.3/lwjgl-tinyfd-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.3/lwjgl-tinyfd-3.4.3-natives-linux-riscv64.jar",
"sha1": "f9b6c895de7f43caf50c12b8d30fa3aef13e82ff",
"size": 56940
}
}
},
"org.lwjgl:lwjgl-freetype:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-freetype:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-freetype/3.4.3/lwjgl-freetype-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.3/lwjgl-freetype-3.4.3-natives-linux-riscv64.jar",
"sha1": "809c6df123ae9ea36aa3117a368e7d300ed712eb",
"size": 1409111
}
}
},
"org.lwjgl:lwjgl-shaderc:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-shaderc:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-shaderc/3.4.3/lwjgl-shaderc-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.3/lwjgl-shaderc-3.4.3-natives-linux-riscv64.jar",
"sha1": "1cb0fc92286da27d5782140a8ba72af5458eb1cf",
"size": 3702544
}
}
},
"org.lwjgl:lwjgl-spvc:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-spvc:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-spvc/3.4.3/lwjgl-spvc-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.3/lwjgl-spvc-3.4.3-natives-linux-riscv64.jar",
"sha1": "38610faedc2a7c44f02c0bc7246ef1f8357a2846",
"size": 1221331
}
}
},
"org.lwjgl:lwjgl-vma:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-vma:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-vma/3.4.3/lwjgl-vma-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.3/lwjgl-vma-3.4.3-natives-linux-riscv64.jar",
"sha1": "e4dc73c846d42eb63c39c2b30d7cb5983a59bdbe",
"size": 66532
}
}
},
"org.lwjgl:lwjgl-sdl:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-sdl:3.4.3:natives-linux-riscv64",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-sdl/3.4.3/lwjgl-sdl-3.4.3-natives-linux-riscv64.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.3/lwjgl-sdl-3.4.3-natives-linux-riscv64.jar",
"sha1": "30cd5ef9665905be2c0c7325dc848296d4788b49",
"size": 1496337
}
}
},
"com.github.oshi:oshi-core:6.6.5": {
"name": "com.github.oshi:oshi-core:6.8.0",
"downloads": {
@@ -7054,6 +7493,138 @@
}
}
},
"org.lwjgl:lwjgl:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl/3.4.3/lwjgl-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl/3.4.3/lwjgl-3.4.3-natives-freebsd.jar",
"sha1": "fbab1de5df7db20be4c74bf6cf14750f599e6d79",
"size": 110742
}
}
},
"org.lwjgl:lwjgl-jemalloc:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-jemalloc:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-jemalloc/3.4.3/lwjgl-jemalloc-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-jemalloc/3.4.3/lwjgl-jemalloc-3.4.3-natives-freebsd.jar",
"sha1": "30a905bf255a0eac294da13cdfddb2963bda4541",
"size": 160297
}
}
},
"org.lwjgl:lwjgl-openal:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-openal:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-openal/3.4.3/lwjgl-openal-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-openal/3.4.3/lwjgl-openal-3.4.3-natives-freebsd.jar",
"sha1": "6f72cdd56726eacaaa1e4f5824f9fc88f0bc2272",
"size": 851111
}
}
},
"org.lwjgl:lwjgl-opengl:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-opengl:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-opengl/3.4.3/lwjgl-opengl-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-opengl/3.4.3/lwjgl-opengl-3.4.3-natives-freebsd.jar",
"sha1": "97992b72536b99ad922ac1318f397e049de50223",
"size": 83069
}
}
},
"org.lwjgl:lwjgl-glfw:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-glfw:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-glfw/3.4.3/lwjgl-glfw-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-glfw/3.4.3/lwjgl-glfw-3.4.3-natives-freebsd.jar",
"sha1": "783d56acc7b0bc5690f4d139d843d5cc083a7ef9",
"size": 108309
}
}
},
"org.lwjgl:lwjgl-stb:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-stb:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-stb/3.4.3/lwjgl-stb-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-stb/3.4.3/lwjgl-stb-3.4.3-natives-freebsd.jar",
"sha1": "0422952f19f24b9a10a29fcc44ac77ed8b842b6a",
"size": 225716
}
}
},
"org.lwjgl:lwjgl-tinyfd:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-tinyfd:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-tinyfd/3.4.3/lwjgl-tinyfd-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-tinyfd/3.4.3/lwjgl-tinyfd-3.4.3-natives-freebsd.jar",
"sha1": "5650a5b1cddce100f5c13761f7e4a5a6cf248320",
"size": 40582
}
}
},
"org.lwjgl:lwjgl-freetype:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-freetype:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-freetype/3.4.3/lwjgl-freetype-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-freetype/3.4.3/lwjgl-freetype-3.4.3-natives-freebsd.jar",
"sha1": "3938689ef674b5894b41af23de67aa53cf5c6a58",
"size": 1217752
}
}
},
"org.lwjgl:lwjgl-shaderc:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-shaderc:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-shaderc/3.4.3/lwjgl-shaderc-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-shaderc/3.4.3/lwjgl-shaderc-3.4.3-natives-freebsd.jar",
"sha1": "843c2c6b1588175e28eefe260d3833244119607b",
"size": 3063282
}
}
},
"org.lwjgl:lwjgl-spvc:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-spvc:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-spvc/3.4.3/lwjgl-spvc-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-spvc/3.4.3/lwjgl-spvc-3.4.3-natives-freebsd.jar",
"sha1": "f66777d0f5e784c203fda965e28e6f41f6287eed",
"size": 1122077
}
}
},
"org.lwjgl:lwjgl-vma:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-vma:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-vma/3.4.3/lwjgl-vma-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-vma/3.4.3/lwjgl-vma-3.4.3-natives-freebsd.jar",
"sha1": "b160d451c6224139bcb47d565dac76b1d242cf2b",
"size": 63336
}
}
},
"org.lwjgl:lwjgl-sdl:3.4.3:natives-linux": {
"name": "org.lwjgl:lwjgl-sdl:3.4.3:natives-freebsd",
"downloads": {
"artifact": {
"path": "org/lwjgl/lwjgl-sdl/3.4.3/lwjgl-sdl-3.4.3-natives-freebsd.jar",
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.3/lwjgl-sdl-3.4.3-natives-freebsd.jar",
"sha1": "aad4d48eb5b3b72d550589fa6d07bbc6113b442e",
"size": 1242611
}
}
},
"net.java.jinput:jinput-platform:2.0.5:natives": null,
"com.mojang:text2speech:1.10.3:natives": null,
"com.mojang:text2speech:1.11.3:natives": null,
@@ -17,17 +17,26 @@
*/
package org.jackhuang.hmcl.ui.image;
import org.jetbrains.annotations.NotNullByDefault;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
/// Verifies image-format detection from bounded input prefixes.
@NotNullByDefault
public final class ImageUtilsTest {
/// Reads the image-loader detection prefix of a test resource.
///
/// @param fileName resource file name
/// @return up to [ImageUtils#HEADER_BUFFER_SIZE] leading bytes
private static byte[] readHeaderBuffer(String fileName) {
try (var input = Files.newInputStream(Path.of("src/test/resources/image/" + fileName))) {
return input.readNBytes(ImageUtils.HEADER_BUFFER_SIZE);
@@ -37,6 +46,15 @@ public final class ImageUtilsTest {
}
}
/// Encodes an XML fixture without introducing non-ASCII bytes.
///
/// @param value fixture text
/// @return US-ASCII bytes
private static byte[] asciiBytes(String value) {
return value.getBytes(StandardCharsets.US_ASCII);
}
/// Verifies APNG detection distinguishes animated and static PNG files.
@Test
public void testIsApng() {
assertTrue(ImageUtils.isApng(readHeaderBuffer("16x16.apng")));
@@ -47,6 +65,7 @@ public final class ImageUtilsTest {
assertFalse(ImageUtils.isApng(readHeaderBuffer("16x16-animation-lossy.webp")));
}
/// Verifies WebP detection recognizes all supported WebP variants.
@Test
public void testIsWebP() {
assertFalse(ImageUtils.isWebP(readHeaderBuffer("16x16.apng")));
@@ -57,4 +76,66 @@ public final class ImageUtilsTest {
assertTrue(ImageUtils.isWebP(readHeaderBuffer("16x16-animation-lossy.webp")));
}
/// Verifies SVG detection accepts XML prologs and an SVG document element.
@Test
public void testIsSVG() {
assertTrue(ImageUtils.isSVG(asciiBytes("<svg/>")));
assertTrue(ImageUtils.isSVG(asciiBytes(" \t\r\n<svg viewBox='0 0 16 16'>")));
assertTrue(ImageUtils.isSVG(
"\ufeff<?xml version='1.0' encoding='UTF-8'?>\n<svg/>"
.getBytes(StandardCharsets.UTF_8)
));
assertTrue(ImageUtils.isSVG(asciiBytes(
"<?xml version='1.0'?>"
+ "<?xml-stylesheet href='<svg.css'?>"
+ "<!-- <svg/> -->"
+ "<svg xmlns='http://www.w3.org/2000/svg'>"
)));
assertTrue(ImageUtils.isSVG(asciiBytes(
"<!DOCTYPE svg ["
+ "<!ENTITY quoted ']> <svg>'>"
+ "<!-- ]> <svg -->"
+ "<!ELEMENT svg ANY>"
+ "]><svg/>"
)));
assertTrue(ImageUtils.isSVG(asciiBytes(
"<!--" + "x".repeat(ImageUtils.HEADER_BUFFER_SIZE - 13) + "--><svg/>"
)));
assertTrue(ImageUtils.isSVG(asciiBytes(
"<svg:svg xmlns:svg='http://www.w3.org/2000/svg'/>"
)));
assertTrue(ImageUtils.isSVG(asciiBytes(
"<image:svg xmlns:image='http://www.w3.org/2000/svg'/>"
)));
}
/// Verifies SVG detection rejects incomplete, embedded, and non-SVG roots.
@Test
public void testIsNotSVG() {
assertFalse(ImageUtils.isSVG(new byte[0]));
assertFalse(ImageUtils.isSVG(asciiBytes("<svg")));
assertFalse(ImageUtils.isSVG(asciiBytes("<svg-image/>")));
assertFalse(ImageUtils.isSVG(asciiBytes("<svg:circle/>")));
assertFalse(ImageUtils.isSVG(asciiBytes("<image:svg-icon/>")));
assertFalse(ImageUtils.isSVG(asciiBytes("<SVG/>")));
assertFalse(ImageUtils.isSVG(asciiBytes("plain text <svg/>")));
assertFalse(ImageUtils.isSVG(asciiBytes("<!-- <svg/> -->")));
assertFalse(ImageUtils.isSVG(asciiBytes(
"<?xml version='1.0'?><html><svg/></html>"
)));
assertFalse(ImageUtils.isSVG(asciiBytes(
"<!DOCTYPE svg [<!-- incomplete ]> <svg -->"
)));
assertFalse(ImageUtils.isSVG(asciiBytes(
"<!--" + "x".repeat(ImageUtils.HEADER_BUFFER_SIZE) + "--><svg/>"
)));
}
/// Verifies sniffing routes a prefixed SVG document to the SVG loader.
@Test
public void testGuessLoaderDetectsPrefixedSVG() {
assertSame(ImageUtils.SVG, ImageUtils.guessLoader(asciiBytes(
"<svg:svg xmlns:svg='http://www.w3.org/2000/svg'/>"
)));
}
}
@@ -38,7 +38,7 @@ import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class ModManager extends LocalAddonManager<LocalModFile> {
public static final List<String> MOD_EXTENSIONS = List.of("jar", "litemod");
public static final List<String> MOD_EXTENSIONS = List.of("jar", "zip", "litemod");
@FunctionalInterface
private interface ModMetadataReader {
@@ -58,6 +58,7 @@ public final class ModManager extends LocalAddonManager<LocalModFile> {
);
map.put("jar", zipReaders);
map.put("zip", zipReaders);
map.put("litemod", List.of(pair(LiteModMetadata::fromFile, ModLoaderType.LITE_LOADER)));
READERS = map;
@@ -32,17 +32,19 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.Checksum;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
@@ -184,23 +186,65 @@ public final class CurseForgeRemoteAddonRepository implements RemoteAddonReposit
}
}
@Override
public Optional<RemoteAddon.Version> getRemoteVersionByLocalFile(Path file) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (InputStream stream = Files.newInputStream(file)) {
byte[] buf = new byte[1024];
int len;
while ((len = stream.read(buf, 0, buf.length)) != -1) {
/// Calculates the CurseForge fingerprint without retaining the filtered file in memory.
static long calculateFingerprint(Path file) throws IOException {
try (SeekableByteChannel channel = Files.newByteChannel(file, StandardOpenOption.READ)) {
long startPosition = channel.position();
byte[] bufferArray = new byte[1024 * 1024];
ByteBuffer buffer = ByteBuffer.wrap(bufferArray);
long filteredLength = 0;
while (channel.read(buffer) > 0) {
int len = buffer.position();
for (int i = 0; i < len; i++) {
byte b = buf[i];
byte b = bufferArray[i];
if (b != 0x9 && b != 0xa && b != 0xd && b != 0x20) {
baos.write(b);
filteredLength++;
}
}
buffer.clear();
}
}
long hash = Integer.toUnsignedLong(MurmurHash2.hash32(baos.toByteArray(), baos.size(), 1));
channel.position(startPosition);
Checksum hasher = MurmurHash2.hash32(filteredLength, 1);
while (channel.read(buffer) > 0) {
int len = buffer.position();
int pos = 0;
while (pos < len) {
byte b = bufferArray[pos];
if (b == 0x9 || b == 0xa || b == 0xd || b == 0x20) {
break;
}
pos++;
}
if (pos < len) {
int pos2 = pos + 1;
while (pos2 < len) {
byte b = bufferArray[pos2];
if (b != 0x9 && b != 0xa && b != 0xd && b != 0x20) {
bufferArray[pos++] = b;
}
pos2++;
}
}
hasher.update(bufferArray, 0, pos);
buffer.clear();
}
return hasher.getValue();
} catch (IllegalArgumentException | IllegalStateException e) {
throw new IOException(e);
}
}
/// Finds the remote CurseForge version matching a local file.
@Override
public Optional<RemoteAddon.Version> getRemoteVersionByLocalFile(Path file) throws IOException {
long hash = calculateFingerprint(file);
if (hash == 811513880) { // Workaround for https://github.com/HMCL-dev/HMCL/issues/4597
return Optional.empty();
}
@@ -17,7 +17,11 @@
*/
package org.jackhuang.hmcl.util;
import org.jetbrains.annotations.NotNullByDefault;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.zip.Checksum;
/**
* Implementation of the MurmurHash2 32-bit and 64-bit hash functions.
@@ -48,6 +52,7 @@ import java.nio.charset.StandardCharsets;
* Original MurmurHash2 c++ code</a>
* @since 1.13
*/
@NotNullByDefault
public final class MurmurHash2 {
// Constants for 32-bit variant
@@ -64,6 +69,177 @@ public final class MurmurHash2 {
private MurmurHash2() {
}
/// Creates a streaming MurmurHash2 32-bit checksum for exactly `length` bytes.
///
/// The length is incorporated into the initial hash state using its low 32 bits. Calls to
/// [Checksum#update(int)] and [Checksum#update(byte[], int, int)] may divide the input at
/// arbitrary byte boundaries. [Checksum#getValue()] returns the hash as an unsigned 32-bit
/// value represented by a `long`, and does not change the checksum state.
///
/// The returned checksum verifies the number of supplied bytes when `getValue()` is called.
/// If too few bytes have been supplied, the caller may continue updating the checksum and call
/// `getValue()` again. Once too many bytes have been supplied, [Checksum#reset()] must be called
/// before a value can be obtained. Resetting retains the configured length and seed. The
/// returned checksum is mutable and is not safe for concurrent use.
///
/// @param length the exact number of input bytes
/// @param seed the initial seed value
/// @return a new checksum initialized with the specified length and seed
/// @throws IllegalArgumentException if `length` is negative
public static Checksum hash32(final long length, final int seed) {
if (length < 0) {
throw new IllegalArgumentException("length must not be negative: " + length);
}
return new Hash32Checksum(length, seed);
}
/// Computes a MurmurHash2 32-bit value from updates whose total length is known in advance.
private static final class Hash32Checksum implements Checksum {
/// The exact number of bytes required before the hash can be obtained.
private final long expectedLength;
/// The hash state restored by [#reset()].
private final int initialHash;
/// The hash state after all complete four-byte blocks received so far.
private int hash;
/// The number of bytes counted before [#inputLengthExceeded] becomes `true`.
private long inputLength;
/// Whether more than [#expectedLength] bytes have been received.
private boolean inputLengthExceeded;
/// Up to three unprocessed bytes packed in little-endian order.
private int tail;
/// The number of bytes currently stored in [#tail].
private int tailLength;
/// Creates a checksum with a precomputed initial hash state.
///
/// @param expectedLength the exact number of input bytes
/// @param seed the initial seed value
private Hash32Checksum(long expectedLength, int seed) {
this.expectedLength = expectedLength;
this.initialHash = seed ^ (int) expectedLength;
this.hash = initialHash;
}
/// Incorporates the low eight bits of `value` into this checksum.
///
/// @param value the value whose low eight bits are incorporated
@Override
public void update(int value) {
addInputLength(1);
appendByte(value);
}
/// Incorporates `length` bytes beginning at `offset` into this checksum.
///
/// @param data the array containing the input bytes
/// @param offset the offset of the first input byte
/// @param length the number of bytes to incorporate
@Override
public void update(byte[] data, int offset, int length) {
Objects.checkFromIndexSize(offset, length, data.length);
addInputLength(length);
int index = offset;
final int end = offset + length;
while (tailLength != 0 && index < end) {
appendByte(data[index++]);
}
while (index <= end - Integer.BYTES) {
mixBlock(ByteArray.getIntLE(data, index));
index += Integer.BYTES;
}
while (index < end) {
appendByte(data[index++]);
}
}
/// Returns the MurmurHash2 value after verifying the exact input length.
///
/// @return the unsigned 32-bit hash value represented by a `long`
/// @throws IllegalStateException if the number of supplied bytes differs from the expected
/// length
@Override
public long getValue() {
if (inputLengthExceeded) {
throw new IllegalStateException(
"Expected " + expectedLength + " bytes, but received more than expected");
}
if (inputLength != expectedLength) {
throw new IllegalStateException(
"Expected " + expectedLength + " bytes, but received " + inputLength);
}
int result = hash;
if (tailLength != 0) {
result ^= tail;
result *= M32;
}
result ^= result >>> 13;
result *= M32;
result ^= result >>> 15;
return Integer.toUnsignedLong(result);
}
/// Restores this checksum to its initial state while retaining its expected length and seed.
@Override
public void reset() {
hash = initialHash;
inputLength = 0;
inputLengthExceeded = false;
tail = 0;
tailLength = 0;
}
/// Records that `length` more input bytes have been supplied.
///
/// @param length the non-negative number of additional bytes
private void addInputLength(int length) {
if (inputLengthExceeded) {
return;
}
if (length > expectedLength - inputLength) {
inputLengthExceeded = true;
} else {
inputLength += length;
}
}
/// Buffers one byte and mixes the resulting block when four bytes are available.
///
/// @param value the value whose low eight bits are appended
private void appendByte(int value) {
tail |= (value & 0xff) << (tailLength * Byte.SIZE);
tailLength++;
if (tailLength == Integer.BYTES) {
mixBlock(tail);
tail = 0;
tailLength = 0;
}
}
/// Mixes one little-endian four-byte block into the current hash state.
///
/// @param block the block to mix
private void mixBlock(int block) {
int mixedBlock = block;
mixedBlock *= M32;
mixedBlock ^= mixedBlock >>> R32;
mixedBlock *= M32;
hash *= M32;
hash ^= mixedBlock;
}
}
/**
* Generates a 32-bit hash from byte array with the given length and seed.
*
@@ -137,14 +137,14 @@ public final class CompressingUtils {
}
public static ZipArchiveReader openZipFile(Path zipFile, Charset charset) throws IOException {
return new ZipArchiveReader(zipFile, charset);
return new ZipArchiveReader(zipFile, charset, true, true);
}
public static ZipArchiveReader openZipFileWithPossibleEncoding(Path zipFile, Charset possibleEncoding) throws IOException {
if (possibleEncoding == null)
possibleEncoding = StandardCharsets.UTF_8;
ZipArchiveReader zipReader = new ZipArchiveReader(Files.newByteChannel(zipFile));
ZipArchiveReader zipReader = new ZipArchiveReader(zipFile, StandardCharsets.UTF_8, true, true);
Charset suitableEncoding;
try {
@@ -161,7 +161,7 @@ public final class CompressingUtils {
}
zipReader.close();
return new ZipArchiveReader(Files.newByteChannel(zipFile), suitableEncoding);
return new ZipArchiveReader(zipFile, suitableEncoding, true, true);
}
public static final class Builder {
@@ -18,7 +18,7 @@
package org.jackhuang.hmcl.util.tree;
import kala.compress.archivers.ArchiveEntry;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
@@ -44,7 +44,7 @@ public abstract class ArchiveFileTree<R, E extends ArchiveEntry> implements Clos
String name = namePath.toString();
if (name.endsWith(".jar") || name.endsWith(".zip")) {
return new ZipFileTree(new ZipArchiveReader(file));
return CompressingUtils.openZipTree(file);
} else if (name.endsWith(".tar") || name.endsWith(".tar.gz") || name.endsWith(".tgz")) {
return TarFileTree.open(file);
} else {
@@ -933,4 +933,21 @@
26.2-pre-6
26.2-rc-1
26.2-rc-2
26.2
26.2
26.3-snapshot-1
26.3-snapshot-2
26.3-snapshot-3
26.3-snapshot-4
26.3-snapshot-5
26.3-snapshot-6
26.3-snapshot-7
26.3-snapshot-8
26.3-snapshot-9
26.3-snapshot-10
26.3-pre-1
26.3-pre-2
26.3-pre-3
26.3-rc-1
26.3-rc-2
26.3-rc-3
26.3
@@ -1,54 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <[email protected]> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.addon.curse;
import org.jackhuang.hmcl.util.MurmurHash2;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import static org.junit.jupiter.api.Assertions.*;
public class CurseForgeRemoteAddonRepositoryTest {
@Test
@Disabled
public void testMurmurHash() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (InputStream is = Files.newInputStream(Paths.get("C:\\Users\\huang\\Downloads\\JustEnoughCalculation-1.16.5-3.8.5.jar"))) {
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf, 0, buf.length)) > 0) {
for (int i = 0; i < len; i++) {
byte b = buf[i];
if (b != 9 && b != 10 && b != 13 && b != 32) {
baos.write(b);
}
}
}
}
long hash = Integer.toUnsignedLong(MurmurHash2.hash32(baos.toByteArray(), baos.size(), 1));
assertEquals(hash, 3333498611L);
}
}
@@ -0,0 +1,73 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2026 huangyuhui <[email protected]> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.addon.repository;
import org.jackhuang.hmcl.util.MurmurHash2;
import org.jetbrains.annotations.NotNullByDefault;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertEquals;
/// Tests CurseForge fingerprint calculation for local files.
@NotNullByDefault
public final class CurseForgeRemoteAddonRepositoryTest {
/// Verifies that streaming calculation remains identical to the previous in-memory algorithm.
@Test
public void calculatesFingerprintWithoutChangingTheResult(@TempDir Path tempDir) throws IOException {
byte[] boundarySample = new byte[8192 + 17];
new Random(0).nextBytes(boundarySample);
for (int i = 0; i < boundarySample.length; i += 97) {
boundarySample[i] = 0x20;
}
byte[][] samples = {
{},
{0x9, 0xa, 0xd, 0x20},
{1},
{1, 2},
{1, 2, 3},
{1, 2, 3, 4},
{1, 0x20, 2, 0xa, 3, 0xd, 4, 0x9, 5},
boundarySample
};
for (int i = 0; i < samples.length; i++) {
byte[] sample = samples[i];
Path file = tempDir.resolve("sample-" + i);
Files.write(file, sample);
ByteArrayOutputStream filtered = new ByteArrayOutputStream();
for (byte b : sample) {
if (b != 0x9 && b != 0xa && b != 0xd && b != 0x20) {
filtered.write(b);
}
}
long expected = Integer.toUnsignedLong(MurmurHash2.hash32(filtered.toByteArray(), filtered.size(), 1));
assertEquals(expected, CurseForgeRemoteAddonRepository.calculateFingerprint(file));
}
}
}
@@ -0,0 +1,272 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2026 huangyuhui <[email protected]> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
import org.jetbrains.annotations.NotNullByDefault;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Random;
import java.util.zip.Checksum;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/// Tests the streaming MurmurHash2 checksum implementation.
@NotNullByDefault
public final class MurmurHash2Test {
/// Compares whole-array, bulk, byte-wise, and fragmented updates across lengths and seeds.
@Test
public void testStreamingHash32AcrossLengthsAndSeeds() {
byte[] data = new byte[513];
new Random(0x6d75726d75724cL).nextBytes(data);
for (int length = 0; length <= data.length; length++) {
assertStreamingHash32(data, length, 0);
assertStreamingHash32(data, length, -1);
assertStreamingHash32(data, length, 0x9747b28c);
assertStreamingHash32(data, length, 0x9747b28c ^ length * 31);
}
}
/// Compares every two-part split while reading from a non-zero source-array offset.
@Test
public void testStreamingHash32AtEverySplitPoint() {
byte[] data = new byte[73];
new Random(0x73706c697473L).nextBytes(data);
int sourceOffset = 5;
byte[] paddedData = new byte[sourceOffset + data.length + 7];
System.arraycopy(data, 0, paddedData, sourceOffset, data.length);
for (int length = 0; length <= data.length; length++) {
int seed = Integer.rotateLeft(0x4d757232, length & 31);
long expected = hash32(data, length, seed);
for (int split = 0; split <= length; split++) {
Checksum checksum = MurmurHash2.hash32(length, seed);
checksum.update(paddedData, sourceOffset, split);
checksum.update(paddedData, sourceOffset + split, length - split);
assertEquals(expected, checksum.getValue(),
"length=" + length + ", split=" + split);
}
}
}
/// Compares interleaved single-byte and bulk updates after every possible short prefix.
@Test
public void testStreamingHash32WithMixedUpdates() {
byte[] data = new byte[129];
new Random(0x6d69786564L).nextBytes(data);
for (int length = 0; length <= data.length; length++) {
int seed = 0x13579bdf ^ length;
long expected = hash32(data, length, seed);
for (int prefixLength = 0; prefixLength <= Math.min(length, 7); prefixLength++) {
Checksum checksum = MurmurHash2.hash32(length, seed);
int offset = 0;
while (offset < prefixLength) {
checksum.update(data[offset++]);
}
// Exercise an empty bulk update both with and without a buffered tail.
checksum.update(data, offset, 0);
while (offset < length) {
int chunkLength = Math.min(offset % 5 + 1, length - offset);
checksum.update(data, offset, chunkLength);
offset += chunkLength;
if (offset < length) {
checksum.update(data[offset++]);
}
}
assertEquals(expected, checksum.getValue(),
"length=" + length + ", prefixLength=" + prefixLength);
}
}
}
/// Compares array-backed, read-only, direct, and empty-buffer updates.
@Test
public void testStreamingHash32WithByteBuffers() {
byte[] data = new byte[8207];
new Random(0x627566666572L).nextBytes(data);
int seed = 0x2468ace0;
long expected = hash32(data, data.length, seed);
int sourceOffset = 3;
byte[] paddedData = new byte[sourceOffset + data.length + 4];
System.arraycopy(data, 0, paddedData, sourceOffset, data.length);
ByteBuffer arrayBackedBuffer = ByteBuffer.wrap(paddedData);
arrayBackedBuffer.position(sourceOffset);
arrayBackedBuffer.limit(sourceOffset + data.length);
assertTrue(arrayBackedBuffer.hasArray());
Checksum arrayBackedChecksum = MurmurHash2.hash32(data.length, seed);
arrayBackedChecksum.update(arrayBackedBuffer);
assertEquals(arrayBackedBuffer.limit(), arrayBackedBuffer.position());
assertEquals(expected, arrayBackedChecksum.getValue());
ByteBuffer readOnlyBuffer = ByteBuffer.wrap(data).asReadOnlyBuffer();
assertFalse(readOnlyBuffer.hasArray());
Checksum readOnlyChecksum = MurmurHash2.hash32(data.length, seed);
readOnlyChecksum.update(readOnlyBuffer);
assertEquals(readOnlyBuffer.limit(), readOnlyBuffer.position());
assertEquals(expected, readOnlyChecksum.getValue());
ByteBuffer directBuffer = ByteBuffer.allocateDirect(sourceOffset + data.length);
directBuffer.position(sourceOffset);
directBuffer.put(data);
directBuffer.position(sourceOffset);
assertFalse(directBuffer.hasArray());
Checksum directChecksum = MurmurHash2.hash32(data.length, seed);
directChecksum.update(directBuffer);
assertEquals(directBuffer.limit(), directBuffer.position());
assertEquals(expected, directChecksum.getValue());
ByteBuffer emptyBuffer = ByteBuffer.allocate(4);
emptyBuffer.position(2);
emptyBuffer.limit(2);
Checksum emptyFirstChecksum = MurmurHash2.hash32(data.length, seed);
emptyFirstChecksum.update(emptyBuffer);
emptyFirstChecksum.update(data);
assertEquals(2, emptyBuffer.position());
assertEquals(expected, emptyFirstChecksum.getValue());
}
/// Verifies that single-byte updates use only the low eight bits of each value.
@Test
public void testStreamingHash32SingleByteValues() {
byte[] data = {0x12, (byte) 0xff, (byte) 0x80, 0x00};
int seed = 0x12345678;
Checksum checksum = MurmurHash2.hash32(data.length, seed);
checksum.update(0x112);
checksum.update(-1);
checksum.update(0xabcdef80);
checksum.update(0x100);
assertEquals(hash32(data, data.length, seed), checksum.getValue());
}
/// Verifies that rejected array ranges leave the checksum state unchanged.
@Test
public void testStreamingHash32ArrayRangeValidation() {
byte[] data = {0x12, 0x34, 0x56};
int seed = 0x12345678;
Checksum checksum = MurmurHash2.hash32(data.length, seed);
assertThrows(NullPointerException.class, () -> checksum.update(null, 0, 0));
assertThrows(IndexOutOfBoundsException.class, () -> checksum.update(data, -1, 1));
assertThrows(IndexOutOfBoundsException.class, () -> checksum.update(data, 0, -1));
assertThrows(IndexOutOfBoundsException.class, () -> checksum.update(data, data.length, 1));
checksum.update(data);
assertEquals(hash32(data, data.length, seed), checksum.getValue());
}
/// Verifies that `getValue()` rejects incomplete and excessive input without finalizing state.
@Test
public void testExactLengthValidation() {
byte[] data = {0x12, 0x34, 0x56, 0x78, (byte) 0x9a};
int seed = 0x12345678;
long expected = hash32(data, data.length, seed);
Checksum checksum = MurmurHash2.hash32(data.length, seed);
checksum.update(data, 0, data.length - 1);
assertThrows(IllegalStateException.class, checksum::getValue);
checksum.update(data[data.length - 1]);
assertEquals(expected, checksum.getValue());
checksum.update(0xff);
checksum.update(0xfe);
assertThrows(IllegalStateException.class, checksum::getValue);
checksum.reset();
checksum.update(data, 0, data.length);
assertEquals(expected, checksum.getValue());
}
/// Verifies the valid range of the declared input length.
@Test
public void testLengthRange() {
assertThrows(IllegalArgumentException.class, () -> MurmurHash2.hash32(-1, 0));
Checksum checksum = MurmurHash2.hash32(1L << 32, 0);
assertThrows(IllegalStateException.class, checksum::getValue);
}
/// Compares the principal update styles with the byte-array implementation.
///
/// @param data the input array
/// @param length the number of bytes to hash
/// @param seed the initial seed value
private static void assertStreamingHash32(byte[] data, int length, int seed) {
long expected = hash32(data, length, seed);
byte[] exactData = Arrays.copyOf(data, length);
Checksum wholeArray = MurmurHash2.hash32(length, seed);
wholeArray.update(exactData);
assertEquals(expected, wholeArray.getValue());
Checksum bulk = MurmurHash2.hash32(length, seed);
bulk.update(data, 0, length);
assertEquals(expected, bulk.getValue());
assertEquals(expected, bulk.getValue());
Checksum byteWise = MurmurHash2.hash32(length, seed);
for (int i = 0; i < length; i++) {
byteWise.update(data[i]);
}
assertEquals(expected, byteWise.getValue());
Checksum fragmented = MurmurHash2.hash32(length, seed);
int offset = 0;
while (offset < length) {
int chunkLength = Math.min(offset % 7 + 1, length - offset);
fragmented.update(data, offset, chunkLength);
offset += chunkLength;
}
assertEquals(expected, fragmented.getValue());
}
/// Returns the unsigned value produced by the byte-array implementation.
///
/// @param data the input array
/// @param length the number of bytes to hash
/// @param seed the initial seed value
/// @return the unsigned 32-bit hash value represented by a `long`
private static long hash32(byte[] data, int length, int seed) {
return Integer.toUnsignedLong(MurmurHash2.hash32(data, length, seed));
}
/// Prevents instantiation.
private MurmurHash2Test() {
}
}
+1
View File
@@ -12,6 +12,7 @@ dependencies {
implementation(libs.jna)
implementation(libs.kala.compress.tar)
implementation(libs.kala.compress.ar)
implementation(libs.weburl)
}
java {
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.gradle.mod;
import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.reflect.TypeToken;
import org.glavo.url.WebURL;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.RegularFileProperty;
@@ -33,7 +34,6 @@ import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
@@ -58,7 +58,7 @@ public abstract class ParseModDataTask extends DefaultTask {
private static final String MOD_SEPARATOR = ",";
private static final Pattern[] CURSEFORGE_PATTERNS = {
Pattern.compile("^/(minecraft|Minecraft|minecraft-bedrock)/(mc-mods|data-packs|modpacks|customization|mc-addons|texture-packs|customization/configuration|addons|scripts)/+(?<modid>[\\w-]+)(/(.*?))?$"),
Pattern.compile("^/(minecraft|Minecraft|minecraft-bedrock)/(mc-mods|data-packs|modpacks|customization|mc-addons|texture-packs|customization/configuration|addons|scripts|bukkit-plugins)/+(?<modid>[\\w-]+)(/(.*?))?$"),
Pattern.compile("^/projects/(?<modid>[\\w-]+)(/(.*?))?$"),
Pattern.compile("^/mc-mods/minecraft/(?<modid>[\\w-]+)(/(.*?))?$"),
Pattern.compile("^/legacy/mc-mods/minecraft/(\\d+)-(?<modid>[\\w-]+)"),
@@ -70,27 +70,6 @@ public abstract class ParseModDataTask extends DefaultTask {
.replace("&gt;", ">");
}
private static String parseCurseforge(String url) {
URI res = URI.create(url.replace(" ", "%20"));
if (!"http".equals(res.getScheme()) && !"https".equals(res.getScheme())) {
return "";
}
if ("edge.forgecdn.net".equals(res.getHost())) {
return "";
}
for (Pattern pattern : CURSEFORGE_PATTERNS) {
Matcher matcher = pattern.matcher(res.getPath());
if (matcher.matches()) {
return matcher.group("modid");
}
}
return "";
}
private static final Pattern MCMOD_PATTERN =
Pattern.compile("^https://www\\.mcmod\\.cn/(class|modpack)/(?<modid>\\d+)\\.html$");
@@ -169,7 +148,8 @@ public abstract class ParseModDataTask extends DefaultTask {
}
if (chineseName.contains(S) || subName.contains(S)) {
throw new GradleException("Error: " + chineseName);
LOGGER.warn("Error chinese name: {}", chineseName);
continue;
}
String curseforgeId = "";
@@ -180,13 +160,40 @@ public abstract class ParseModDataTask extends DefaultTask {
List<ModData.Link> mcmodLinks = links.get("mcmod");
if (curseforgeLinks != null && !curseforgeLinks.isEmpty()) {
boolean reportError = true;
for (ModData.Link link : curseforgeLinks) {
curseforgeId = parseCurseforge(link.url);
WebURL res = WebURL.parse(link.url);
if (!"http".equals(res.getScheme()) && !"https".equals(res.getScheme())) {
curseforgeId = "";
break;
}
if (Set.of(
"files.xmdhs.com",
"edge.forgecdn.net",
"minecraft.curseforge.com",
"mediafilez.forgecdn.net"
).contains(res.getHost())) {
reportError = false;
curseforgeId = "";
break;
}
for (Pattern pattern : CURSEFORGE_PATTERNS) {
Matcher matcher = pattern.matcher(res.getPath());
if (matcher.matches()) {
curseforgeId = matcher.group("modid");
break;
}
}
if (!curseforgeId.isEmpty()) {
break;
}
}
if (curseforgeId.isEmpty()) {
if (curseforgeId.isEmpty() && reportError) {
LOGGER.warn("Error curseforge: {}", chineseName);
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
[versions]
hmclauncher = "3.7.0.1"
jetbrains-annotations = "26.1.0"
kala-compress = "1.27.1-3"
kala-compress = "1.27.1-5"
simple-png-javafx = "0.3.0"
gson = "2.13.2"
tomlj = "1.1.1"
@@ -13,7 +13,7 @@ hello-nbt = "0.3.0"
nanohttpd = "2.3.1"
jsoup = "1.21.2"
chardet = "2.5.0"
fxsvgimage = "1.3"
fxsvgimage = "1.9.1"
jna = "5.18.1"
pci-ids = "0.4.0"
java-info = "1.0"