Merge branch 'removeArch' into Data_Source_Rewrite

This commit is contained in:
James Seibel
2024-04-14 16:41:28 -05:00
19 changed files with 566 additions and 401 deletions
+3
View File
@@ -37,3 +37,6 @@ build.properties
*.sqlite-journal
*.sqlite-shm
*.sqlite-wal
# Don't add access transformers to git as it's dynamically generated
accesstransformer.cfg
+4
View File
@@ -47,12 +47,16 @@ build:
exclude:
# TODO: There is a lot of duplicate stuff here, try to maybe make it smaller
- fabric/build/libs/*-all.jar
- fabric/build/libs/*-dev.jar
- fabric/build/libs/*-sources.jar
- quilt/build/libs/*-all.jar
- quilt/build/libs/*-dev.jar
- quilt/build/libs/*-sources.jar
- forge/build/libs/*-all.jar
- forge/build/libs/*-dev.jar
- forge/build/libs/*-sources.jar
- neoforge/build/libs/*-all.jar
- neoforge/build/libs/*-dev.jar
- neoforge/build/libs/*-sources.jar
expire_in: 14 days
when: always
+2 -2
View File
@@ -21,8 +21,8 @@ Below is a video demonstrating the system:
#### 1.20.4, 1.20.3 (Default)
Fabric: 0.15.1\
Fabric API: 0.91.2+1.20.4\
Forge: 49.0.16\
NeoForge: 20.4.83-beta\
Forge: 49.0.30\
NeoForge: 118-beta\
Parchment: 1.20.2:2023.12.10\
Modmenu: 9.0.0-pre.1
+127 -139
View File
@@ -1,29 +1,32 @@
plugins {
id "java"
// Plugin to handle dependencies
// Plugin to put dependencies inside our final jar
id "com.github.johnrengelman.shadow" version '7.1.2' apply false
// Plugin to create merged jars
id "io.github.pacifistmc.forgix" version "1.2.6"
// Manifold preprocessor
id "systems.manifold.manifold-gradle-plugin" version "0.0.2-alpha"
// // Provides mc libraries to core
// id "org.spongepowered.gradle.vanilla" version '0.2.1-SNAPSHOT' apply false
// Architectury is used here only as a replacement for forge's own loom
id "dev.architectury.loom" version "1.4-SNAPSHOT" apply false
// Unimined is our all in one solution to minecraft loaders
// id "xyz.wagyourtail.unimined" version "1.2.0-SNAPSHOT" apply false // Unstable Release (TODO: Check back to see when the unstable branch fixes forge run without the jank)
id "xyz.wagyourtail.unimined" version "1.1.2-SNAPSHOT" apply false // LTS Release
}
// Transfers the values set in settings.gradle to the rest of the project
project.gradle.ext.getProperties().each { prop ->
rootProject.ext.set(prop.key, prop.value)
// println "Added prop [key:" + prop.key + ", value:" + prop.value + "]"
}
/**
* Creates the list of preprocessors to use.
*
* @param mcVers array of all MC versions
* @param mcIndex array index of the currently active MC version
*/
def writeBuildGradlePredefine(List<String> mcVers, int mcIndex)
def writeBuildGradlePredefine(List<String> mcVers, int mcIndex)
{
// Build the list of preprocessors to use
StringBuilder sb = new StringBuilder();
@@ -31,7 +34,7 @@ def writeBuildGradlePredefine(List<String> mcVers, int mcIndex)
sb.append("# DON'T TOUCH THIS FILE, This is handled by the build script\n");
for (int i = 0; i < mcVers.size(); i++)
for (int i = 0; i < mcVers.size(); i++)
{
String verStr = mcVers[i].replace(".", "_");
sb.append("MC_" + verStr + "=" + i.toString() + "\n");
@@ -42,23 +45,15 @@ def writeBuildGradlePredefine(List<String> mcVers, int mcIndex)
// Check if this is a development build
if (mod_version.toLowerCase().contains("dev"))
if (mod_version.toLowerCase().contains("dev"))
{
// WARNING: only use this for logging, we don't want to have confusion
// when a method doesn't work correctly in the release build.
sb.append("DEV_BUILD=\n");
}
new File(projectDir, "build.properties").text = sb.toString()
}
// Transfers the values set in settings.gradle to the rest of the project
project.gradle.ext.getProperties().each { prop ->
rootProject.ext.set(prop.key, prop.value)
// println "Added prop [key:" + prop.key + ", value:" + prop.value + "]"
}
// Sets up manifold stuff
writeBuildGradlePredefine(rootProject.mcVers, rootProject.mcIndex)
@@ -66,6 +61,7 @@ writeBuildGradlePredefine(rootProject.mcVers, rootProject.mcIndex)
// Sets up the version string (the name we use for our jar)
rootProject.versionStr = rootProject.mod_version + "-" + rootProject.minecraft_version // + "-" + new Date().format("yyyy_MM_dd_HH_mm")
// Forgix settings (used for merging jars)
forgix {
group = "com.seibel.distanthorizons"
@@ -96,7 +92,7 @@ forgix {
}
subprojects { p ->
// Does the same as "p == project(":common") || p == project(":fabric") || p == project(":quilt") || p == project(":forge") || p == project("WhateverWeAddLaterOn")"
// Does the same as "p == project(":common") || p == project(":fabric") || p == project(":forge") || p == project("WhateverLoaderWeAddLaterOn")"
// Useful later on so we dont have duplicated code
def isMinecraftSubProject = p != project(":core") && p != project(":api")
@@ -104,73 +100,68 @@ subprojects { p ->
// Apply plugins
apply plugin: "java"
apply plugin: "com.github.johnrengelman.shadow"
if (isMinecraftSubProject)
apply plugin: "systems.manifold.manifold-gradle-plugin"
if (p == project(":core"))
if (p == project(":core"))
apply plugin: "application"
// apply plugin: "org.spongepowered.gradle.vanilla" // Provides minecraft libraries
if (isMinecraftSubProject) {
apply plugin: "xyz.wagyourtail.unimined"
// Apply forge's loom
if (
(findProject(":forge") && p == project(":forge")) ||
(findProject(":neoforge") && p == project(":neoforge"))
)
apply plugin: "dev.architectury.loom"
unimined.minecraft(sourceSets.main, true) {
version = rootProject.minecraft_version
def parchmentVersionParts = rootProject.parchment_version.split(":")
mappings {
mojmap()
parchment(parchmentVersionParts[0], parchmentVersionParts[1])
devNamespace "mojmap"
}
runs {
config("client") {
workingDir = rootProject.file("run")
}
config("server") {
workingDir = rootProject.file("run") // TODO: When running the server, would it be a better idea to change this to a different dir?
disabled = true // TODO: Once server-side support is added, remove this
}
}
}
// Set the manifold version (may not be required tough)
manifold {
manifoldVersion = rootProject.manifold_version
if (p != project(":common")) {
tasks.withType(JavaCompile).configureEach {
source(project(":common").sourceSets.main.java)
source(project(":api").sourceSets.main.java)
source(project(":core").sourceSets.main.java)
}
}
}
tasks.withType(GenerateModuleMetadata) {
enabled = false
}
// Disable testing for projects that aren't the core or api project.
// If not done compiling will fail due to an issue with Manifold
if (isMinecraftSubProject) {
test {
enabled = false
}
compileTestJava {
enabled = false
}
}
// set up custom configurations (configurations are a way to handle dependencies)
configurations {
// extends the shadowJar configuration
shadowMe
shadowMc // Configuration that doesn't contain coreProjects
// have implemented dependencies automatically embedded in the final jar
implementation.extendsFrom(shadowMe)
implementation.extendsFrom(shadowMc)
// Configuration fpr core & api
coreProjects
shadowMe.extendsFrom(coreProjects)
// FIXME this additional configuration is necessary because forge
// needs forgeRuntimeLibrary, although adding it to shadowMe
// causes runtime issues where the libraries aren't properly added
forgeShadowMe
// this should match shadowMe pretty closely
implementation.extendsFrom(forgeShadowMe)
shadowMe.extendsFrom(forgeShadowMe)
forgeRuntimeLibrary.extendsFrom(forgeShadowMe)
if (isMinecraftSubProject && p != project(":common")) {
// Shadow common
common
shadowCommon // Don't use shadow from the shadow plugin because we don't want IDEA to index this.
compileClasspath.extendsFrom common
runtimeClasspath.extendsFrom common
if (findProject(":forge"))
developmentForge.extendsFrom common
if (findProject(":neoforge"))
developmentNeoForge.extendsFrom common
compileClasspath.extendsFrom coreProjects
runtimeClasspath.extendsFrom coreProjects
if (findProject(":forge"))
developmentForge.extendsFrom coreProjects
if (findProject(":neoforge"))
developmentNeoForge.extendsFrom coreProjects
if (findProject(":fabricLike") && p != project(":fabricLike")) {
// Shadow fabricLike
fabricLike
shadowFabricLike
compileClasspath.extendsFrom fabricLike
runtimeClasspath.extendsFrom fabricLike
}
}
// Add shaded libraries very early in the classpath (excluding coreProjects as that's added in a different way)
minecraftLibraries.extendsFrom(shadowMc)
}
@@ -187,53 +178,52 @@ subprojects { p ->
// shared dependencies //
//=====================//
// Manifold
if (isMinecraftSubProject) {
annotationProcessor("systems.manifold:manifold-preprocessor:${rootProject.manifold_version}")
}
// Log4j
// TODO: Change to shadowMe later to work in the standalone jar
// TODO: Change to shadowCore later to work in the standalone jar
// We cannot do this now as it would break Quilt
implementation("org.apache.logging.log4j:log4j-api:${rootProject.log4j_version}")
implementation("org.apache.logging.log4j:log4j-core:${rootProject.log4j_version}")
// JOML
if (project.hasProperty("embed_joml") && embed_joml == "true")
forgeShadowMe("org.joml:joml:${rootProject.joml_version}")
else
implementation("org.joml:joml:${rootProject.joml_version}")
// JUnit tests
implementation("org.junit.jupiter:junit-jupiter:5.8.2")
implementation("org.junit.jupiter:junit-jupiter-engine:5.8.2")
implementation("junit:junit:4.13")
// JOML
if (project.hasProperty("embed_joml") && embed_joml == "true")
shadowMc("org.joml:joml:${rootProject.joml_version}")
else
implementation("org.joml:joml:${rootProject.joml_version}")
// Compression
forgeShadowMe("org.lz4:lz4-java:${rootProject.lz4_version}") // LZ4
forgeShadowMe("com.github.luben:zstd-jni:${rootProject.zstd_version}") // Zstd
forgeShadowMe("org.tukaani:xz:${rootProject.xz_version}") // LZMA
shadowMc("org.lz4:lz4-java:${rootProject.lz4_version}") // LZ4
shadowMc("com.github.luben:zstd-jni:${rootProject.zstd_version}") // Zstd
shadowMc("org.tukaani:xz:${rootProject.xz_version}") // LZMA
// Sqlite Database
forgeShadowMe("org.xerial:sqlite-jdbc:${rootProject.sqlite_jdbc_version}")
shadowMc("org.xerial:sqlite-jdbc:${rootProject.sqlite_jdbc_version}")
// NightConfig (includes Toml & Json)
forgeShadowMe("com.electronwill.night-config:toml:${rootProject.nightconfig_version}")
forgeShadowMe("com.electronwill.night-config:json:${rootProject.nightconfig_version}")
shadowMc("com.electronwill.night-config:toml:${rootProject.nightconfig_version}")
shadowMc("com.electronwill.night-config:json:${rootProject.nightconfig_version}")
// Fastutil
forgeShadowMe("it.unimi.dsi:fastutil:${rootProject.fastutil_version}")
shadowMc("it.unimi.dsi:fastutil:${rootProject.fastutil_version}")
// SVG (not needed atm)
// forgeShadowMe("com.formdev:svgSalamander:${rootProject.svgSalamander_version}")
//shadowMc("com.formdev:svgSalamander:${rootProject.svgSalamander_version}")
// Netty
// Breaks 1.16.5
//forgeShadowMe("io.netty:netty-all:${rootProject.netty_version}")
//shadowMc("io.netty:netty-all:${rootProject.netty_version}")
// Remember, for lwjgl dependencies that arent included in Minecraft, you need to also need to add it to the ShadowJar thing
forgeShadowMe("org.lwjgl:lwjgl-jawt:${rootProject.lwjgl_version}") {
shadowMc("org.lwjgl:lwjgl-jawt:${rootProject.lwjgl_version}") {
exclude group: "org.lwjgl", module: "lwjgl" // This module is imported by Minecraft so exclude it
}
@@ -246,11 +236,12 @@ subprojects { p ->
// Add core
if (isMinecraftSubProject) {
coreProjects(project(":core")) {
compileOnly(project(":core")) {
// Remove Junit test libraries
exclude group: "org.junit.jupiter", module: "junit-jupiter"
exclude group: "org.junit.jupiter", module: "junit-jupiter-engine"
exclude group: "junit", module: "junit"
// Removed dependencies
transitive false
}
@@ -258,11 +249,12 @@ subprojects { p ->
// Add the api
if (p != project(":api")) {
coreProjects(project(":api")) {
implementation(project(":api")) {
// Remove Junit test libraries
exclude group: "org.junit.jupiter", module: "junit-jupiter"
exclude group: "org.junit.jupiter", module: "junit-jupiter-engine"
exclude group: "junit", module: "junit"
// Removed dependencies
transitive false
}
@@ -271,29 +263,19 @@ subprojects { p ->
// Add common
if (isMinecraftSubProject && p != project(":common")) {
// Common
common(project(":common")) { transitive false }
shadowCommon(project(":common")) { transitive false }
// FabricLike
if (findProject(":fabricLike") && p != project(":fabricLike")) {
fabricLike(project(path: ":fabricLike")) { transitive false }
shadowFabricLike(project(path: ":fabricLike")) { transitive false }
}
compileOnly(project(":common")) { transitive false }
}
}
shadowJar {
configurations = [project.configurations.shadowMe]
configurations = [project.configurations.shadowMc]
if (isMinecraftSubProject && p != project(":common")) {
configurations.push(project.configurations.shadowCommon) // Shadow the common subproject
relocate "com.seibel.distanthorizons.common", "loaderCommon.${p.name}.com.seibel.distanthorizons.common" // Move the loader files to a different location
if (findProject(":fabricLike") && p != project(":fabricLike")) {
configurations.push(project.configurations.shadowFabricLike) // Shadow the fabricLike subproject
relocate "com.seibel.distanthorizons.fabriclike", "loaderCommon.${p.name}.com.seibel.distanthorizons.fabriclike" // Move the loader files to a different location
}
}
def librariesLocation = "distanthorizons.libraries"
// LWJGL
@@ -331,7 +313,20 @@ subprojects { p ->
// Put stuff from gradle.properties into the mod info
processResources {
def resourceTargets = [ // Location of where to inject the properties
duplicatesStrategy = DuplicatesStrategy.WARN
// Include all the resources
from project(":common").sourceSets.main.resources
from project(":core").sourceSets.main.resources
from project(":api").sourceSets.main.resources
// Copy accessWideners
// FIXME: remove copyCommonLoaderResources and use this instead (and if you are removing that task, also remove copyCoreResources while your at it)
// from project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener")
// into(file(p.file("build/resources/main")))
rename "${accessWidenerVersion}.distanthorizons.accesswidener", "distanthorizons.accesswidener"
// Location of where to inject the properties
def resourceTargets = [
// Holds info like git commit
// TODO: For some reason this script doesnt work with the core project
"build_info.json",
@@ -344,7 +339,7 @@ subprojects { p ->
// The mixins for each of the loaders
"DistantHorizons."+ p.name +".fabricLike.mixins.json"
]
def intoTargets = ["$buildDir/resources/main/"] // Location of the built resources folder
def buildResourceTargets = ["$buildDir/resources/main/"] // Location of the built resources folder
// Fix forge version numbering system as it is weird
// For whatever reason forge uses [1.18, 1.18.1, 1.18.2) instead of the standard ["1.18", "1.18.1", "1.18.2"]
@@ -352,7 +347,7 @@ subprojects { p ->
// println compatible_forgemc_versions
// Quilt's custom contributors system
// This has to be like
// has to be in the format:
// "Person": "Developer", "Another person": "Developer"
def quilt_contributors = []
def mod_author_list = mod_authors.replaceAll("\"", "").replace("[", "").replace("]", "").split(",")
@@ -360,9 +355,9 @@ subprojects { p ->
quilt_contributors.push("\"${dev.strip()}\": \"Developer\"")
}
quilt_contributors.reverse()
// println quilt_contributors.join(", ")
//println quilt_contributors.join(", ")
// TODOI: Find something we can use so we can basically re-map only when the jar is shadowed and relocated
// TODO: Find something we can use so we can basically re-map only when the jar is shadowed and relocated
// println p.tasks.findByName('shadowJar')
@@ -377,6 +372,7 @@ subprojects { p ->
println "Git or Git project not found"
}
// The left side is what gets replaced in the mod info and the right side is where to get it from in the gradle.properties
def replaceProperties = [
version : mod_version,
mod_name : mod_readable_name,
@@ -400,15 +396,17 @@ subprojects { p ->
fabric_incompatibility_list : fabric_incompatibility_list,
fabric_recommend_list : fabric_recommend_list,
]
// The left side is what gets replaced in the mod info and the right side is where to get it from in the gradle.properties
// replace any properties in the sub-projects with the values defined here
inputs.properties replaceProperties
replaceProperties.put "project", project
replaceProperties.put("project", project)
filesMatching(resourceTargets) {
expand replaceProperties
}
intoTargets.each { target ->
// copy all our resources into the loader specific resource directory
buildResourceTargets.each { target ->
if (file(target).exists()) {
copy {
from(sourceSets.main.resources) {
@@ -465,13 +463,6 @@ subprojects { p ->
}
}
*/
// Run mergeJars when running build
// TODO: Fix later
// if (isMinecraftSubProject) {
// build.finalizedBy(mergeJars)
// assemble.finalizedBy(mergeJars)
// }
}
allprojects { p ->
@@ -579,6 +570,7 @@ allprojects { p ->
// Set the OS lwjgl is using to the current os
project.ext.lwjglNatives = "natives-" + os.toFamilyName()
// TODO: Include Minecraft in core-projects but dont include MC code stuff
dependencies { // All of these dependencies are in Vanilla Minecraft, but we need to depend on it as we arent importing Minecraft in the core
// Imports most of lwjgl's libraries (well, only the ones that we need)
implementation platform("org.lwjgl:lwjgl-bom:${rootProject.lwjgl_version}") // TODO: Use Minecraft's version for lwjgl_version (which changes in nearly every version) instead of a hard defined version for all versions
@@ -610,26 +602,22 @@ allprojects { p ->
}
// TODO: Remove this as no loader needs this
// - Fabric can rename which aw they use
// - (Neo)Forge converts the aw to their own at, which is stored at a different place
task copyCommonLoaderResources(type: Copy) {
from project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener")
into(file(p.file("build/resources/main")))
rename "${accessWidenerVersion}.distanthorizons.accesswidener", "distanthorizons.accesswidener"
// Move the fabricLike mixin to its different places for each subproject
if (findProject(":fabricLike")) {
from project(":fabricLike").file("src/main/resources/DistantHorizons.fabricLike.mixins.json")
into(file(p.file("build/resources/main")))
rename "DistantHorizons.fabricLike.mixins.json", "DistantHorizons." + p.name + ".fabricLike.mixins.json"
}
}
// TODO: Remove this later as we no longer need this. We are now including the resources in the processResources section
task copyCoreResources(type: Copy) {
from fileTree(project(":core").file("src/main/resources"))
into p.file("build/resources/main")
}
tasks.withType(JavaCompile) {
compileJava {
if (isMinecraftSubProject) {
options.release = rootProject.java_version as Integer
options.compilerArgs += ["-Xplugin:Manifold"]
+239
View File
@@ -0,0 +1,239 @@
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AWToAT {
private static final Map<String, String> ACCESS_POINT_MAP = new HashMap<>();
static {
ACCESS_POINT_MAP.put("accessible", "public");
ACCESS_POINT_MAP.put("extendable", "public-f");
ACCESS_POINT_MAP.put("mutable", "public-f");
}
public String minecraftVersion;
public File remap(File file, String minecraftVersion) {
this.minecraftVersion = minecraftVersion.replace("_", ".");
File atFile = createATFile(file);
processFile(file, atFile);
return atFile;
}
private File createATFile(File file) {
File metaInf = new File(file.getParentFile(), "META-INF");
if (!metaInf.exists() && !metaInf.mkdir()) throw new RuntimeException("Error creating META-INF folder");
File atFile = new File(metaInf, "accesstransformer.cfg");
try {
atFile.createNewFile();
} catch (IOException e) {
throw new RuntimeException("Error creating new file", e);
}
return atFile;
}
private void processFile(File file, File atFile) {
/* Validates if we need to recreate the Access Transformer file if it's out of date */
// Get the hash of the file
String fileHash = getFileHash(file);
try (Scanner atScanner = new Scanner(atFile)) {
// Check if the AT file is up-to-date by comparing the hash of the file with the hash stored in the AT file
boolean hashFound = false;
while (atScanner.hasNextLine()) {
String line = atScanner.nextLine();
if (hashCheck(line, fileHash)) {
hashFound = true;
}
}
// If the AT file is up-to-date, print a message and return
if (hashFound) {
System.out.println("Access Transformer file is already up to date.");
return;
}
} catch (FileNotFoundException ignored) {
// If the AT file does not exist, continue
}
/* Creates the Access Transformer file */
// Opens a scanner for reading the Access Widener file and a writer for writing to the Access Transformer file
try (Scanner scanner = new Scanner(file); FileWriter writer = new FileWriter(atFile)) {
// Create an ExecutorService with a fixed thread pool size equal to the number of available processors
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
// List to hold Future objects representing results of computation
List<Future<String>> futures = new ArrayList<>();
// Write the hash of the file to the AT file
writer.write("#DH_MAPPING_HASH:" + fileHash + "\n");
// Read each line from the file
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
// Skip lines starting with "accessWidener", "#" or blank lines
if (line.startsWith("accessWidener") || line.startsWith("#") || line.isBlank()) continue;
// Submit the line to the executor service for processing
// The processing is done by the processLine method
futures.add(executor.submit(() -> processLine(line.split(" "))));
}
// Write the results to the output file
// The results are obtained by calling the get method on each Future
for (Future<String> future : futures) {
writer.write(future.get());
}
// Shutdown the executor service to free up resources
executor.shutdown();
} catch (Exception e) {
throw new RuntimeException("Error reading or writing to file", e);
}
}
private String processLine(String[] fields) {
// fields[0] = access point like "accessible", "extendable", "mutable"
// fields[1] = type like "field", "method", "class"
// fields[2] = class name
// fields[3] = field/method name
// fields[4] = field/method descriptor
try {
// Store the original field/method name
String originalName = "";
// If there is a class name, replace the slashes with dots in the package name
if (fields.length > 2) fields[2] = fields[2].replace("/", ".");
// If there is a field/method name, store the original name and remap it to SRG
if (fields.length > 3) {
originalName = fields[3];
fields[3] = remapToSRG(fields[2], fields[3]);
}
StringBuilder line = new StringBuilder(ACCESS_POINT_MAP.getOrDefault(fields[0], "public")).append(" ");
switch (fields[1]) {
case "field":
line.append(fields[2]).append(" ").append(fields[3]).append(" #").append(originalName);
// It'll be like: access-point class-name field-name-SRG # field-name-Mojmap
// Eg: public net.minecraft.client.Minecraft f_90981_ # instance
break;
case "method":
line.append(fields[2]).append(" ").append(fields[3]).append(fields[4]).append(" #").append(originalName);
// It'll be like: access-point class-name method-name-SRG method-descriptor # method-name-Mojmap
// Eg: public net.minecraft.client.Minecraft m_172797_()Lnet/minecraft/client/Minecraft; # getInstance
break;
default:
line.append(fields[2]);
// It'll be like: access-point class-name
// Eg: public net.minecraft.client.Minecraft
break;
}
line.append("\n");
return line.toString();
} catch (Exception e) {
throw new RuntimeException("Error processing line", e);
}
}
private boolean hashCheck(String line, String fileHash) {
if (line.startsWith("#DH_MAPPING_HASH:")) {
String hash = line.substring(17);
return hash.equals(fileHash);
}
return false;
}
public String getFileHash(File file) {
try {
MessageDigest shaDigest = MessageDigest.getInstance("SHA-256");
try (InputStream fis = new FileInputStream(file)) {
byte[] byteArray = new byte[1024];
int bytesCount;
// Read file data and update in message digest
while ((bytesCount = fis.read(byteArray)) != -1) {
shaDigest.update(byteArray, 0, bytesCount);
}
}
byte[] bytes = shaDigest.digest();
// Convert byte array into signum representation
StringBuilder sb = new StringBuilder();
for (byte aByte : bytes) {
sb.append(Integer.toString((aByte & 0xff) + 0x100, 16).substring(1));
}
// Return complete hash
return sb.toString();
} catch (NoSuchAlgorithmException | IOException e) {
throw new RuntimeException(e);
}
}
// WARNING: BELOW LIES HIGHLY CURSED CODE AND MIGHT EVEN BE ILLEGAL
// Flag to track if there was an error in the GET request
boolean error = false;
/**
* This method returns a field or method name from Mojang mappings as SRG mappings.
* It makes a GET request to the Linkie API to fetch the SRG name.
*
* @param clazz The class name
* @param name The field or method name
* @return The SRG name
* @throws Exception If there is an error in the GET request or the SRG name is not found in the response
*/
private String remapToSRG(String clazz, String name) throws Exception {
// Encode the class and field/method name to be used in the URL
String query = URLEncoder.encode(clazz + "." + name, StandardCharsets.UTF_8);
// Construct the URL for the GET request
String urlString = "https://linkieapi.shedaniel.me/api/search?namespace=mojang&query=" + query + "&version=" + this.minecraftVersion + "&limit=1&allowClasses=false&allowFields=true&allowMethods=true&translate=mojang_srg";
URL url = new URI(urlString).toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuilder content = new StringBuilder();
// Read the response line by line
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
conn.disconnect();
// Regex to find the SRG name in the response
Pattern pattern = Pattern.compile("\"l\"\\s*:\\s*\\{[^}]*\"i\"\\s*:\\s*\"([^\"]*)\"");
Matcher matcher = pattern.matcher(content.toString());
if (matcher.find()) return matcher.group(1);
else throw new Exception("Couldn't find the SRG mapping for name: " + name + "\nCould not find 'i' in 'l' object in the response"); // `i` is the SRG name which is stored in the `l` JSON object
} else {
if (error) {
// If there was an error in the GET request, and we already tried again, throw an exception
throw new Exception("The GET request failed");
}
// If there was an error in the GET request, wait 2.5 seconds and try again as we probably got rate limited
error = true;
Thread.sleep(2500);
return remapToSRG(clazz, name);
}
}
}
+12 -28
View File
@@ -1,32 +1,16 @@
plugins {
id "org.spongepowered.gradle.vanilla" version "0.2.1-SNAPSHOT"
}
// Use Unimined's implimentation for MC as we can use Parchment mappings in common now! :tada:
// With Sponge's vanilla gradle, we cannot change the mappings to anything out of mojmaps
unimined.minecraft {
fabric { // TODO: Find a way to only include the mc stuff, not fabric's loader
loader rootProject.fabric_loader_version
accessWidener(project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener"))
minecraft {
accessWideners(project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener"))
version(rootProject.minecraft_version)
skipInsertAw = true
}
defaultRemapJar = false
}
dependencies {
// We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies
// Do NOT use other classes from fabric loader
// modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
// So mixins can be written in common
compileOnly group:'org.spongepowered', name:'mixin', version:'0.8.5'
}
publishing {
publications {
mavenCommon(MavenPublication) {
artifactId = rootProject.mod_readable_name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
}
}
}
@@ -9,7 +9,7 @@ import net.minecraft.world.level.chunk.storage.RegionFile;
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import org.jetbrains.annotations.Nullable;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.file.Files;
+39 -47
View File
@@ -1,65 +1,49 @@
plugins {
id "fabric-loom" version "1.4-SNAPSHOT"
}
unimined.minecraft {
fabric {
loader rootProject.fabric_loader_version
accessWidener(project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener"))
loom {
accessWidenerPath = project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener")
// "runs" isn't required, but when we do need it then it can be useful
runs {
client {
client()
setConfigName("Fabric Client")
ideConfigGenerated(true)
runDir("../run")
}
server {
server()
setConfigName("Fabric Server")
ideConfigGenerated(true)
runDir("../run")
}
skipInsertAw = true
}
}
remapJar {
inputFile = shadowJar.archiveFile
dependsOn shadowJar
// classifier null
}
configurations {
// The addModJar basically embeds the mod to the built jar
addModJar
include.extendsFrom addModJar
modImplementation.extendsFrom addModJar
dummy
}
def addMod(path, enabled) {
if (enabled == "2")
dependencies { modImplementation(path) }
else if (enabled == "1")
dependencies { modCompileOnly(path) }
dependencies { compileOnly(path) }
}
// TODO: There currently seems to be a bug which causes the regular addModJar to not work, swap back to the regular addModJar when fixed
def addModJar_(path) {
dependencies {
modImplementation(path)
include(path)
}
}
dependencies {
minecraft "com.mojang:minecraft:${minecraft_version}"
mappings loom.layered() {
// Mojmap mappings
officialMojangMappings()
// Parchment mappings (it adds parameter mappings & javadoc)
parchment("org.parchmentmc.data:parchment-${rootProject.parchment_version}@zip")
}
// Fabric loader
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
annotationProcessor "javax.annotation:javax.annotation-api:1.3.2"
implementation("javax.annotation:javax.annotation-api:1.3.2")
runtimeOnly "javax.annotation:javax.annotation-api:1.3.2"
compileOnly "javax.annotation:javax.annotation-api:1.3.2"
modImplementation "javax.annotation:javax.annotation-api:1.3.2"
// Fabric API
addModJar(fabricApi.module("fabric-api-base", rootProject.fabric_api_version))
addModJar(fabricApi.module("fabric-lifecycle-events-v1", rootProject.fabric_api_version))
addModJar(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version))
addModJar(fabricApi.module("fabric-events-interaction-v0", rootProject.fabric_api_version))
addModJar(fabricApi.module("fabric-rendering-v1", rootProject.fabric_api_version)) // TODO: Remove this as it is only needed in 1 line (FabricClientProxy)
addModJar(fabricApi.module("fabric-networking-api-v1", rootProject.fabric_api_version))
addModJar_(fabricApi.fabricModule("fabric-api-base", rootProject.fabric_api_version))
addModJar_(fabricApi.fabricModule("fabric-lifecycle-events-v1", rootProject.fabric_api_version))
addModJar_(fabricApi.fabricModule("fabric-resource-loader-v0", rootProject.fabric_api_version))
addModJar_(fabricApi.fabricModule("fabric-events-interaction-v0", rootProject.fabric_api_version))
addModJar_(fabricApi.fabricModule("fabric-rendering-v1", rootProject.fabric_api_version)) // TODO: Remove this as it is only needed in 1 line (FabricClientProxy)
addModJar_(fabricApi.fabricModule("fabric-networking-api-v1", rootProject.fabric_api_version))
// Mod Menu
modImplementation("com.terraformersmc:modmenu:${rootProject.modmenu_version}")
@@ -114,6 +98,12 @@ dependencies {
}
}
remapJar {
inputFile = shadowJar.archiveFile
dependsOn shadowJar
// classifier null
}
task deleteResources(type: Delete) {
delete file("build/resources/main")
@@ -124,11 +114,13 @@ processResources {
dependsOn(copyCommonLoaderResources)
}
runClient {
dependsOn(copyCoreResources)
dependsOn(copyCommonLoaderResources)
afterEvaluate {
runClient {
dependsOn(copyCoreResources)
dependsOn(copyCommonLoaderResources)
// jvmArgs([ "-XX:-OmitStackTraceInFastThrow", minecraftMemoryJavaArg ])
finalizedBy(deleteResources)
finalizedBy(deleteResources)
}
}
//jar {
+63 -75
View File
@@ -1,80 +1,24 @@
plugins {
// Note: This is only needed for multi-loader projects
// The main architectury loom version is set at the start of the root build.gradle
id "architectury-plugin" version "3.4-SNAPSHOT"
}
unimined.minecraft {
minecraftForge {
loader forge_version
mixinConfig("DistantHorizons.forge.mixins.json")
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17
architectury {
platformSetupLoomIde()
forge()
}
//loom {
// forge {
// convertAccessWideners.set(true)
// extraAccessWideners.add("lod.accesswidener")
// mixinConfigs("DistantHorizons.mixins.json")
// }
//}
loom {
silentMojangMappingsLicense() // Shut the licencing warning
accessWidenerPath = project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener")
forge {
convertAccessWideners = true
extraAccessWideners.add loom.accessWidenerPath.get().asFile.name
mixinConfigs = [
"DistantHorizons.forge.mixins.json"
]
file("build/sourcesSets/main/META-INF/").mkdirs()
accessTransformer(aw2at(
project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener"),
file("build/sourcesSets/main/META-INF/accesstransformer.cfg") // We'd wanna output the access transformer to somewhere where it'll only appear in the final jar
))
}
// "runs" isn't required, but when we do need it then it can be useful
runs {
client {
client()
setConfigName("Forge Client")
ideConfigGenerated(true)
runDir("../run")
// vmArgs("-XX:-OmitStackTraceInFastThrow", minecraftMemoryJavaArg)
}
server {
server()
setConfigName("Forge Server")
ideConfigGenerated(true)
runDir("../run")
}
}
}
remapJar {
inputFile = shadowJar.archiveFile
dependsOn shadowJar
// classifier null
}
def addMod(path, enabled) {
if (enabled == "2")
dependencies { implementation(path) }
dependencies { modImplementation(path) }
else if (enabled == "1")
dependencies { modCompileOnly(path) }
dependencies { compileOnly(path) }
}
dependencies {
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
mappings loom.layered() {
// Mojmap mappings
officialMojangMappings()
// Parchment mappings (it adds parameter mappings & javadoc)
parchment("org.parchmentmc.data:parchment-${rootProject.parchment_version}@zip")
}
// Forge
forge "net.minecraftforge:forge:${rootProject.minecraft_version}-${rootProject.forge_version}"
// Architectury API
// if (minecraft_version == "1.16.5") {
// implementation("me.shedaniel:architectury-forge:${rootProject.architectury_version}")
@@ -89,28 +33,45 @@ dependencies {
addMod("curse.maven:TerraForged-363820:${rootProject.terraforged_version}", rootProject.enable_terraforged)
addMod("curse.maven:TerraFirmaCraft-302973:4616004", rootProject.enable_terrafirmacraft)
// annotationProcessor "org.spongepowered:mixin:0.8.5:processor"
// if (System.getProperty("idea.sync.active") != "true") {
// annotationProcessor "org.spongepowered:mixin:0.8.4:processor"
// }
}
task deleteResources(type: Delete) {
delete file("build/resources/main")
}
tasks.register('copyAllResources') {
dependsOn(copyCoreResources)
dependsOn(copyCommonLoaderResources)
}
processResources {
dependsOn(tasks.named('copyAllResources'))
}
tasks.named('runClient') {
dependsOn(tasks.named('copyAllResources'))
finalizedBy(deleteResources)
afterEvaluate {
runClient {
dependsOn(tasks.named('copyAllResources'))
}
// TODO this isn't a great place for these, but `tasks.build.doLast` doesn't always work and I'm not sure of a better place right now
tasks.runClient.doFirst {
// TODO can we just ignore these folders instead?
// deleting them may cause issues if the OS locks the files
// and it feels hacky
delete file("../common/build/libs")
delete file("../coreSubProjects/core/build/libs")
delete file("../coreSubProjects/api/build/libs")
}
}
remapJar {
inputFile = shadowJar.archiveFile
dependsOn shadowJar
// classifier null
}
@@ -124,4 +85,31 @@ sourcesJar {
// withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) {
// skip()
// }
//}
//}
// TODO this was specifically added for MC 1.20.4 and should not be used on MC versions prior to it
// source: https://github.com/MinecraftForge/MinecraftForge/blob/5d0047753dfac0caaf5d97cc3f5c9a8b0990cb44/mdk/build.gradle#L209-L217
//
// Merge the resources and classes into the same directory.
// This is done because java expects modules to be in a single directory.
// And if we have it in multiple we have to do performance intensive hacks like having the UnionFileSystem
// This will eventually be migrated to ForgeGradle so modders don't need to manually do it. But that is later.
sourceSets.each {
if ( // Only run on MC 1.20.4 or later
// FIXME: Add an environment variable for the Major, Minor, and Patch version number of Minecraft
minecraft_version.split("\\.")[1].toInteger() >= 20 &&
(
minecraft_version.split("\\.").length > 1 && // Incase there isn't a minor version
minecraft_version.split("\\.")[2].toInteger() >= 4
)
) {
// all of our code and resources should be in the sourceSets/main/ folder for Forge 1.20.4+
def dir = layout.buildDirectory.dir("sourcesSets/$it.name")
println "source name: [" + it.name + "]"// as of 2024-2-4 "it.name" only returned "main" and "test"
it.output.resourcesDir = dir
it.java.destinationDirectory = dir
}
}
@@ -70,7 +70,7 @@ import java.util.function.Consumer;
* If you are looking for the real start of the mod
* check out the ClientProxy.
*/
@Mod(ModInfo.ID)
@Mod("distanthorizons") // TODO: Change it back to ModInfo.ID when forge works
public class ForgeMain extends AbstractModInitializer
{
public ForgeMain()
@@ -38,7 +38,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import javax.annotation.Nullable;
import org.jetbrains.annotations.Nullable;
@Mixin(DynamicTexture.class)
public abstract class MixinDynamicTexture implements ILightTextureMarker
@@ -19,6 +19,5 @@
"client.MixinOptionsScreen",
"client.MixinTextureUtil"
],
"server": [],
"plugin": "com.seibel.distanthorizons.forge.mixins.ForgeMixinPlugin"
"server": []
}
+7 -2
View File
@@ -23,11 +23,16 @@ issueTrackerURL = "${issues}"
#// Allow any version to be present (or not) on the server
acceptableRemoteVersions = "*"
[[dependencies.distanthorizons]]
modId="forge" #mandatory
mandatory = true # Forge syntax
versionRange="[0,)" #mandatory
ordering="NONE"
side="BOTH"
[[dependencies.distanthorizons]]
modId = "minecraft"
mandatory = true # Forge syntax
type = "required" # Neoforge syntax
versionRange = "${compatible_forgemc_versions}" # Where we set what version of mc it is avalible for
ordering = "NONE"
ordering = "AFTER"
side = "BOTH"
+1
View File
@@ -2,6 +2,7 @@
org.gradle.jvmargs=-Xmx4096M
org.gradle.parallel=true
org.gradle.caching=true
fabric.loom.multiProjectOptimisation=true
# Mod Info
mod_name=DistantHorizons
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+24 -90
View File
@@ -1,100 +1,24 @@
plugins {
// Note: This is only needed for multi-loader projects
// The main architectury loom version is set at the start of the root build.gradle
id "architectury-plugin" version "3.4-SNAPSHOT"
}
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17
architectury {
platformSetupLoomIde()
neoForge()
}
repositories {
maven {
name "Neoforge"
url "https://maven.neoforged.net/releases/"
}
}
//loom {
// forge {
// convertAccessWideners.set(true)
// extraAccessWideners.add("lod.accesswidener")
// mixinConfigs("DistantHorizons.mixins.json")
// }
//}
loom {
silentMojangMappingsLicense() // Shut the licencing warning
accessWidenerPath = project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener")
neoForge {
// Access wideners are now defined in the `remapJar.atAccessWideners`
// convertAccessWideners = true
// extraAccessWideners.add loom.accessWidenerPath.get().asFile.name
unimined.minecraft {
neoForged {
loader neoforge_version
mixinConfig("DistantHorizons.neoforge.mixins.json")
// Mixins are now defined in the `mods.toml`
// mixinConfigs = [
// "DistantHorizons.mixins.json"
// ]
file("build/sourcesSets/main/META-INF/").mkdirs()
accessTransformer(aw2at(
project(":common").file("src/main/resources/${accessWidenerVersion}.distanthorizons.accesswidener"),
file("build/sourcesSets/main/META-INF/accesstransformer.cfg") // We'd wanna output the access transformer to somewhere where it'll only appear in the final jar
))
}
mixin {
// Mixins are now defined in the `mods.toml`
// mixinConfigs = [
// "DistantHorizons.mixins.json"
// ]
}
// "runs" isn't required, but when we do need it then it can be useful
runs {
client {
client()
setConfigName("NeoForge Client")
ideConfigGenerated(true)
runDir("../run")
// vmArgs("-XX:-OmitStackTraceInFastThrow", minecraftMemoryJavaArg)
}
server {
server()
setConfigName("NeoForge Server")
ideConfigGenerated(true)
runDir("../run")
}
}
}
remapJar {
inputFile = shadowJar.archiveFile
dependsOn shadowJar
// classifier null
atAccessWideners.add("distanthorizons.accesswidener")
}
def addMod(path, enabled) {
if (enabled == "2")
dependencies { implementation(path) }
dependencies { modImplementation(path) }
else if (enabled == "1")
dependencies { modCompileOnly(path) }
dependencies { compileOnly(path) }
}
dependencies {
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
mappings loom.layered() {
// Mojmap mappings
officialMojangMappings()
// Parchment mappings (it adds parameter mappings & javadoc)
parchment("org.parchmentmc.data:parchment-${rootProject.parchment_version}@zip")
// Architectury hackishness
// it.mappings "dev.architectury:yarn-mappings-patch-forge:${rootProject.mappings_patch}"
}
// Neoforge
neoForge "net.neoforged:neoforge:${rootProject.neoforge_version}"
// Architectury API
// if (minecraft_version == "1.16.5") {
// implementation("me.shedaniel:architectury-forge:${rootProject.architectury_version}")
@@ -110,6 +34,8 @@ dependencies {
addMod("curse.maven:TerraFirmaCraft-302973:4616004", rootProject.enable_terrafirmacraft)
// annotationProcessor "org.spongepowered:mixin:0.8.5:processor"
// if (System.getProperty("idea.sync.active") != "true") {
// annotationProcessor "org.spongepowered:mixin:0.8.4:processor"
// }
@@ -128,9 +54,17 @@ processResources {
dependsOn(tasks.named('copyAllResources'))
}
tasks.named('runClient') {
dependsOn(tasks.named('copyAllResources'))
finalizedBy(deleteResources)
afterEvaluate {
runClient {
dependsOn(tasks.named('copyAllResources'))
finalizedBy(deleteResources)
}
}
remapJar {
inputFile = shadowJar.archiveFile
dependsOn shadowJar
// classifier null
}
@@ -23,10 +23,9 @@ issueTrackerURL = "${issues}"
#// Allow any version to be present (or not) on the server
acceptableRemoteVersions = "*"
# TODO: Once there is a way to move this to the `META-INF/MANIFEST.MF` with architectury, DO SO!
# (currently, this only works cus neoforge's mods.toml is added to the jar after forge's mods.toml, so this can work
[[mixins]]
config = "DistantHorizons.neoforge.mixins.json"
# We may need this to make forge (lexforge) & neoforge work together
#[[mixins]]
# config = "DistantHorizons.neoforge.mixins.json"
[[dependencies.distanthorizons]]
modId = "minecraft"
+34 -5
View File
@@ -25,6 +25,16 @@ pluginManagement {
name "Sponge"
url "https://repo.spongepowered.org/repository/maven-public/"
}
maven {
name "ParchmentMC"
url "https://maven.parchmentmc.org"
}
maven {
url = "https://maven.wagyourtail.xyz/releases"
}
maven {
url = "https://maven.wagyourtail.xyz/snapshots"
}
mavenCentral()
gradlePluginPortal()
@@ -33,9 +43,29 @@ pluginManagement {
}
}
plugins {
// handles JVM and toolchain downloading
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
// Loads the version.properties
// Throw an error and a little help message if the user forgot to clone the core sub-project
if (!file("./coreSubProjects/LICENSE.txt").exists()) { // the LICENCE.txt file should always, and only exist if the core-sub-project was cloned
println('''
It seems that the core sub project was not included...
please make sure that when you were cloning the repo, you were using the `--recurse-submodules` flag on git.
and if its too late now to re-clone the project, please grab the core sub project in whatever way you can from https://gitlab.com/jeseibel/distant-horizons-core.git
If you still need help with compiling, please read the Readme.md
''')
throw new GradleException("coreSubProject not found")
}
/** Loads the VersionProperties fiel for the currently selected Minecraft version. */
def loadProperties() {
def defaultMcVersion = "1.20.1" // 1.20.1 is our current most stable version so we use that if no version was defined
@@ -72,7 +102,6 @@ def loadProperties() {
gradle.ext.mcVers = mcVers
gradle.ext.mcIndex = mcIndex
}
loadProperties()
@@ -88,9 +117,9 @@ project(":api").projectDir = file('coreSubProjects/api')
include("common")
// Enables or disables the subprojects depending on whats in the versionProperties/mcVer.properties
for (loader in ((String) gradle.builds_for).split(",")) {
def l = loader.strip() // Strip it in case a space is added before or after the comma
println "Adding loader " + l
include(l)
def loaderName = loader.strip() // Strip it in case a space is added before or after the comma
println "Adding loader " + loaderName
include(loaderName)
}
//if (gradle.builds_for.contains("fabric") || gradle.builds_for.contains("quilt"))
// include("fabricLike")
+3 -3
View File
@@ -4,7 +4,7 @@ minecraft_version=1.20.4
parchment_version=1.20.2:2023.12.10
compatible_minecraft_versions=["1.20.3", "1.20.4"]
accessWidenerVersion=1_20_2
builds_for=fabric,forge,neoforge
builds_for=fabric,neoforge,forge
# Fabric loader
fabric_loader_version=0.15.1
@@ -37,8 +37,8 @@ fabric_api_version=0.91.2+1.20.4
enable_canvas=0
# (Neo)Forge loader
forge_version=49.0.16
neoforge_version=20.4.83-beta
forge_version=49.0.30
neoforge_version=118-beta
# (Neo)Forge mod versions
starlight_version_forge=
terraforged_version=