mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 01:04:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bca349d64 | ||
|
|
7a44626d5e | ||
|
|
30ec79b19e | ||
|
|
0794e51f62 | ||
|
|
c86172db31 | ||
|
|
ee14c0c911 | ||
|
|
b3a4ac864f | ||
|
|
f9139a3002 | ||
|
|
784693b7e5 | ||
|
|
774048154f | ||
|
|
490e35ba8e | ||
|
|
667a164430 | ||
|
|
0434d67016 | ||
|
|
02229557ed | ||
|
|
79224a70e6 | ||
|
|
5c9c8ce539 | ||
|
|
c186c38113 | ||
|
|
1507717fae | ||
|
|
12c4233fac | ||
|
|
f331376d9d | ||
|
|
b5b5064a4f | ||
|
|
f4801960e7 | ||
|
|
68c60c17e5 | ||
|
|
4a5f977691 | ||
|
|
896cfb38a3 | ||
|
|
185273022c | ||
|
|
eb65e1db51 | ||
|
|
6a5c413a13 | ||
|
|
1b7b2b6ff1 | ||
|
|
cb290f23f5 | ||
|
|
6e8eaeeec9 | ||
|
|
56c41f3ca8 | ||
|
|
0084b8cf48 | ||
|
|
eb6c564ce4 | ||
|
|
618da273dc | ||
|
|
074c0cdfd6 | ||
|
|
c22589b5b3 | ||
|
|
e0fbab3c49 | ||
|
|
7cfed38194 | ||
|
|
b8105ca5a6 | ||
|
|
c3ee633897 | ||
|
|
23b41ce9b3 | ||
|
|
2c6156be25 | ||
|
|
eeb3eb3689 | ||
|
|
44a158a714 | ||
|
|
770298b1d5 | ||
|
|
7bd50903a6 | ||
|
|
64295d04ff | ||
|
|
f168ce4499 | ||
|
|
419c349a1f | ||
|
|
26d0f7282f | ||
|
|
93c72d2d92 | ||
|
|
f252871322 | ||
|
|
32c8597305 | ||
|
|
9714512c31 | ||
|
|
f106ff0c21 | ||
|
|
9c85a6bb64 | ||
|
|
dc594f196a |
No files matched your search
@@ -9,5 +9,5 @@ dependencies {
|
||||
implementation 'net.minecraftforge:srgutils:0.5.10'
|
||||
implementation 'commons-io:commons-io:2.13.0'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
|
||||
}
|
||||
+1
-1
@@ -27,6 +27,6 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
|
||||
@Internal
|
||||
@Override
|
||||
protected Object getData() {
|
||||
return matches.toSorted(Comparator.naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
|
||||
return matches.toSorted(Comparator.<String>naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -117,7 +117,7 @@ dependencies {
|
||||
bootstrap(libs.unsafe) // Needed by securemodules
|
||||
bootstrap(libs.bundles.asm) // Needed by securemodules
|
||||
|
||||
implementation(libs.jopt.simple) { version { strictly '5.0.4' } }
|
||||
implementation(libs.jopt.simple)
|
||||
|
||||
installer(libs.bootstrap)
|
||||
installer(libs.bootstrap.api) // Needed by securemodules
|
||||
@@ -194,6 +194,7 @@ patcher {
|
||||
|
||||
forge_server_gametest {
|
||||
args '--launchTarget', 'forge_dev_server_gametest'
|
||||
args '--uniqueWorld' // Unique world is used so that the world regenerates, as well as the world config isn't influenced by other runs
|
||||
}
|
||||
|
||||
forge_server_gametest_test {
|
||||
@@ -815,6 +816,9 @@ tasks.named('universalJar').configure {
|
||||
exclude '.cache'
|
||||
|
||||
manifest {
|
||||
attributes([
|
||||
'Automatic-Module-Name': 'net.minecraftforge.forge'
|
||||
] as LinkedHashMap)
|
||||
attributes([
|
||||
'Specification-Title': 'Forge',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
|
||||
@@ -75,3 +75,11 @@ eclipse {
|
||||
// Run when importing the project
|
||||
synchronizationTasks generateResources, copyEclipseSettings, eclipseClasspath, eclipseProject
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
// IntelliJ IDEA does not do this by itself anymore...
|
||||
downloadJavadoc = true
|
||||
downloadSources = true
|
||||
}
|
||||
}
|
||||
+25
-6
@@ -2,16 +2,15 @@
|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[][Download]
|
||||
[][Download]
|
||||
[][Download] [][Discord] [][Patreon]
|
||||
|
||||
Forge is a free, open-source modding API all of your favourite mods use!
|
||||
|
||||
| Version | Support |
|
||||
|---------| ------------- |
|
||||
| 1.20.x | Active |
|
||||
| 1.19.x | Legacy |
|
||||
| Version | Support |
|
||||
|--------------|-----------------------------|
|
||||
| All versions | [Supported][Support-Policy] |
|
||||
|
||||
* [Download]
|
||||
* [Forum]
|
||||
@@ -31,6 +30,19 @@ You can download the installer for the *Recommended Build* or the
|
||||
|
||||
For support and questions, visit [the Support Forum][Support-Forum] or [the Forge Discord server][Discord].
|
||||
|
||||
## Required Java Versions
|
||||
|
||||
We require the version of Java that Minecraft requires, which is currently:
|
||||
|
||||
| Minecraft | Java |
|
||||
|---------------|-------------------|
|
||||
| 1.20.6+ | [21][Adoptium-21] |
|
||||
| 1.18 - 1.20.4 | [17][Adoptium-17] |
|
||||
| 1.17.1 | [16][Adoptium-16] |
|
||||
| 1.16.5- | [8][Adoptium-8] |
|
||||
|
||||
**If you are on Windows and have difficulty using Java to launch JAR files, consider using [Jarfix by Johann Löfflmann][Jarfix].**
|
||||
|
||||
# Creating Mods
|
||||
|
||||
[See the "Getting Started" section in the Forge Documentation][Getting-Started].
|
||||
@@ -63,8 +75,15 @@ Please ensure you have a valid email address associated with your GitHub account
|
||||
|
||||
[Download]: https://files.minecraftforge.net/
|
||||
|
||||
[Adoptium-21]: https://adoptium.net/temurin/releases/?version=21
|
||||
[Adoptium-17]: https://adoptium.net/temurin/releases/?version=17
|
||||
[Adoptium-16]: https://adoptium.net/temurin/releases/?version=16
|
||||
[Adoptium-8]: https://adoptium.net/temurin/releases/?version=8
|
||||
[Jarfix]: https://johann.loefflmann.net/en/software/jarfix/index.html
|
||||
|
||||
[Forum]: https://forums.minecraftforge.net/
|
||||
[Support-Forum]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
|
||||
[Support-Policy]: https://forums.minecraftforge.net/topic/144690-new-tiered-support-policy
|
||||
|
||||
[Discord]: https://discord.minecraftforge.net/
|
||||
|
||||
|
||||
@@ -57,7 +57,9 @@ public class DeferredWorkQueue
|
||||
LOGGER.debug(LOADING, "Dispatching synchronous work for work queue {}: {} jobs", modLoadingStage, tasks.size());
|
||||
RuntimeException aggregate = new RuntimeException();
|
||||
Stopwatch timer = Stopwatch.createStarted();
|
||||
tasks.forEach(t -> makeRunnable(t, Runnable::run, aggregate));
|
||||
for (TaskInfo t : tasks) {
|
||||
makeRunnable(t, Runnable::run, aggregate);
|
||||
}
|
||||
timer.stop();
|
||||
if (aggregate.getSuppressed().length > 0) {
|
||||
LOGGER.fatal(
|
||||
@@ -114,7 +116,7 @@ public class DeferredWorkQueue
|
||||
return future;
|
||||
}
|
||||
|
||||
private static class TaskInfo {
|
||||
private static final class TaskInfo {
|
||||
private final ModContainer owner;
|
||||
private Runnable task;
|
||||
private CompletableFuture<?> future;
|
||||
|
||||
@@ -61,7 +61,7 @@ public class InterModComms
|
||||
}
|
||||
}
|
||||
|
||||
private static ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Send IMC to remote. Sender will default to the active modcontainer, or minecraft if not.
|
||||
@@ -115,16 +115,14 @@ public class InterModComms
|
||||
return getMessages(modId, s->Boolean.TRUE);
|
||||
}
|
||||
|
||||
private static class QueueFilteringSpliterator implements Spliterator<IMCMessage>
|
||||
{
|
||||
private final ConcurrentLinkedQueue<IMCMessage> queue;
|
||||
private final Predicate<String> methodFilter;
|
||||
private final Iterator<IMCMessage> iterator;
|
||||
private record QueueFilteringSpliterator(
|
||||
ConcurrentLinkedQueue<IMCMessage> queue,
|
||||
Predicate<String> methodFilter,
|
||||
Iterator<IMCMessage> iterator
|
||||
) implements Spliterator<IMCMessage> {
|
||||
|
||||
public QueueFilteringSpliterator(final ConcurrentLinkedQueue<IMCMessage> queue, final Predicate<String> methodFilter) {
|
||||
this.queue = queue;
|
||||
this.iterator = queue.iterator();
|
||||
this.methodFilter = methodFilter;
|
||||
this(queue, methodFilter, queue.iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -45,7 +45,7 @@ public abstract class ModContainer
|
||||
protected final IModInfo modInfo;
|
||||
protected ModLoadingStage modLoadingStage;
|
||||
protected Supplier<?> contextExtension;
|
||||
protected final Map<ModLoadingStage, Runnable> activityMap = new HashMap<>();
|
||||
protected final Map<ModLoadingStage, Runnable> activityMap = new EnumMap<>(ModLoadingStage.class);
|
||||
protected final Map<Class<? extends IExtensionPoint<?>>, Supplier<?>> extensionPoints = new IdentityHashMap<>();
|
||||
protected final EnumMap<ModConfig.Type, ModConfig> configs = new EnumMap<>(ModConfig.Type.class);
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
@@ -168,7 +168,10 @@ public abstract class ModContainer
|
||||
}
|
||||
|
||||
public void dispatchConfigEvent(IConfigEvent event) {
|
||||
configHandler.ifPresent(configHandler->configHandler.accept(event));
|
||||
var handler = configHandler.orElse(null);
|
||||
if (handler != null) {
|
||||
handler.accept(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,11 +74,12 @@ public class ModList
|
||||
}
|
||||
|
||||
private String fileToLine(IModFile mf) {
|
||||
var mainMod = mf.getModInfos().getFirst();
|
||||
return String.format(Locale.ENGLISH, "%-50.50s|%-30.30s|%-30.30s|%-20.20s|%-10.10s|Manifest: %s", mf.getFileName(),
|
||||
mf.getModInfos().get(0).getDisplayName(),
|
||||
mf.getModInfos().get(0).getModId(),
|
||||
mf.getModInfos().get(0).getVersion(),
|
||||
getModContainerState(mf.getModInfos().get(0).getModId()),
|
||||
mainMod.getDisplayName(),
|
||||
mainMod.getModId(),
|
||||
mainMod.getVersion(),
|
||||
getModContainerState(mainMod.getModId()),
|
||||
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
|
||||
}
|
||||
private String crashReport() {
|
||||
@@ -95,14 +96,6 @@ public class ModList
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
|
||||
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
|
||||
thread.setName("modloading-worker-" + thread.getPoolIndex());
|
||||
// The default sets it to the SystemClassloader, so copy the current one.
|
||||
thread.setContextClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
return thread;
|
||||
}
|
||||
|
||||
public List<IModFileInfo> getModFiles()
|
||||
{
|
||||
return modFiles;
|
||||
@@ -131,16 +124,18 @@ public class ModList
|
||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||
final RuntimeException accumulator = new RuntimeException();
|
||||
cf.completeExceptionally(accumulator);
|
||||
throwables.forEach(exception -> {
|
||||
for (Throwable exception : throwables) {
|
||||
if (exception instanceof CompletionException) {
|
||||
exception = exception.getCause();
|
||||
}
|
||||
if (exception.getSuppressed().length!=0) {
|
||||
Arrays.stream(exception.getSuppressed()).forEach(accumulator::addSuppressed);
|
||||
if (exception.getSuppressed().length != 0) {
|
||||
for (Throwable throwable : exception.getSuppressed()) {
|
||||
accumulator.addSuppressed(throwable);
|
||||
}
|
||||
} else {
|
||||
accumulator.addSuppressed(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
return cf;
|
||||
}
|
||||
}
|
||||
@@ -148,11 +143,11 @@ public class ModList
|
||||
static <V> CompletableFuture<List<Map.Entry<V, Throwable>>> gather(List<? extends CompletableFuture<? extends V>> futures) {
|
||||
List<Map.Entry<V, Throwable>> list = new ArrayList<>(futures.size());
|
||||
CompletableFuture<?>[] results = new CompletableFuture[futures.size()];
|
||||
futures.forEach(future -> {
|
||||
for (var future : futures) {
|
||||
int i = list.size();
|
||||
list.add(null);
|
||||
results[i] = future.whenComplete((result, exception) -> list.set(i, new AbstractMap.SimpleImmutableEntry<>(result, exception)));
|
||||
});
|
||||
}
|
||||
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,13 +77,13 @@ import static net.minecraftforge.fml.Logging.LOADING;
|
||||
public class ModLoader
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static ModLoader INSTANCE;
|
||||
private final LoadingModList loadingModList;
|
||||
|
||||
private final List<ModLoadingException> loadingExceptions;
|
||||
private final Set<IModInfo> erroredModInfos;
|
||||
private final List<ModLoadingWarning> loadingWarnings;
|
||||
private final ModStateManager stateManager;
|
||||
private boolean loadingStateValid;
|
||||
private static boolean loadingStateValid;
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
|
||||
private final Set<IModLoadingState> completedStates = new HashSet<>();
|
||||
@@ -91,7 +91,6 @@ public class ModLoader
|
||||
|
||||
private ModLoader()
|
||||
{
|
||||
INSTANCE = this;
|
||||
this.loadingModList = FMLLoader.getLoadingModList();
|
||||
this.loadingExceptions = this.loadingModList.getErrors().stream()
|
||||
.flatMap(ModLoadingException::fromEarlyException)
|
||||
@@ -99,24 +98,32 @@ public class ModLoader
|
||||
this.loadingWarnings = this.loadingModList.getBrokenFiles().stream()
|
||||
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
|
||||
.collect(Collectors.toList());
|
||||
if (this.loadingExceptions.isEmpty()) {
|
||||
this.erroredModInfos = Collections.emptySet();
|
||||
} else {
|
||||
this.erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
this.erroredModInfos.addAll(this.loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
|
||||
}
|
||||
this.loadingModList.getModFiles().stream()
|
||||
.filter(ModFileInfo::missingLicense)
|
||||
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> this.loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error
|
||||
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(this.erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
|
||||
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
|
||||
.forEach(this.loadingExceptions::add);
|
||||
this.stateManager = new ModStateManager();
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher services", this::computeModLauncherServiceList);
|
||||
CrashReportCallables.registerCrashCallable("FML Language Providers", this::computeLanguageList);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList);
|
||||
CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::computeLanguageList);
|
||||
}
|
||||
|
||||
private String computeLanguageList() {
|
||||
return "\n"+FMLLoader.getLanguageLoadingProvider().applyForEach(lp->lp.name() +"@"+ lp.getClass().getPackage().getImplementationVersion()).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
private static String computeLanguageList() {
|
||||
return "\n" + FMLLoader.getLanguageLoadingProvider()
|
||||
.applyForEach(lp -> lp.name() + "@" + lp.getClass().getPackage().getImplementationVersion())
|
||||
.collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
}
|
||||
|
||||
private String computeModLauncherServiceList() {
|
||||
private static String computeModLauncherServiceList() {
|
||||
final List<Map<String, String>> mods = FMLLoader.modLauncherModList();
|
||||
return "\n"+mods.stream().map(mod->mod.getOrDefault("file","nofile")+
|
||||
" "+mod.getOrDefault("name", "missing")+
|
||||
@@ -125,9 +132,13 @@ public class ModLoader
|
||||
collect(Collectors.joining("\n\t\t","\t\t",""));
|
||||
}
|
||||
|
||||
public static ModLoader get()
|
||||
{
|
||||
return INSTANCE == null ? INSTANCE = new ModLoader() : INSTANCE;
|
||||
public static ModLoader get() {
|
||||
return LazyInit.INSTANCE;
|
||||
}
|
||||
|
||||
private static final class LazyInit {
|
||||
private static final ModLoader INSTANCE = new ModLoader();
|
||||
private LazyInit() {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,7 +155,7 @@ public class ModLoader
|
||||
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(),
|
||||
loadingModList.getMods());
|
||||
if (!this.loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0));
|
||||
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.getFirst());
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
@@ -152,7 +163,7 @@ public class ModLoader
|
||||
}
|
||||
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
|
||||
.map(ModInfo::getForgeFeatures)
|
||||
.flatMap(Collection::stream)
|
||||
.flatMap(List::stream)
|
||||
.filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound))
|
||||
.toList();
|
||||
|
||||
@@ -169,10 +180,10 @@ public class ModLoader
|
||||
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.map(this::buildMods)
|
||||
.<ModContainer>mapMulti(Iterable::forEach)
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
if (!loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0));
|
||||
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.getFirst());
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
@@ -181,22 +192,28 @@ public class ModLoader
|
||||
modList.setLoadedMods(modContainers);
|
||||
this.modList = modList;
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
|
||||
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(this.modList)).sum());
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.LOAD);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
|
||||
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.COMPLETE);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
|
||||
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size())));
|
||||
progress.complete();
|
||||
}
|
||||
@@ -206,10 +223,15 @@ public class ModLoader
|
||||
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
|
||||
return;
|
||||
}
|
||||
progressBar.label(progressBar.name()+ " working");
|
||||
progressBar.label(progressBar.name() + " working");
|
||||
syncExecutor.drive(ticker);
|
||||
state.inlineRunnable().ifPresent(a->this.handleInlineTransition(a, state, syncExecutor, ticker));
|
||||
state.buildTransition(syncExecutor, parallelExecutor, progressBar).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
|
||||
|
||||
var inlineRunnable = state.inlineRunnable().orElse(null);
|
||||
if (inlineRunnable != null) handleInlineTransition(inlineRunnable, state, syncExecutor, ticker);
|
||||
|
||||
var transition = state.buildTransition(syncExecutor, parallelExecutor, progressBar).orElse(null);
|
||||
if (transition != null) waitForTransition(state, syncExecutor, ticker, transition);
|
||||
|
||||
completedStates.add(state);
|
||||
}
|
||||
|
||||
@@ -286,9 +308,12 @@ public class ModLoader
|
||||
try {
|
||||
final String modId = idToProviderEntry.getKey();
|
||||
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
|
||||
IModInfo info = Optional.ofNullable(modInfoMap.get(modId)).
|
||||
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
|
||||
orElseThrow(()->new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId));
|
||||
|
||||
IModInfo info = modInfoMap.get(modId);
|
||||
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
|
||||
if (info == null)
|
||||
throw new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId);
|
||||
|
||||
return languageLoader.loadMod(info, modFile.getScanResult(), FMLLoader.getGameLayer());
|
||||
} catch (ModLoadingException mle) {
|
||||
// exceptions are caught and added to the error list for later handling
|
||||
@@ -303,7 +328,7 @@ public class ModLoader
|
||||
* and don't want to cause extraneous crashes due to trying to do things that aren't possible in a "broken load"
|
||||
*/
|
||||
public static boolean isLoadingStateValid() {
|
||||
return get().loadingStateValid;
|
||||
return loadingStateValid;
|
||||
}
|
||||
|
||||
public boolean hasCompletedState(final String stateName) {
|
||||
|
||||
@@ -19,28 +19,51 @@ public class ModLoadingContext
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
|
||||
private ModContainer activeContainer;
|
||||
private Object languageExtension;
|
||||
private ModLoadingStage stage;
|
||||
|
||||
/**
|
||||
* @deprecated Use the context provided by your language loader in your mod's constructor
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
public static ModLoadingContext get() {
|
||||
return context.get();
|
||||
}
|
||||
|
||||
private ModContainer activeContainer;
|
||||
|
||||
/**
|
||||
* @deprecated Going to be moved to ForgeHooks for Internal use.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
public void setActiveContainer(final ModContainer container) {
|
||||
this.activeContainer = container;
|
||||
this.languageExtension = container == null ? null : container.contextExtension.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Going to be moved to ForgeHooks for Internal use.
|
||||
* @deprecated Override/Use {@link ModLoadingContext#getContainer()}
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
public ModContainer getActiveContainer() {
|
||||
return activeContainer == null ? ModList.get().getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!")) : activeContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Going to be moved to ForgeHooks for Internal use.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
public String getActiveNamespace() {
|
||||
return activeContainer == null ? "minecraft" : activeContainer.getNamespace();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link ModLoadingContext#getActiveContainer()} by default.
|
||||
*/
|
||||
public ModContainer getContainer() {
|
||||
return getActiveContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an {@link IExtensionPoint} with the mod container.
|
||||
* @param point The extension point to register
|
||||
@@ -48,7 +71,7 @@ public class ModLoadingContext
|
||||
* @param <T> The type signature of the extension operator
|
||||
*/
|
||||
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension) {
|
||||
getActiveContainer().registerExtensionPoint(point, extension);
|
||||
getContainer().registerExtensionPoint(point, extension);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +80,7 @@ public class ModLoadingContext
|
||||
* @param displayTest The {@link IExtensionPoint.DisplayTest} to register
|
||||
*/
|
||||
public void registerDisplayTest(IExtensionPoint.DisplayTest displayTest) {
|
||||
getActiveContainer().registerDisplayTest(() -> displayTest);
|
||||
getContainer().registerDisplayTest(() -> displayTest);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +89,7 @@ public class ModLoadingContext
|
||||
* @param displayTest The {@link Supplier<IExtensionPoint.DisplayTest>} to register
|
||||
*/
|
||||
public void registerDisplayTest(Supplier<IExtensionPoint.DisplayTest> displayTest) {
|
||||
getActiveContainer().registerDisplayTest(displayTest);
|
||||
getContainer().registerDisplayTest(displayTest);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +99,7 @@ public class ModLoadingContext
|
||||
* @see IExtensionPoint.DisplayTest#DisplayTest(String, BiPredicate)
|
||||
*/
|
||||
public void registerDisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
|
||||
getActiveContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
|
||||
getContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,29 +109,29 @@ public class ModLoadingContext
|
||||
* @see IExtensionPoint.DisplayTest#DisplayTest(Supplier, BiPredicate)
|
||||
*/
|
||||
public void registerDisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) {
|
||||
getActiveContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
|
||||
getContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
|
||||
}
|
||||
|
||||
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec) {
|
||||
if (spec.isEmpty())
|
||||
{
|
||||
// This handles the case where a mod tries to register a config, without any options configured inside it.
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getActiveContainer().getModId());
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getContainer().getModId());
|
||||
return;
|
||||
}
|
||||
|
||||
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer()));
|
||||
getContainer().addConfig(new ModConfig(type, spec, getContainer()));
|
||||
}
|
||||
|
||||
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec, String fileName) {
|
||||
if (spec.isEmpty())
|
||||
{
|
||||
// This handles the case where a mod tries to register a config, without any options configured inside it.
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getActiveContainer().getModId(), fileName);
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getContainer().getModId(), fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer(), fileName));
|
||||
getContainer().addConfig(new ModConfig(type, spec, getContainer(), fileName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,10 @@ public record ModLoadingState(String name, String previous,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
return transition.map(t -> t.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
|
||||
var transition = this.transition.orElse(null);
|
||||
return transition == null
|
||||
? Optional.empty()
|
||||
: Optional.ofNullable(transition.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,15 +16,13 @@ import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class ModStateManager {
|
||||
static ModStateManager INSTANCE;
|
||||
private final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
|
||||
|
||||
public ModStateManager() {
|
||||
INSTANCE = this;
|
||||
final var sp = ServiceLoader.load(FMLLoader.getGameLayer(), IModStateProvider.class);
|
||||
this.stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, sce->{})
|
||||
.map(IModStateProvider::getAllStates)
|
||||
.<IModLoadingState>mapMulti(Iterable::forEach)
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.groupingBy(IModLoadingState::phase, ()->new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
|
||||
}
|
||||
|
||||
@@ -36,14 +34,16 @@ public class ModStateManager {
|
||||
var dummy = ModLoadingState.empty("", "", phase);
|
||||
nodes.forEach(graph::addNode);
|
||||
graph.addNode(dummy);
|
||||
nodes.forEach(n->graph.putEdge(lookup.getOrDefault(n.previous(), dummy), n));
|
||||
for (IModLoadingState node : nodes) {
|
||||
graph.putEdge(lookup.getOrDefault(node.previous(), dummy), node);
|
||||
}
|
||||
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
|
||||
}
|
||||
|
||||
public IModLoadingState findState(final String stateName) {
|
||||
return stateMap.values()
|
||||
.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.flatMap(List::stream)
|
||||
.filter(mls -> mls.name().equals(stateName))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
|
||||
|
||||
@@ -17,6 +17,7 @@ import static net.minecraftforge.fml.Logging.LOADING;
|
||||
public class ModWorkManager {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final long PARK_TIME = TimeUnit.MILLISECONDS.toNanos(1);
|
||||
|
||||
public interface DrivenExecutor extends Executor {
|
||||
boolean selfDriven();
|
||||
boolean driveOne();
|
||||
@@ -33,8 +34,11 @@ public class ModWorkManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
private static class SyncExecutor implements DrivenExecutor {
|
||||
private ConcurrentLinkedDeque<Runnable> tasks = new ConcurrentLinkedDeque<>();
|
||||
|
||||
private record SyncExecutor(ConcurrentLinkedDeque<Runnable> tasks) implements DrivenExecutor {
|
||||
public SyncExecutor() {
|
||||
this(new ConcurrentLinkedDeque<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean driveOne() {
|
||||
@@ -56,13 +60,7 @@ public class ModWorkManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static class WrappingExecutor implements DrivenExecutor {
|
||||
private final Executor wrapped;
|
||||
|
||||
public WrappingExecutor(final Executor executor) {
|
||||
this.wrapped = executor;
|
||||
}
|
||||
|
||||
private record WrappingExecutor(Executor wrapped) implements DrivenExecutor {
|
||||
@Override
|
||||
public boolean selfDriven() {
|
||||
return true;
|
||||
@@ -79,11 +77,9 @@ public class ModWorkManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static SyncExecutor syncExecutor;
|
||||
private static final SyncExecutor syncExecutor = new SyncExecutor();
|
||||
|
||||
public static DrivenExecutor syncExecutor() {
|
||||
if (syncExecutor == null)
|
||||
syncExecutor = new SyncExecutor();
|
||||
return syncExecutor;
|
||||
}
|
||||
|
||||
@@ -91,14 +87,8 @@ public class ModWorkManager {
|
||||
return new WrappingExecutor(executor);
|
||||
}
|
||||
|
||||
private static ForkJoinPool parallelThreadPool;
|
||||
public static Executor parallelExecutor() {
|
||||
if (parallelThreadPool == null) {
|
||||
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
|
||||
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
|
||||
parallelThreadPool = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
|
||||
}
|
||||
return parallelThreadPool;
|
||||
return LazyInit.PARALLEL_EXECUTOR;
|
||||
}
|
||||
|
||||
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
|
||||
@@ -109,4 +99,14 @@ public class ModWorkManager {
|
||||
return thread;
|
||||
}
|
||||
|
||||
private static final class LazyInit {
|
||||
private LazyInit() {}
|
||||
private static final ForkJoinPool PARALLEL_EXECUTOR;
|
||||
|
||||
static {
|
||||
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
|
||||
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
|
||||
PARALLEL_EXECUTOR = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public final class OptionalMod<T>
|
||||
return new OptionalMod<>(modId);
|
||||
}
|
||||
|
||||
private static OptionalMod<?> EMPTY = new OptionalMod<>(true);
|
||||
private static final OptionalMod<?> EMPTY = new OptionalMod<>(true);
|
||||
private static <T> OptionalMod<T> empty() {
|
||||
@SuppressWarnings("unchecked")
|
||||
OptionalMod<T> t = (OptionalMod<T>) EMPTY;
|
||||
@@ -213,8 +213,8 @@ public final class OptionalMod<T>
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj instanceof OptionalMod) {
|
||||
return Objects.equals(((OptionalMod)obj).modId, modId);
|
||||
if (obj instanceof OptionalMod<?> optionalMod) {
|
||||
return Objects.equals(optionalMod.modId, modId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -25,9 +26,66 @@ import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
|
||||
|
||||
public class ConfigFileTypeHandler {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
|
||||
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.getConfigValue(FMLConfig.ConfigValue.DEFAULT_CONFIG_PATH));
|
||||
|
||||
private static final ConfigFileTypeHandler CLIENT = new ConfigFileTypeHandler(ModConfig.Type.CLIENT);
|
||||
private static final ConfigFileTypeHandler COMMON = new ConfigFileTypeHandler(ModConfig.Type.COMMON);
|
||||
private static final ConfigFileTypeHandler SERVER = new ConfigFileTypeHandler(ModConfig.Type.SERVER);
|
||||
|
||||
private final @Nullable ModConfig.Type type;
|
||||
private @Nullable FileWatcher watcher;
|
||||
|
||||
// exists for bin compat
|
||||
public ConfigFileTypeHandler() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
private ConfigFileTypeHandler(@Nullable ModConfig.Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the handler for the given {@link ModConfig.Type}.
|
||||
*
|
||||
* @param type The type to get the handler for
|
||||
* @return The handler
|
||||
*/
|
||||
static ConfigFileTypeHandler get(ModConfig.Type type) {
|
||||
return switch (type) {
|
||||
case CLIENT -> CLIENT;
|
||||
case COMMON -> COMMON;
|
||||
case SERVER -> SERVER;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link FileWatcher} for this handler, creating it if it doesn't exist and/or has been stopped.
|
||||
*
|
||||
* @return The watcher
|
||||
*
|
||||
* @apiNote This is package-private so modders can't just call {@link FileWatcher#stop()} and fuck up everything.
|
||||
*/
|
||||
FileWatcher getWatcher() {
|
||||
if (this.watcher == null) {
|
||||
LOGGER.debug(CONFIG, "Starting watcher for handler: {}", this);
|
||||
this.watcher = new FileWatcher();
|
||||
}
|
||||
|
||||
return this.watcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the {@link FileWatcher} for this handler, and sets it to null afterward. Use this instead of
|
||||
* {@link FileWatcher#stop()}.
|
||||
*/
|
||||
void stopWatcher() {
|
||||
if (this.watcher == null) return;
|
||||
|
||||
LOGGER.debug(CONFIG, "Stopping watcher for hander: {}", this);
|
||||
this.watcher.stop();
|
||||
this.watcher = null;
|
||||
}
|
||||
|
||||
public Function<ModConfig, CommentedFileConfig> reader(Path configBasePath) {
|
||||
return (c) -> {
|
||||
final Path configPath = configBasePath.resolve(c.getFileName());
|
||||
@@ -37,7 +95,7 @@ public class ConfigFileTypeHandler {
|
||||
onFileNotFound((newfile, configFormat)-> setupConfigFile(c, newfile, configFormat)).
|
||||
writingMode(WritingMode.REPLACE).
|
||||
build();
|
||||
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath.toString());
|
||||
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath);
|
||||
try
|
||||
{
|
||||
configData.load();
|
||||
@@ -46,9 +104,9 @@ public class ConfigFileTypeHandler {
|
||||
{
|
||||
throw new ConfigLoadingException(c, ex);
|
||||
}
|
||||
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath.toString());
|
||||
FileWatcher.defaultInstance().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
|
||||
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath.toString());
|
||||
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath);
|
||||
this.getWatcher().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
|
||||
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath);
|
||||
return configData;
|
||||
};
|
||||
}
|
||||
@@ -56,13 +114,13 @@ public class ConfigFileTypeHandler {
|
||||
public void unload(Path configBasePath, ModConfig config) {
|
||||
Path configPath = configBasePath.resolve(config.getFileName());
|
||||
try {
|
||||
FileWatcher.defaultInstance().removeWatch(configBasePath.resolve(config.getFileName()));
|
||||
this.getWatcher().removeWatch(configPath);
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.error("Failed to remove config {} from tracker!", configPath.toString(), e);
|
||||
LOGGER.error("Failed to remove config {} from tracker!", configPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
|
||||
private static boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
|
||||
if (!Files.isDirectory(file.getParent())) {
|
||||
Files.createDirectories(file.getParent());
|
||||
}
|
||||
@@ -109,17 +167,16 @@ public class ConfigFileTypeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConfigWatcher implements Runnable {
|
||||
private final ModConfig modConfig;
|
||||
private final CommentedFileConfig commentedFileConfig;
|
||||
private final ClassLoader realClassLoader;
|
||||
|
||||
ConfigWatcher(final ModConfig modConfig, final CommentedFileConfig commentedFileConfig, final ClassLoader classLoader) {
|
||||
this.modConfig = modConfig;
|
||||
this.commentedFileConfig = commentedFileConfig;
|
||||
this.realClassLoader = classLoader;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigFileTypeHandler[" + (type != null ? type : "UNKNOWN") + "]";
|
||||
}
|
||||
|
||||
private record ConfigWatcher(
|
||||
ModConfig modConfig,
|
||||
CommentedFileConfig commentedFileConfig,
|
||||
ClassLoader realClassLoader
|
||||
) implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
// Force the regular classloader onto the special thread
|
||||
|
||||
@@ -8,6 +8,9 @@ package net.minecraftforge.fml.config;
|
||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
@@ -47,15 +50,32 @@ public class ConfigTracker {
|
||||
|
||||
public void loadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
LOGGER.debug(CONFIG, "Loading configs type {}", type);
|
||||
this.configSets.get(type).forEach(config -> openConfig(config, configBasePath));
|
||||
for (ModConfig config : this.configSets.get(type)) {
|
||||
openConfig(config, configBasePath);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [FML] This is only called for the server (outside of forceUnload)
|
||||
// rethink config implementation for eventual FML rewrite
|
||||
public void unloadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
LOGGER.debug(CONFIG, "Unloading configs type {}", type);
|
||||
this.configSets.get(type).forEach(config -> closeConfig(config, configBasePath));
|
||||
for (ModConfig config : this.configSets.get(type)) {
|
||||
closeConfig(config, configBasePath);
|
||||
}
|
||||
ConfigFileTypeHandler.get(type).stopWatcher();
|
||||
}
|
||||
|
||||
private void openConfig(final ModConfig config, final Path configBasePath) {
|
||||
// If there is a better way to do this, please tell me. Because this is FUCKED
|
||||
public void forceUnload() {
|
||||
// This is how ModStateProvider handles loading configs. So we're doing the same but with unloadConfigs instead
|
||||
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> this.unloadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get()));
|
||||
this.unloadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get());
|
||||
|
||||
// just in case server watcher is still alive somehow...
|
||||
ConfigFileTypeHandler.get(ModConfig.Type.SERVER).stopWatcher();
|
||||
}
|
||||
|
||||
private static void openConfig(final ModConfig config, final Path configBasePath) {
|
||||
LOGGER.trace(CONFIG, "Loading config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
|
||||
final CommentedFileConfig configData = config.getHandler().reader(configBasePath).apply(config);
|
||||
config.setConfigData(configData);
|
||||
@@ -63,7 +83,7 @@ public class ConfigTracker {
|
||||
config.save();
|
||||
}
|
||||
|
||||
private void closeConfig(final ModConfig config, final Path configBasePath) {
|
||||
private static void closeConfig(final ModConfig config, final Path configBasePath) {
|
||||
if (config.getConfigData() != null) {
|
||||
LOGGER.trace(CONFIG, "Closing config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
|
||||
// stop the filewatcher before we save the file and close it, so reload doesn't fire
|
||||
|
||||
@@ -24,14 +24,13 @@ public class ModConfig
|
||||
private final ModContainer container;
|
||||
private final ConfigFileTypeHandler configHandler;
|
||||
private CommentedConfig configData;
|
||||
private Callable<Void> saveHandler;
|
||||
|
||||
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer container, final String fileName) {
|
||||
this.type = type;
|
||||
this.spec = spec;
|
||||
this.fileName = fileName;
|
||||
this.container = container;
|
||||
this.configHandler = ConfigFileTypeHandler.TOML;
|
||||
this.configHandler = ConfigFileTypeHandler.get(type);
|
||||
ConfigTracker.INSTANCE.trackConfig(this);
|
||||
}
|
||||
|
||||
|
||||
+21
-15
@@ -22,9 +22,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.URI;
|
||||
@@ -302,13 +300,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
}
|
||||
|
||||
private void crashElegantly(String errorDetails) {
|
||||
String qrText;
|
||||
try (var is = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/glfailure.txt")))) {
|
||||
qrText = is.lines().collect(Collectors.joining("\n"));
|
||||
} catch (IOException ioe) {
|
||||
qrText = "";
|
||||
}
|
||||
|
||||
StringBuilder msgBuilder = new StringBuilder(2000);
|
||||
msgBuilder.append("Failed to initialize graphics window with current settings.\n");
|
||||
msgBuilder.append("\n\n");
|
||||
@@ -407,13 +398,13 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
if (showHelpLog && versidx == 0) {
|
||||
LOGGER.info("""
|
||||
If this message is the only thing at the bottom of your log before a crash, you probably have a driver issue.
|
||||
|
||||
|
||||
Possible solutions:
|
||||
A) Make sure Minecraft is set to prefer high performance graphics in the OS and/or driver control panel
|
||||
B) Check for driver updates on the graphics brand's website
|
||||
C) Try reinstalling your graphics drivers
|
||||
D) If still not working after trying all of the above, ask for further help on the Forge forums or Discord
|
||||
|
||||
|
||||
You can safely ignore this message if the game starts up successfully.""");
|
||||
}
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSIONS[versidx][0]); // we try our versions one at a time
|
||||
@@ -612,10 +603,25 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
|
||||
@Override
|
||||
public void updateModuleReads(final ModuleLayer layer) {
|
||||
var fm = layer.findModule("forge").orElseThrow();
|
||||
getClass().getModule().addReads(fm);
|
||||
var clz = FMLLoader.getGameLayer().findModule("forge").map(l->Class.forName(l, "net.minecraftforge.client.loading.ForgeLoadingOverlay")).orElseThrow();
|
||||
loadingOverlay = Arrays.stream(clz.getDeclaredMethods()).filter(m-> Modifier.isStatic(m.getModifiers()) && m.getName().equals("newInstance")).findFirst().orElseThrow();
|
||||
final var FORGE_MODULE = "net.minecraftforge.forge";
|
||||
var forge_module = layer.findModule(FORGE_MODULE).orElse(null);
|
||||
if (forge_module == null)
|
||||
throw new IllegalStateException("Could not find " + FORGE_MODULE + " in " + layer);
|
||||
|
||||
getClass().getModule().addReads(forge_module);
|
||||
|
||||
|
||||
var clz = Class.forName(forge_module, "net.minecraftforge.client.loading.ForgeLoadingOverlay");
|
||||
|
||||
for (var mtd : clz.getDeclaredMethods()) {
|
||||
if (Modifier.isStatic(mtd.getModifiers()) && "newInstance".equals(mtd.getName())) {
|
||||
this.loadingOverlay = mtd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (loadingOverlay == null)
|
||||
throw new IllegalStateException("Could not find static newInstace method in " + clz.getName());
|
||||
}
|
||||
|
||||
public int getFramebufferTextureId() {
|
||||
|
||||
+4
-5
@@ -13,10 +13,10 @@ import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.MemoryUsage;
|
||||
|
||||
public class PerformanceInfo {
|
||||
private static final MemoryMXBean MEMORY_BEAN = ManagementFactory.getMemoryMXBean();
|
||||
|
||||
private final boolean showCPUUsage;
|
||||
private final OperatingSystemMXBean osBean;
|
||||
private final MemoryMXBean memoryBean;
|
||||
|
||||
private float memory;
|
||||
private String text;
|
||||
@@ -24,15 +24,14 @@ public class PerformanceInfo {
|
||||
PerformanceInfo() {
|
||||
showCPUUsage = FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SHOW_CPU);
|
||||
osBean = showCPUUsage ? ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class) : null;
|
||||
memoryBean = ManagementFactory.getMemoryMXBean();
|
||||
}
|
||||
|
||||
void update() {
|
||||
final MemoryUsage heapusage = memoryBean.getHeapMemoryUsage();
|
||||
final MemoryUsage heapusage = MEMORY_BEAN.getHeapMemoryUsage();
|
||||
memory = (float) heapusage.getUsed() / heapusage.getMax();
|
||||
|
||||
if (!showCPUUsage) {
|
||||
text = "Heap: %d/%d MB (%.1f%%) OffHeap: %d MB".formatted(heapusage.getUsed() >> 20, heapusage.getMax() >> 20, memory * 100.0, memoryBean.getNonHeapMemoryUsage().getUsed() >> 20);
|
||||
text = "Memory: %d/%dMB (%.1f%%)".formatted(heapusage.getUsed() >>> 20, heapusage.getMax() >>> 20, memory * 100f);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,7 +43,7 @@ public class PerformanceInfo {
|
||||
cpuText = "CPU: %.1f%%".formatted(cpuLoad * 100f);
|
||||
}
|
||||
|
||||
text = "Heap: %d/%d MB (%.1f%%) OffHeap: %d MB %s".formatted(heapusage.getUsed() >> 20, heapusage.getMax() >> 20, memory * 100.0, memoryBean.getNonHeapMemoryUsage().getUsed() >> 20, cpuText);
|
||||
text = "Memory: %d/%dMB (%.1f%%) %s".formatted(heapusage.getUsed() >>> 20, heapusage.getMax() >>> 20, memory * 100f, cpuText);
|
||||
}
|
||||
|
||||
String text() {
|
||||
|
||||
@@ -25,7 +25,7 @@ public class SimpleFont {
|
||||
private final int lineSpacing;
|
||||
private final int descent;
|
||||
private final int GLYPH_COUNT = 127-32;
|
||||
private Glyph[] glyphs;
|
||||
private final Glyph[] glyphs;
|
||||
|
||||
private record Glyph(char c, int charwidth, int[] pos, float[] uv) {
|
||||
Pos loadQuad(Pos pos, int colour, SimpleBufferBuilder bb) {
|
||||
|
||||
+13
-6
@@ -36,9 +36,9 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
*/
|
||||
public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
|
||||
private final String FUNC_NAME = "getType";
|
||||
private final String FUNC_DESC = "()Ljava/lang/String;";
|
||||
private final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
|
||||
private static final String FUNC_NAME = "getType";
|
||||
private static final String FUNC_DESC = "()Ljava/lang/String;";
|
||||
private static final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
@@ -50,7 +50,14 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
return isEmpty ? NAY : YAY;
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,7 +74,7 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
|
||||
SignatureReader reader = new SignatureReader(classNode.signature); // Having a node version of this would probably be useful.
|
||||
reader.accept(new SignatureVisitor(Opcodes.ASM9) {
|
||||
Deque<String> stack = new ArrayDeque<>();
|
||||
final Deque<String> stack = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void visitClassType(final String name) {
|
||||
@@ -100,7 +107,7 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Holder {
|
||||
private static final class Holder {
|
||||
String value;
|
||||
}
|
||||
}
|
||||
+30
-10
@@ -8,6 +8,7 @@ package net.minecraftforge.fml.common.asm;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -34,8 +35,12 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
new VanillaObjectHolderData("net.minecraft.world.effect.MobEffects", "mob_effect", "net.minecraft.world.effect.MobEffect"),
|
||||
new VanillaObjectHolderData("net.minecraft.core.particles.ParticleTypes", "particle_type", "net.minecraft.core.particles.ParticleType"),
|
||||
new VanillaObjectHolderData("net.minecraft.sounds.SoundEvents", "sound_event", "net.minecraft.sounds.SoundEvent")
|
||||
).collect(Collectors.toMap(VanillaObjectHolderData::holderClass, Function.identity()));
|
||||
private final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
|
||||
).collect(Collectors.toUnmodifiableMap(VanillaObjectHolderData::holderClass, Function.identity()));
|
||||
private static final Set<String> VANILLA_OBJECT_HOLDER_CLASSES = VANILLA_OBJECT_HOLDERS.keySet().stream()
|
||||
.map(it -> it.replace('.', '/'))
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
private static final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
|
||||
private static final int PUBLIC_STATIC_FINAL_FLAGS = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
@@ -46,13 +51,29 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
|
||||
{
|
||||
return isEmpty ? NAY : YAY;
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
// Let's skip processing classes that definitely don't have object holders
|
||||
String internalName = classType.getInternalName();
|
||||
|
||||
// Forge classes that aren't in the debug package (that package is used for tests)
|
||||
if (internalName.startsWith("net/minecraftforge/") && !internalName.substring(18).contains("debug"))
|
||||
return NAY;
|
||||
|
||||
// Vanilla classes that don't have object holders
|
||||
if (internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
|
||||
// ...except specific ones we added to the VANILLA_OBJECT_HOLDERS map
|
||||
if (internalName.startsWith("net/minecraft/") && !VANILLA_OBJECT_HOLDER_CLASSES.contains(internalName))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
private boolean hasHolder(List<AnnotationNode> lst)
|
||||
{
|
||||
private static boolean hasHolder(List<AnnotationNode> lst) {
|
||||
return lst != null && lst.stream().anyMatch(n -> n.desc.equals(OBJECT_HOLDER));
|
||||
}
|
||||
|
||||
@@ -76,10 +97,9 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
{
|
||||
final AtomicBoolean changes = new AtomicBoolean();
|
||||
//Must be public static finals, and non-array objects
|
||||
final int flags = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
|
||||
|
||||
//Fix Annotated Fields before injecting from class level
|
||||
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
|
||||
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
|
||||
{
|
||||
int prev = f.access;
|
||||
f.access &= ~Opcodes.ACC_FINAL; //Strip final
|
||||
@@ -89,7 +109,7 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
|
||||
if (VANILLA_OBJECT_HOLDERS.containsKey(classType.getClassName())) //Class level, de-finalize all fields and add @ObjectHolder to them!
|
||||
{
|
||||
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L")).forEach(f ->
|
||||
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L")).forEach(f ->
|
||||
{
|
||||
int prev = f.access;
|
||||
f.access &= ~Opcodes.ACC_FINAL;
|
||||
|
||||
+29
-24
@@ -28,15 +28,16 @@ import org.slf4j.Logger;
|
||||
public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final Type STRING = Type.getType(String.class);
|
||||
private final Type ENUM = Type.getType(Enum.class);
|
||||
private final Type MARKER_IFACE = Type.getType("Lnet/minecraftforge/common/IExtensibleEnum;");
|
||||
private final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); //Don't directly reference this to prevent class loading.
|
||||
private final String ADD_DESC = Type.getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class));
|
||||
private final Type UNSAFE_HACKS = Type.getType("Lnet/minecraftforge/fml/unsafe/UnsafeHacks;"); //Again, not direct reference to prevent class loading.
|
||||
private final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class));
|
||||
private final String NAME_DESC = Type.getMethodDescriptor(STRING);
|
||||
private final String EQUALS_DESC = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, STRING);
|
||||
private static final Type STRING = Type.getType(String.class);
|
||||
private static final Type ENUM = Type.getType(Enum.class);
|
||||
private static final Type MARKER_IFACE = Type.getType("Lnet/minecraftforge/common/IExtensibleEnum;");
|
||||
private static final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); //Don't directly reference this to prevent class loading.
|
||||
private static final String ADD_DESC = Type.getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class));
|
||||
private static final Type UNSAFE_HACKS = Type.getType("Lnet/minecraftforge/fml/unsafe/UnsafeHacks;"); //Again, not direct reference to prevent class loading.
|
||||
private static final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class));
|
||||
private static final String NAME_DESC = Type.getMethodDescriptor(STRING);
|
||||
private static final String EQUALS_DESC = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, STRING);
|
||||
private static final int FLAGS = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
@@ -47,9 +48,15 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
|
||||
{
|
||||
return isEmpty ? NAY : YAY;
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraftforge/") || internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,25 +65,24 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
if ((classNode.access & Opcodes.ACC_ENUM) == 0)
|
||||
return ComputeFlags.NO_REWRITE;
|
||||
|
||||
Type array = Type.getType("[" + classType.getDescriptor());
|
||||
final int flags = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
|
||||
|
||||
FieldNode values = classNode.fields.stream().filter(f -> f.desc.contentEquals(array.getDescriptor()) && ((f.access & flags) == flags)).findFirst().orElse(null);
|
||||
|
||||
if (!classNode.interfaces.contains(MARKER_IFACE.getInternalName())) {
|
||||
if (!classNode.interfaces.contains(MARKER_IFACE.getInternalName()))
|
||||
return ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
|
||||
Type array = Type.getType("[" + classType.getDescriptor());
|
||||
String arrayDesc = array.getDescriptor();
|
||||
|
||||
FieldNode values = classNode.fields.stream().filter(f -> f.desc.equals(arrayDesc) && ((f.access & FLAGS) == FLAGS)).findFirst().orElse(null);
|
||||
|
||||
//Static methods named "create" with first argument as a string
|
||||
List<MethodNode> candidates = classNode.methods.stream()
|
||||
.filter(m -> ((m.access & Opcodes.ACC_STATIC) != 0) && m.name.equals("create"))
|
||||
.collect(Collectors.toList());
|
||||
.toList();
|
||||
|
||||
if (candidates.isEmpty()) {
|
||||
throw new IllegalStateException("IExtensibleEnum has no candidate factory methods: " + classType.getClassName());
|
||||
}
|
||||
|
||||
candidates.forEach(mtd ->
|
||||
for (var mtd : candidates)
|
||||
{
|
||||
Type[] args = Type.getArgumentTypes(mtd.desc);
|
||||
if (args.length == 0 || !args[0].equals(STRING)) {
|
||||
@@ -108,8 +114,7 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
Type[] ctrArgs = new Type[args.length + 1];
|
||||
ctrArgs[0] = STRING;
|
||||
ctrArgs[1] = Type.INT_TYPE;
|
||||
for (int x = 1; x < args.length; x++)
|
||||
ctrArgs[1 + x] = args[x];
|
||||
System.arraycopy(args, 1, ctrArgs, 2, args.length - 1);
|
||||
|
||||
String desc = Type.getMethodDescriptor(Type.VOID_TYPE, ctrArgs);
|
||||
|
||||
@@ -231,7 +236,7 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
ins.load(vars, classType);
|
||||
ins.areturn(classType);
|
||||
}
|
||||
});
|
||||
}
|
||||
return ComputeFlags.COMPUTE_FRAMES;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,9 @@ import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -36,7 +34,6 @@ import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
|
||||
public class FMLLoader {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static AccessTransformerService accessTransformer;
|
||||
private static ModDiscoverer modDiscoverer;
|
||||
private static ICoreModProvider coreModProvider;
|
||||
private static LanguageLoadingProvider languageLoadingProvider;
|
||||
private static Dist dist;
|
||||
@@ -57,12 +54,15 @@ public class FMLLoader {
|
||||
LOGGER.debug(CORE, "Detected version data : {}", versionInfo);
|
||||
LOGGER.debug(CORE, "FML {} loading", LauncherVersion.getVersion());
|
||||
|
||||
// Allows us to communicate properties with other services through ModLauncher
|
||||
setupBlackboardKeys();
|
||||
|
||||
checkPackage(ITransformationService.class, "4.0", "ModLauncher");
|
||||
accessTransformer = getPlugin(env, "accesstransformer", "1.0", "AccessTransformer");
|
||||
/*eventBus =*/ getPlugin(env, "eventbus", "1.0", "EventBus");
|
||||
runtimeDistCleaner = getPlugin(env, "runtimedistcleaner", "1.0", "RuntimeDistCleaner");
|
||||
coreModProvider = getSingleService(ICoreModProvider.class, "CoreMod");
|
||||
LOGGER.debug(CORE,"FML found CoreMod version : {}", JarVersionLookupHandler.getInfo(coreModProvider.getClass()).impl().version().orElse("MISSING"));
|
||||
LOGGER.debug(CORE, "FML found CoreMod version : {}", JarVersionLookupHandler.getInfo(coreModProvider.getClass()).impl().version().orElse("MISSING"));
|
||||
checkPackage(Environment.class, "2.0", "ForgeSPI");
|
||||
|
||||
try {
|
||||
@@ -74,14 +74,15 @@ public class FMLLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T getPlugin(IEnvironment env, String id, String version, String name) throws IncompatibleEnvironmentException {
|
||||
@SuppressWarnings("unchecked")
|
||||
var plugin = (T)env.findLaunchPlugin(id).orElseThrow(() -> {
|
||||
var plugin = env.findLaunchPlugin(id).orElse(null);
|
||||
if (plugin == null) {
|
||||
LOGGER.error(CORE, "{} library is missing, we need this to run", name);
|
||||
return new IncompatibleEnvironmentException("Missing " + name + ", cannot run");
|
||||
});
|
||||
throw new IncompatibleEnvironmentException("Missing " + name + ", cannot run");
|
||||
}
|
||||
checkPackage(plugin.getClass(), version, name);
|
||||
return plugin;
|
||||
return (T) plugin;
|
||||
}
|
||||
|
||||
private static void checkPackage(Class<?> cls, String version, String name) throws IncompatibleEnvironmentException {
|
||||
@@ -120,6 +121,12 @@ public class FMLLoader {
|
||||
return providers.get(0);
|
||||
}
|
||||
|
||||
private static void setupBlackboardKeys() {
|
||||
LOGGER.debug(CORE, "Requesting CoreMods to not apply the fix for ASMAPI.findFirstInstructionBefore by default");
|
||||
var blackboardKey = TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "coremods.use_old_findFirstInstructionBefore", Boolean.class);
|
||||
Launcher.INSTANCE.blackboard().<Boolean>computeIfAbsent(blackboardKey, k -> true);
|
||||
}
|
||||
|
||||
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments) {
|
||||
final String launchTarget = environment.getProperty(IEnvironment.Keys.LAUNCHTARGET.get()).orElse("MISSING");
|
||||
arguments.put("launchTarget", launchTarget);
|
||||
@@ -137,20 +144,20 @@ public class FMLLoader {
|
||||
}
|
||||
commonLaunchHandler = (CommonLaunchHandler)launchHandler.get();
|
||||
launchHandlerName = launchHandler.get().name();
|
||||
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Paths.get(".").toAbsolutePath());
|
||||
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Path.of(".").toAbsolutePath());
|
||||
|
||||
naming = commonLaunchHandler.getNaming();
|
||||
dist = commonLaunchHandler.getDist();
|
||||
production = commonLaunchHandler.isProduction();
|
||||
|
||||
accessTransformer.getExtension().accept(Pair.of(naming, "srg"));
|
||||
accessTransformer.getExtension().accept(Map.entry(naming, "srg"));
|
||||
|
||||
runtimeDistCleaner.getExtension().accept(dist);
|
||||
}
|
||||
|
||||
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments) {
|
||||
LOGGER.debug(SCAN,"Scanning for Mod Locators");
|
||||
modDiscoverer = new ModDiscoverer(arguments);
|
||||
var modDiscoverer = new ModDiscoverer(arguments);
|
||||
modValidator = modDiscoverer.discoverMods();
|
||||
var pluginResources = modValidator.getPluginResources();
|
||||
return List.of(pluginResources);
|
||||
@@ -172,10 +179,6 @@ public class FMLLoader {
|
||||
return languageLoadingProvider;
|
||||
}
|
||||
|
||||
static ModDiscoverer getModDiscoverer() {
|
||||
return modDiscoverer;
|
||||
}
|
||||
|
||||
public static CommonLaunchHandler getLaunchHandler() {
|
||||
return commonLaunchHandler;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class FMLServiceProvider implements ITransformationService {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private Map<String, Object> arguments;
|
||||
private final Map<String, Object> arguments = new HashMap<>();
|
||||
|
||||
public FMLServiceProvider() {
|
||||
var markers = System.getProperty("forge.logging.markers", "").split(",");
|
||||
@@ -45,7 +45,6 @@ public class FMLServiceProvider implements ITransformationService {
|
||||
FMLConfig.load();
|
||||
LOGGER.debug(CORE, "Preparing ModFile");
|
||||
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile::new);
|
||||
arguments = new HashMap<>();
|
||||
LOGGER.debug(CORE, "Preparing launch handler");
|
||||
FMLLoader.setupLaunchHandler(environment, arguments);
|
||||
FMLEnvironment.setupInteropEnvironment(environment);
|
||||
@@ -68,14 +67,6 @@ public class FMLServiceProvider implements ITransformationService {
|
||||
FMLLoader.onInitialLoad(environment, otherServices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arguments(BiFunction<String, String, OptionSpecBuilder> argumentBuilder) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void argumentValues(OptionResult option) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public @NotNull List<ITransformer> transformers() {
|
||||
|
||||
@@ -140,7 +140,7 @@ public class ImmediateWindowHandler {
|
||||
|
||||
@Override
|
||||
public void updateModuleReads(final ModuleLayer layer) {
|
||||
var fm = layer.findModule("forge");
|
||||
var fm = layer.findModule("net.minecraftforge.forge");
|
||||
if (fm.isPresent()) {
|
||||
getClass().getModule().addReads(fm.get());
|
||||
var clz = fm.map(l -> Class.forName(l, "net.minecraftforge.client.loading.NoVizFallback")).orElseThrow();
|
||||
|
||||
@@ -78,9 +78,13 @@ public class LoadingModList
|
||||
|
||||
public void addAccessTransformers()
|
||||
{
|
||||
modFiles.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.forEach(mod -> mod.getAccessTransformer().ifPresent(path -> FMLLoader.addAccessTransformer(path, mod)));
|
||||
for (ModFileInfo modFile : modFiles) {
|
||||
ModFile mod = modFile.getFile();
|
||||
var at = mod.getAccessTransformer().orElse(null);
|
||||
if (at != null) {
|
||||
FMLLoader.addAccessTransformer(at, mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
|
||||
|
||||
@@ -36,7 +36,7 @@ public class MCPNamingService implements INameMappingService {
|
||||
|
||||
@Override
|
||||
public Map.Entry<String, String> understanding() {
|
||||
return Pair.of("srg", "mcp");
|
||||
return Map.entry("srg", "mcp");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -34,7 +34,7 @@ public class MavenCoordinateResolver {
|
||||
(!extension.isEmpty() ? "." + extension : ".jar");
|
||||
|
||||
String[] groups = groupId.split("\\.");
|
||||
Path result = Paths.get(groups[0]);
|
||||
Path result = Path.of(groups[0]);
|
||||
for (int i = 1; i < groups.length; i++) {
|
||||
result = result.resolve(groups[i]);
|
||||
}
|
||||
|
||||
@@ -57,13 +57,13 @@ public class ModSorter
|
||||
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
|
||||
// So all this is really doing is wasting time.
|
||||
// But i'm leaving it here until I rewrite all of cpw's mod loading code because its such a clusterfuck.
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), e);
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).toList(), e);
|
||||
}
|
||||
// try and validate dependencies
|
||||
final List<ExceptionData> failedList = Stream.concat(ms.verifyDependencyVersions().stream(), errors.stream()).toList();
|
||||
// if we miss one or the other, we abort now
|
||||
if (!failedList.isEmpty()) {
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), new EarlyLoadingException("failure to validate mod list", null, failedList));
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).toList(), new EarlyLoadingException("failure to validate mod list", null, failedList));
|
||||
} else {
|
||||
// Otherwise, lets try and sort the modlist and proceed
|
||||
EarlyLoadingException earlyLoadingException = null;
|
||||
@@ -89,9 +89,9 @@ public class ModSorter
|
||||
infos.keySet().forEach(graph::addNode);
|
||||
modFiles.stream()
|
||||
.map(ModFile::getModInfos)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.flatMap(List::stream)
|
||||
.map(IModInfo::getDependencies)
|
||||
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
|
||||
.<IModInfo.ModVersion>flatMap(List::stream)
|
||||
.forEach(dep -> addDependency(graph, dep));
|
||||
|
||||
final List<ModFileInfo> sorted;
|
||||
@@ -107,7 +107,7 @@ public class ModSorter
|
||||
LOGGER.error(LOADING, "Mod Sorting failed.\nDetected Cycles: {}\n", cycles);
|
||||
}
|
||||
var dataList = cycles.stream()
|
||||
.<ModFileInfo>mapMulti(Iterable::forEach)
|
||||
.flatMap(Set::stream)
|
||||
.<IModInfo>mapMulti((mf,c)->mf.getMods().forEach(c))
|
||||
.map(IModInfo::getModId)
|
||||
.map(list -> new ExceptionData("fml.modloading.cycle", list))
|
||||
@@ -116,7 +116,7 @@ public class ModSorter
|
||||
}
|
||||
this.sortedList = sorted.stream()
|
||||
.map(ModFileInfo::getMods)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.flatMap(List::stream)
|
||||
.map(ModInfo.class::cast)
|
||||
.collect(toList());
|
||||
this.modFiles = sorted.stream()
|
||||
@@ -126,12 +126,14 @@ public class ModSorter
|
||||
|
||||
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
|
||||
{
|
||||
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
|
||||
final IModInfo targetModInfo = modIdNameLookup.get(dep.getModId());
|
||||
// soft dep that doesn't exist. Just return. No edge required.
|
||||
if (targetModInfo == null || !(targetModInfo.getOwningFile() instanceof final ModFileInfo target)) return;
|
||||
|
||||
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
|
||||
if (self == target)
|
||||
return; // in case a jar has two mods that have dependencies between
|
||||
|
||||
switch (dep.getOrdering()) {
|
||||
case BEFORE -> topoGraph.putEdge(self, target);
|
||||
case AFTER -> topoGraph.putEdge(target, self);
|
||||
@@ -165,7 +167,7 @@ public class ModSorter
|
||||
var container = modFilesByFirstId.get(systemMod);
|
||||
if (container != null && !container.isEmpty()) {
|
||||
LOGGER.debug("Found system mod: {}", systemMod);
|
||||
this.systemMods.add((ModFile) container.get(0));
|
||||
this.systemMods.add(container.getFirst());
|
||||
} else {
|
||||
throw new IllegalStateException("Failed to find system mod: " + systemMod);
|
||||
}
|
||||
@@ -176,23 +178,23 @@ public class ModSorter
|
||||
{
|
||||
final var modVersions = modFiles.stream()
|
||||
.map(ModFile::getModInfos)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.flatMap(List::stream)
|
||||
.collect(toMap(IModInfo::getModId, IModInfo::getVersion));
|
||||
|
||||
final var modVersionDependencies = modFiles.stream()
|
||||
.map(ModFile::getModInfos)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.flatMap(List::stream)
|
||||
.collect(groupingBy(Function.identity(), flatMapping(e -> e.getDependencies().stream(), toList())));
|
||||
|
||||
final var modRequirements = modVersionDependencies.values().stream()
|
||||
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
|
||||
.<IModInfo.ModVersion>flatMap(List::stream)
|
||||
.filter(mv -> mv.getSide().isCorrectSide())
|
||||
.collect(toSet());
|
||||
|
||||
final long mandatoryRequired = modRequirements.stream().filter(IModInfo.ModVersion::isMandatory).count();
|
||||
LOGGER.debug(LOADING, "Found {} mod requirements ({} mandatory, {} optional)", modRequirements.size(), mandatoryRequired, modRequirements.size() - mandatoryRequired);
|
||||
final var missingVersions = modRequirements.stream()
|
||||
.filter(mv -> (mv.isMandatory() || modVersions.containsKey(mv.getModId())) && this.modVersionNotContained(mv, modVersions))
|
||||
.filter(mv -> (mv.isMandatory() || modVersions.containsKey(mv.getModId())) && !modVersionContained(mv, modVersions))
|
||||
.collect(toSet());
|
||||
final long mandatoryMissing = missingVersions.stream().filter(IModInfo.ModVersion::isMandatory).count();
|
||||
LOGGER.debug(LOADING, "Found {} mod requirements missing ({} mandatory, {} optional)", missingVersions.size(), mandatoryMissing, missingVersions.size() - mandatoryMissing);
|
||||
@@ -240,9 +242,13 @@ public class ModSorter
|
||||
);
|
||||
}
|
||||
|
||||
private boolean modVersionNotContained(final IModInfo.ModVersion mv, final Map<String, ArtifactVersion> modVersions)
|
||||
{
|
||||
return !(VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod", (modId, range) -> modVersions.containsKey(modId) &&
|
||||
(range.containsVersion(modVersions.get(modId)) || modVersions.get(modId).toString().equals("0.0NONE"))));
|
||||
private static boolean modVersionContained(IModInfo.ModVersion mv, Map<String, ArtifactVersion> modVersions) {
|
||||
var modId = mv.getModId();
|
||||
var range = mv.getVersionRange();
|
||||
if (modVersions.containsKey(modId)
|
||||
&& (range.containsVersion(modVersions.get(modId)) || modVersions.get(modId).toString().equals("0.0NONE")))
|
||||
return true;
|
||||
|
||||
return VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod");
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,8 @@ package net.minecraftforge.fml.loading;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
@@ -28,200 +22,177 @@ import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
public class RuntimeDistCleaner implements ILaunchPluginService
|
||||
{
|
||||
public class RuntimeDistCleaner implements ILaunchPluginService {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final Marker DISTXFORM = MarkerFactory.getMarker("DISTXFORM");
|
||||
private static String DIST;
|
||||
private static final String ONLYIN = Type.getDescriptor(OnlyIn.class);
|
||||
private static final String ONLYINS = Type.getDescriptor(OnlyIns.class);
|
||||
private static final String MOD = "Lnet/minecraftforge/fml/common/Mod;";
|
||||
|
||||
@Override
|
||||
public String name()
|
||||
{
|
||||
public String name() {
|
||||
return "runtimedistcleaner";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
|
||||
{
|
||||
AtomicBoolean changes = new AtomicBoolean();
|
||||
if (remove(classNode.visibleAnnotations, DIST))
|
||||
{
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) {
|
||||
var changed = false;
|
||||
|
||||
var annotations = unpack(classNode.visibleAnnotations);
|
||||
|
||||
if (remove(annotations, DIST)) {
|
||||
LOGGER.error(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
|
||||
throw new RuntimeException("Attempted to load class "+ classNode.name + " for invalid dist "+ DIST);
|
||||
}
|
||||
|
||||
if (!FMLEnvironment.production && hasOnlyInWithModAnnotation(classNode.visibleAnnotations))
|
||||
{
|
||||
if (!FMLEnvironment.production && !annotations.isEmpty() && hasModAnnotation(classNode.visibleAnnotations)) {
|
||||
LOGGER.error(DISTXFORM, "Attempted to load class {} with @Mod and @OnlyIn/@OnlyIns annotations", classNode.name);
|
||||
throw new RuntimeException("Found @OnlyIn on @Mod class "+ classNode.name + " - this is not allowed as it causes crashes. Remove the OnlyIn and consider setting clientSideOnly=true in the root of your mods.toml instead");
|
||||
}
|
||||
|
||||
if (classNode.interfaces != null )
|
||||
{
|
||||
unpack(classNode.visibleAnnotations).stream()
|
||||
.filter(ann->Objects.equals(ann.desc, ONLYIN))
|
||||
.filter(ann-> ann.values.contains("_interface"))
|
||||
.filter(ann->!Objects.equals(((String[])ann.values.get(ann.values.indexOf("value") + 1))[1], DIST))
|
||||
.map(ann -> ((Type)ann.values.get(ann.values.indexOf("_interface") + 1)).getInternalName())
|
||||
.forEach(intf -> {
|
||||
if (classNode.interfaces.remove(intf)) {
|
||||
LOGGER.debug(DISTXFORM,"Removing Interface: {} implements {}", classNode.name, intf);
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
});
|
||||
if (classNode.interfaces != null && !classNode.interfaces.isEmpty()) {
|
||||
for (var ann : annotations) {
|
||||
if (ann.intf == null || DIST.equals(ann.side))
|
||||
continue;
|
||||
|
||||
if (classNode.interfaces.remove(ann.intf)) {
|
||||
LOGGER.debug(DISTXFORM,"Removing Interface: {} implements {}", classNode.name, ann.intf);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Remove Class level @OnlyIn/@OnlyIns annotations, this is important if anyone gets ambitious and tries to reflect an annotation with _interface set.
|
||||
if (classNode.visibleAnnotations != null) {
|
||||
Iterator<AnnotationNode> itr = classNode.visibleAnnotations.iterator();
|
||||
while (itr.hasNext()) {
|
||||
AnnotationNode ann = itr.next();
|
||||
if (Objects.equals(ann.desc, ONLYIN) || Objects.equals(ann.desc, ONLYINS)) {
|
||||
LOGGER.debug(DISTXFORM,"Removing Class Annotation: {} @{}", classNode.name, ann.desc);
|
||||
for (var itr = classNode.visibleAnnotations.iterator(); itr.hasNext(); ) {
|
||||
var ann = itr.next();
|
||||
if (ONLYIN.equals(ann.desc) || ONLYINS.equals(ann.desc)) {
|
||||
LOGGER.debug(DISTXFORM, "Removing Class Annotation: {} @{}", classNode.name, ann.desc);
|
||||
itr.remove();
|
||||
changes.compareAndSet(false, true);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<FieldNode> fields = classNode.fields.iterator();
|
||||
while(fields.hasNext())
|
||||
{
|
||||
FieldNode field = fields.next();
|
||||
if (remove(field.visibleAnnotations, DIST))
|
||||
{
|
||||
LOGGER.debug(DISTXFORM,"Removing field: {}.{}", classNode.name, field.name);
|
||||
fields.remove();
|
||||
changes.compareAndSet(false, true);
|
||||
for (var itr = classNode.fields.iterator(); itr.hasNext(); ) {
|
||||
var field = itr.next();
|
||||
if (remove(unpack(field.visibleAnnotations), DIST)) {
|
||||
LOGGER.debug(DISTXFORM, "Removing field: {}.{}", classNode.name, field.name);
|
||||
itr.remove();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
LambdaGatherer lambdaGatherer = new LambdaGatherer();
|
||||
Iterator<MethodNode> methods = classNode.methods.iterator();
|
||||
while(methods.hasNext())
|
||||
{
|
||||
MethodNode method = methods.next();
|
||||
if (remove(method.visibleAnnotations, DIST))
|
||||
{
|
||||
LOGGER.debug(DISTXFORM,"Removing method: {}.{}{}", classNode.name, method.name, method.desc);
|
||||
methods.remove();
|
||||
var lambdaGatherer = new LambdaGatherer();
|
||||
for (var itr = classNode.methods.iterator(); itr.hasNext(); ) {
|
||||
var method = itr.next();
|
||||
if (remove(unpack(method.visibleAnnotations), DIST)) {
|
||||
LOGGER.debug(DISTXFORM, "Removing method: {}.{}{}", classNode.name, method.name, method.desc);
|
||||
itr.remove();
|
||||
lambdaGatherer.accept(method);
|
||||
changes.compareAndSet(false, true);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// remove dynamic synthetic lambda methods that are inside of removed methods
|
||||
for (List<Handle> dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles();
|
||||
!dynamicLambdaHandles.isEmpty(); dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles())
|
||||
{
|
||||
for (List<Handle> handles = lambdaGatherer.getDynamicLambdaHandles();
|
||||
!handles.isEmpty(); handles = lambdaGatherer.getDynamicLambdaHandles()) {
|
||||
lambdaGatherer = new LambdaGatherer();
|
||||
methods = classNode.methods.iterator();
|
||||
while (methods.hasNext())
|
||||
{
|
||||
MethodNode method = methods.next();
|
||||
if ((method.access & Opcodes.ACC_SYNTHETIC) == 0) continue;
|
||||
for (Handle dynamicLambdaHandle : dynamicLambdaHandles)
|
||||
{
|
||||
if (method.name.equals(dynamicLambdaHandle.getName()) && method.desc.equals(dynamicLambdaHandle.getDesc()))
|
||||
{
|
||||
LOGGER.debug(DISTXFORM,"Removing lambda method: {}.{}{}", classNode.name, method.name, method.desc);
|
||||
methods.remove();
|
||||
for (var itr = classNode.methods.iterator(); itr.hasNext(); ) {
|
||||
MethodNode method = itr.next();
|
||||
if ((method.access & Opcodes.ACC_SYNTHETIC) == 0)
|
||||
continue;
|
||||
|
||||
for (var handle : handles) {
|
||||
if (method.name.equals(handle.getName()) && method.desc.equals(handle.getDesc())) {
|
||||
LOGGER.debug(DISTXFORM, "Removing lambda method: {}.{}{}", classNode.name, method.name, method.desc);
|
||||
itr.remove();
|
||||
lambdaGatherer.accept(method);
|
||||
changes.compareAndSet(false, true);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
|
||||
|
||||
return changed ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
|
||||
private static record Target(String side, String intf) {
|
||||
static Target from(final AnnotationNode node) {
|
||||
var idx = node.values.indexOf("value");
|
||||
var value = (String[])node.values.get(idx + 1); // Enums are stored as [Type, Value]
|
||||
|
||||
idx = node.values.indexOf("_interface");
|
||||
if (idx == -1)
|
||||
return new Target(value[1], null);
|
||||
|
||||
var intf = (Type)node.values.get(idx + 1);
|
||||
return new Target(value[1], intf.getInternalName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack any OnlyIn or OnlyIns annotations to Target objects.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<AnnotationNode> unpack(final List<AnnotationNode> anns)
|
||||
{
|
||||
if (anns == null)
|
||||
{
|
||||
private static List<Target> unpack(final List<AnnotationNode> anns) {
|
||||
if (anns == null || anns.isEmpty())
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<AnnotationNode> unpacked = new ArrayList<>();
|
||||
var ret = new ArrayList<Target>();
|
||||
for (var node : anns) {
|
||||
if (ONLYIN.equals(node.desc))
|
||||
ret.add(Target.from(node));
|
||||
else if (ONLYINS.equals(node.desc)) {
|
||||
if (node.values == null || node.values.isEmpty())
|
||||
continue;
|
||||
|
||||
for (var annotationNode : anns)
|
||||
{
|
||||
if (Objects.equals(annotationNode.desc, ONLYINS))
|
||||
{
|
||||
unpacked.add(annotationNode);
|
||||
int idx = node.values.indexOf("value");
|
||||
if (idx == -1)
|
||||
continue;
|
||||
|
||||
if (annotationNode.values != null)
|
||||
{
|
||||
List<AnnotationNode> subNodes = (List<AnnotationNode>) annotationNode.values.get(annotationNode.values.indexOf("value") + 1);
|
||||
|
||||
if (subNodes != null)
|
||||
{
|
||||
unpacked.addAll(subNodes);
|
||||
}
|
||||
var subNodes = (List<AnnotationNode>)node.values.get(idx + 1);
|
||||
if (subNodes != null) {
|
||||
for (var sub : subNodes)
|
||||
ret.add(Target.from(sub));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unpacked;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private boolean remove(final List<AnnotationNode> anns, final String side)
|
||||
{
|
||||
var onlyIns = unpack(anns);
|
||||
|
||||
for (var onlyIn : onlyIns)
|
||||
{
|
||||
if (!Objects.equals(onlyIn.desc, ONLYINS) || onlyIn.values.contains("_interface"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Objects.equals(((String[])onlyIn.values.get(onlyIn.values.indexOf("value") + 1))[1], side))
|
||||
{
|
||||
private static boolean remove(final List<Target> targets, final String side) {
|
||||
for (var target : targets) {
|
||||
if (target.intf == null && !side.equals(target.side))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasOnlyInWithModAnnotation(final List<AnnotationNode> anns)
|
||||
{
|
||||
if (anns == null)
|
||||
{
|
||||
private static boolean hasModAnnotation(final List<AnnotationNode> anns) {
|
||||
if (anns == null || anns.isEmpty())
|
||||
return false;
|
||||
|
||||
for (var ann : anns) {
|
||||
if (ann.desc.equals(MOD))
|
||||
return true;
|
||||
}
|
||||
|
||||
var foundModAnnotation = false;
|
||||
var foundOnlyIn = false;
|
||||
|
||||
for (var ann : anns)
|
||||
{
|
||||
if (ann.desc.equals("Lnet/minecraftforge/fml/common/Mod;"))
|
||||
{
|
||||
foundModAnnotation = true;
|
||||
} else if (Objects.equals(ann.desc, ONLYIN) || Objects.equals(ann.desc, ONLYINS))
|
||||
{
|
||||
foundOnlyIn = true;
|
||||
}
|
||||
}
|
||||
|
||||
return foundModAnnotation && foundOnlyIn;
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Consumer<Dist> getExtension()
|
||||
{
|
||||
return (s)-> {
|
||||
public Consumer<Dist> getExtension() {
|
||||
return s -> {
|
||||
DIST = s.name();
|
||||
LOGGER.debug(DISTXFORM, "Configuring for Dist {}", DIST);
|
||||
};
|
||||
@@ -231,9 +202,15 @@ public class RuntimeDistCleaner implements ILaunchPluginService
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
|
||||
{
|
||||
return isEmpty ? NAY : YAY;
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraftforge/"))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
private static class LambdaGatherer extends MethodVisitor {
|
||||
@@ -248,23 +225,21 @@ public class RuntimeDistCleaner implements ILaunchPluginService
|
||||
}
|
||||
|
||||
public void accept(MethodNode method) {
|
||||
Streams.stream(method.instructions.iterator()).
|
||||
filter(insnNode->insnNode.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN).
|
||||
forEach(insnNode->insnNode.accept(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs)
|
||||
{
|
||||
if (META_FACTORY.equals(bsm))
|
||||
{
|
||||
Handle dynamicLambdaHandle = (Handle) bsmArgs[1];
|
||||
dynamicLambdaHandles.add(dynamicLambdaHandle);
|
||||
for (var insn : method.instructions) {
|
||||
if (insn.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN)
|
||||
insn.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Handle> getDynamicLambdaHandles()
|
||||
{
|
||||
@Override
|
||||
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
|
||||
if (META_FACTORY.equals(bsm)) {
|
||||
var handle = (Handle)bsmArgs[1];
|
||||
dynamicLambdaHandles.add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Handle> getDynamicLambdaHandles() {
|
||||
return dynamicLambdaHandles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,16 +14,17 @@ import org.apache.commons.lang3.text.StrSubstitutor;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class StringSubstitutor {
|
||||
private static final Map<String, String> GLOBALS = Map.of(
|
||||
"mcVersion", FMLLoader.versionInfo().mcVersion(),
|
||||
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
|
||||
);
|
||||
|
||||
public static String replace(final String in, final ModFile file) {
|
||||
return new StrSubstitutor(getStringLookup(file)).replace(in);
|
||||
}
|
||||
|
||||
private static StrLookup<String> getStringLookup(final ModFile file) {
|
||||
var globals = Map.of(
|
||||
"mcVersion", FMLLoader.versionInfo().mcVersion(),
|
||||
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
|
||||
);
|
||||
return new StrLookup<String>() {
|
||||
return new StrLookup<>() {
|
||||
@Override
|
||||
public String lookup(String key) {
|
||||
var parts = key.split("\\.");
|
||||
@@ -32,7 +33,7 @@ public class StringSubstitutor {
|
||||
var pfx = parts[0];
|
||||
|
||||
if ("global".equals(pfx))
|
||||
return globals.get(parts[1]);
|
||||
return GLOBALS.get(parts[1]);
|
||||
else if ("file".equals(pfx) && file != null)
|
||||
return String.valueOf(file.getSubstitutionMap().get().get(parts[1]));
|
||||
|
||||
|
||||
@@ -47,12 +47,12 @@ public class UniqueModListBuilder
|
||||
|
||||
// Select the newest by artifact version sorting of non-unique files thus identified
|
||||
uniqueModList = modFilesByFirstId.entrySet().stream()
|
||||
.map(this::selectNewestModInfo)
|
||||
.map(UniqueModListBuilder::selectNewestModInfo)
|
||||
.toList();
|
||||
|
||||
// Select the newest by artifact version sorting of non-unique files thus identified
|
||||
uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
|
||||
.map(this::selectNewestModInfo)
|
||||
.map(UniqueModListBuilder::selectNewestModInfo)
|
||||
.toList();
|
||||
|
||||
// Transform to the full mod id list
|
||||
@@ -75,7 +75,7 @@ public class UniqueModListBuilder
|
||||
final List<String> dupedModErrors = modIds.values().stream()
|
||||
.filter(modInfos -> modInfos.size() > 1)
|
||||
.map(mods -> String.format("\tMod ID: '%s' from mod files: %s",
|
||||
mods.get(0).getModId(),
|
||||
mods.getFirst().getModId(),
|
||||
mods.stream()
|
||||
.map(modInfo -> modInfo.getOwningFile().getFile().getFileName()).collect(joining(", "))
|
||||
)).toList();
|
||||
@@ -91,9 +91,8 @@ public class UniqueModListBuilder
|
||||
final List<String> dupedLibErrors = versionedLibIds.values().stream()
|
||||
.filter(modFiles -> modFiles.size() > 1)
|
||||
.map(mods -> String.format("\tLibrary: '%s' from files: %s",
|
||||
getModId(mods.get(0)),
|
||||
mods.stream()
|
||||
.map(modFile -> modFile.getFileName()).collect(joining(", "))
|
||||
getModId(mods.getFirst()),
|
||||
mods.stream().map(ModFile::getFileName).collect(joining(", "))
|
||||
)).toList();
|
||||
|
||||
if (!dupedLibErrors.isEmpty()) {
|
||||
@@ -107,25 +106,23 @@ public class UniqueModListBuilder
|
||||
final Map<String, List<ModFile>> uniqueModFilesByFirstId = uniqueModList.stream()
|
||||
.collect(groupingBy(UniqueModListBuilder::getModId));
|
||||
|
||||
final List<ModFile> loadedList = new ArrayList<>();
|
||||
loadedList.addAll(uniqueModList);
|
||||
final List<ModFile> loadedList = new ArrayList<>(uniqueModList);
|
||||
loadedList.addAll(uniqueLibListWithVersion);
|
||||
|
||||
return new UniqueModListData(loadedList, uniqueModFilesByFirstId);
|
||||
}
|
||||
|
||||
private ModFile selectNewestModInfo(Map.Entry<String, List<ModFile>> fullList) {
|
||||
private static ModFile selectNewestModInfo(Map.Entry<String, List<ModFile>> fullList) {
|
||||
List<ModFile> modInfoList = fullList.getValue();
|
||||
if (modInfoList.size() > 1) {
|
||||
LOGGER.debug("Found {} mods for first modid {}, selecting most recent based on version data", modInfoList.size(), fullList.getKey());
|
||||
modInfoList.sort(Comparator.comparing(this::getVersion).reversed());
|
||||
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), this.getVersion(modInfoList.get(0)));
|
||||
modInfoList.sort(Comparator.comparing(UniqueModListBuilder::getVersion).reversed());
|
||||
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.getFirst().getFileName(), fullList.getKey(), getVersion(modInfoList.getFirst()));
|
||||
}
|
||||
return modInfoList.get(0);
|
||||
}
|
||||
|
||||
private ArtifactVersion getVersion(final ModFile mf)
|
||||
{
|
||||
private static ArtifactVersion getVersion(final ModFile mf) {
|
||||
if (mf.getModFileInfo() == null || mf.getModInfos() == null || mf.getModInfos().isEmpty()) {
|
||||
return mf.getJarVersion();
|
||||
}
|
||||
@@ -138,7 +135,7 @@ public class UniqueModListBuilder
|
||||
return modFile.getSecureJar().name();
|
||||
}
|
||||
|
||||
return modFile.getModFileInfo().moduleName();
|
||||
return modFile.getModFileInfo().getMods().get(0).getModId();
|
||||
}
|
||||
|
||||
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
|
||||
|
||||
@@ -5,33 +5,53 @@
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
@ApiStatus.Internal // since 1.21.1, will be made non-public in a later MC version
|
||||
public class VersionSupportMatrix {
|
||||
private static final HashMap<String, List<ArtifactVersion>> overrideVersions = new HashMap<>();
|
||||
private static final Map<String, List<ArtifactVersion>> OVERRIDE_VERSIONS;
|
||||
|
||||
static {
|
||||
final ArtifactVersion version = new DefaultArtifactVersion(FMLLoader.versionInfo().mcVersion());
|
||||
if (MavenVersionAdapter.createFromVersionSpec("[1.19.2]").containsVersion(version)) {
|
||||
// 1.19.2 is Compatible with 1.19.1
|
||||
add("languageloader.javafml", "42");
|
||||
add("mod.minecraft", "1.19.1");
|
||||
add("mod.forge", "42.0.9");
|
||||
if ("1.21.1".equals(FMLLoader.versionInfo().mcVersion())) {
|
||||
OVERRIDE_VERSIONS = Map.ofEntries(
|
||||
// 1.21.1 is compatible with 1.21
|
||||
entry("languageloader.javafml", "51"),
|
||||
entry("mod.minecraft", "1.21"),
|
||||
entry("mod.forge", "51.0.33")
|
||||
);
|
||||
} else {
|
||||
OVERRIDE_VERSIONS = Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
private static void add(String key, String value) {
|
||||
overrideVersions.computeIfAbsent(key, k -> new ArrayList<>()).add(new DefaultArtifactVersion(value));
|
||||
}
|
||||
public static <T> boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #testVersionSupportMatrix(VersionRange, String, String)} instead, unwrapping your BiPredicate.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public static boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
|
||||
if (standardLookup.test(lookupId, declaredRange)) return true;
|
||||
List<ArtifactVersion> custom = overrideVersions.get(type + "." + lookupId);
|
||||
return custom == null ? false : custom.stream().anyMatch(declaredRange::containsVersion);
|
||||
return testVersionSupportMatrix(declaredRange, lookupId, type);
|
||||
}
|
||||
|
||||
public static boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type) {
|
||||
if (OVERRIDE_VERSIONS.isEmpty()) return false;
|
||||
List<ArtifactVersion> custom = OVERRIDE_VERSIONS.get(type + "." + lookupId);
|
||||
return custom != null && custom.stream().anyMatch(declaredRange::containsVersion);
|
||||
}
|
||||
|
||||
private static Map.Entry<String, List<ArtifactVersion>> entry(String typeAndLookupId, String declaredRange) {
|
||||
return Map.entry(typeAndLookupId, List.of(new DefaultArtifactVersion(declaredRange)));
|
||||
}
|
||||
|
||||
private static Map.Entry<String, List<ArtifactVersion>> entry(String typeAndLookupId, List<String> declaredRanges) {
|
||||
return Map.entry(typeAndLookupId, declaredRanges.stream().map(DefaultArtifactVersion::new).map(it -> (ArtifactVersion) it).toList());
|
||||
}
|
||||
}
|
||||
@@ -49,10 +49,7 @@ public class ForgeHighlight {
|
||||
LOGGER.warn("Failed to invoke initializeTerminal on TCA", e);
|
||||
}
|
||||
if (!TerminalConsoleAppender.isAnsiSupported() && Arrays.stream(options).noneMatch(s -> s.equals("disableAnsi=true"))) {
|
||||
List<String> optionList = new ArrayList<>();
|
||||
optionList.add(options[0]);
|
||||
optionList.add("disableAnsi=true");
|
||||
options = optionList.toArray(new String[0]);
|
||||
options = new String[] { options[0], "disableAnsi=true" };
|
||||
}
|
||||
return HighlightConverter.newInstance(config, options);
|
||||
}
|
||||
|
||||
+70
-9
@@ -6,6 +6,7 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import cpw.mods.jarhandling.JarMetadata;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
@@ -22,19 +23,25 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.module.InvalidModuleDescriptorException;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.lang.module.ModuleDescriptor.Version;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public abstract class AbstractModProvider implements IModProvider {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
protected static final String MODS_TOML = "META-INF/mods.toml";
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
protected static final String MODS_TOML = "META-INF/mods.toml";
|
||||
protected static final String MODULE_INFO = "module-info.class";
|
||||
|
||||
protected IModLocator.ModFileOrException createMod(Path path) {
|
||||
return createMod(path, false);
|
||||
@@ -42,16 +49,18 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
|
||||
@Nullable
|
||||
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown) {
|
||||
return createMod(path, false, getDefaultJarModType());
|
||||
return createMod(path, ignoreUnknown, getDefaultJarModType());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown, String defaultType) {
|
||||
var mjm = new ModJarMetadata();
|
||||
var sj = SecureJar.from(
|
||||
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
|
||||
path
|
||||
);
|
||||
SecureJar sj = null;
|
||||
try {
|
||||
sj = SecureJar.from(jar -> loadMetaFromJar(jar, mjm), path);
|
||||
} catch (Throwable t) {
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Failed to create secure jar for \"" + path + "\" - " + t.getMessage()));
|
||||
}
|
||||
|
||||
IModFile mod;
|
||||
var type = sj.moduleDataProvider().getManifest().getMainAttributes().getValue(ModFile.TYPE);
|
||||
@@ -61,6 +70,8 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
|
||||
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
|
||||
// ModJarMetadata is only used when loading mods.toml mods as ModJarMetadata
|
||||
mjm.setModFile(mod);
|
||||
if (mod.getModFileInfo().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Unable to load file \"{}\" because its mods.toml is requesting an invalid javafml loaderVersion (use \"*\" if you want to allow all versions) and is missing a forge modId dependency declaration (see the sample mods.toml in the MDK).", path);
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("File \"%s\" is not a Forge mod and cannot be loaded. Look for a Forge version of this mod or consider alternative mods.".formatted(mod.getFileName())));
|
||||
@@ -73,14 +84,63 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
} else
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + path));
|
||||
|
||||
mjm.setModFile(mod);
|
||||
return new IModLocator.ModFileOrException(mod, null);
|
||||
}
|
||||
|
||||
private static JarMetadata loadMetaFromJar(SecureJar jar, ModJarMetadata mjm) {
|
||||
var info = jar.moduleDataProvider().open(MODULE_INFO).orElse(null);
|
||||
if (info != null) {
|
||||
try {
|
||||
var desc = ModuleDescriptor.read(info, jar::getPackages);
|
||||
var all = new HashSet<>(jar.getPackages());
|
||||
all.removeAll(desc.packages());
|
||||
if (!all.isEmpty()) {
|
||||
var missing = all.stream().sorted().collect(Collectors.joining(", "));
|
||||
LOGGER.error("Invalid module-info, missing packages " + missing);
|
||||
throw new ModFileLoadingException("Invalid module-info, missing packages " + missing);
|
||||
} else {
|
||||
return new JarMetadata() {
|
||||
@Override
|
||||
public String name() {
|
||||
return desc.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String version() {
|
||||
return desc.version().map(Version::toString).or(desc::rawVersion).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleDescriptor descriptor() {
|
||||
return desc;
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (InvalidModuleDescriptorException | IOException e) {
|
||||
LOGGER.error("Failed to parse " + jar.getPrimaryPath() + " module-info", e);
|
||||
throw new ModFileLoadingException("Invalid module-info: " + e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
info.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (jar.moduleDataProvider().findFile(MODS_TOML).isEmpty())
|
||||
return JarMetadata.from(jar, jar.getPrimaryPath());
|
||||
|
||||
return mjm;
|
||||
}
|
||||
|
||||
protected IModFileInfo manifestParser(final IModFile mod) {
|
||||
var mf = mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes();
|
||||
var license = mf.getValue("LICENSE");
|
||||
var dummy = new IConfigurable() {
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(String key) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(String... key) {
|
||||
return Optional.empty();
|
||||
@@ -140,10 +200,11 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
}
|
||||
|
||||
private static final class Holder<T> {
|
||||
T value;
|
||||
private T value;
|
||||
}
|
||||
|
||||
private record DefaultModFileInfo(IModFile mod, String license, IConfigurable configurable) implements IModFileInfo, IConfigurable {
|
||||
@Override public <T> Optional<T> getConfigElement(final String string) { return Optional.empty(); }
|
||||
@Override public <T> Optional<T> getConfigElement(final String... strings) { return Optional.empty(); }
|
||||
@Override public List<? extends IConfigurable> getConfigList(final String... strings) { return null; }
|
||||
@Override public List<IModInfo> getMods() { return Collections.emptyList(); }
|
||||
|
||||
+6
-5
@@ -29,6 +29,7 @@ public class BackgroundScanHandler
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final boolean DEBUG = LOGGER.isErrorEnabled(LogMarkers.SCAN);
|
||||
private final ExecutorService modContentScanner;
|
||||
private final List<ModFile> modFiles;
|
||||
private ScanStatus status;
|
||||
@@ -55,16 +56,16 @@ public class BackgroundScanHandler
|
||||
}
|
||||
status = ScanStatus.RUNNING;
|
||||
ImmediateWindowHandler.updateProgress("Scanning mod candidates");
|
||||
final CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
|
||||
.whenComplete(file::setScanResult)
|
||||
.whenComplete((r,t)-> this.addCompletedFile(file,r,t));
|
||||
CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
|
||||
.whenComplete(file::setScanResult);
|
||||
if (DEBUG) future = future.whenComplete((r, t) -> addCompletedFile(file, t));
|
||||
file.setFutureScanResult(future);
|
||||
}
|
||||
|
||||
private void addCompletedFile(final ModFile file, final ModFileScanData modFileScanData, final Throwable throwable) {
|
||||
private void addCompletedFile(final ModFile file, final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
status = ScanStatus.ERRORED;
|
||||
LOGGER.error(LogMarkers.SCAN,"An error occurred scanning file {}", file, throwable);
|
||||
LOGGER.error(LogMarkers.SCAN, "An error occurred scanning file {}", file, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-6
@@ -18,9 +18,9 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.jar.Attributes;
|
||||
@@ -28,7 +28,7 @@ import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ClasspathLocator extends AbstractModProvider implements IModLocator {
|
||||
public final class ClasspathLocator extends AbstractModProvider implements IModLocator {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final Attributes.Name MOD_TYPE = new Attributes.Name("FMLModType");
|
||||
|
||||
@@ -80,11 +80,11 @@ public class ClasspathLocator extends AbstractModProvider implements IModLocator
|
||||
return ret;
|
||||
}
|
||||
|
||||
private List<URL> getUrls(ClassLoader cl, String resource) {
|
||||
private static List<URL> getUrls(ClassLoader cl, String resource) {
|
||||
try {
|
||||
var lst = Collections.list(cl.getResources(resource));
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
|
||||
Collections.sort(lst, (a, b) -> a.toString().compareTo(b.toString()));
|
||||
lst.sort(Comparator.comparing(URL::toString));
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scanning Classloader: {} for {}", cl, resource);
|
||||
for (var url : lst)
|
||||
LOGGER.debug(LogMarkers.SCAN, "\t{}", url);
|
||||
@@ -104,8 +104,7 @@ public class ClasspathLocator extends AbstractModProvider implements IModLocator
|
||||
len += 2;
|
||||
}
|
||||
str = str.substring(0, str.length() - len);
|
||||
var path = Paths.get(URI.create(str));
|
||||
return path;
|
||||
return Path.of(URI.create(str));
|
||||
}
|
||||
|
||||
private static Path getPathFromResource(ClassLoader cl, String resource) {
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ import java.nio.file.Path;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class CoreModFile implements ICoreModFile {
|
||||
public final class CoreModFile implements ICoreModFile {
|
||||
private final Path internalPath;
|
||||
private final ModFile file;
|
||||
private final String name;
|
||||
|
||||
+9
-9
@@ -6,7 +6,6 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
@@ -26,6 +25,7 @@ import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -51,7 +51,7 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
|
||||
|
||||
@Override
|
||||
public List<IModFile> scanMods(Iterable<IModFile> loadedMods) {
|
||||
final List<IModFile> sources = Lists.newArrayList();
|
||||
final List<IModFile> sources = new ArrayList<>();
|
||||
loadedMods.forEach(sources::add);
|
||||
|
||||
var dependenciesToLoad = JarSelector.detectAndSelect(
|
||||
@@ -102,34 +102,34 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
|
||||
protected EarlyLoadingException exception(Collection<JarSelector.ResolutionFailureInformation<IModFile>> failedDependencies) {
|
||||
final List<EarlyLoadingException.ExceptionData> errors = failedDependencies.stream()
|
||||
.filter(entry -> !entry.sources().isEmpty()) //Should never be the case, but just to be sure
|
||||
.map(this::buildExceptionData)
|
||||
.map(JarInJarDependencyLocator::buildExceptionData)
|
||||
.toList();
|
||||
|
||||
return new EarlyLoadingException(failedDependencies.size() + " Dependency restrictions were not met.", null, errors);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private EarlyLoadingException.ExceptionData buildExceptionData(JarSelector.ResolutionFailureInformation<IModFile> entry) {
|
||||
private static EarlyLoadingException.ExceptionData buildExceptionData(JarSelector.ResolutionFailureInformation<IModFile> entry) {
|
||||
return new EarlyLoadingException.ExceptionData(
|
||||
getErrorTranslationKey(entry),
|
||||
entry.identifier().group() + ":" + entry.identifier().artifact(),
|
||||
entry.sources()
|
||||
.stream()
|
||||
.flatMap(this::getModWithVersionRangeStream)
|
||||
.map(this::formatError)
|
||||
.flatMap(JarInJarDependencyLocator::getModWithVersionRangeStream)
|
||||
.map(JarInJarDependencyLocator::formatError)
|
||||
.collect(Collectors.joining(", "))
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getErrorTranslationKey(JarSelector.ResolutionFailureInformation<IModFile> entry) {
|
||||
private static String getErrorTranslationKey(JarSelector.ResolutionFailureInformation<IModFile> entry) {
|
||||
return entry.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ?
|
||||
"fml.dependencyloading.conflictingdependencies" :
|
||||
"fml.dependencyloading.mismatchedcontaineddependencies";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Stream<ModWithVersionRange> getModWithVersionRangeStream(JarSelector.SourceWithRequestedVersionRange<IModFile> file) {
|
||||
private static Stream<ModWithVersionRange> getModWithVersionRangeStream(JarSelector.SourceWithRequestedVersionRange<IModFile> file) {
|
||||
return file.sources()
|
||||
.stream()
|
||||
.map(IModFile::getModFileInfo)
|
||||
@@ -152,7 +152,7 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String formatError(ModWithVersionRange modWithVersionRange){
|
||||
private static String formatError(ModWithVersionRange modWithVersionRange){
|
||||
return YELLOW + modWithVersionRange.modInfo().getModId() + RESET + " - " +
|
||||
RED + modWithVersionRange.versionRange().toString() + RESET + " - " +
|
||||
GREEN + modWithVersionRange.artifactVersion().toString() + RESET;
|
||||
|
||||
+6
-8
@@ -18,24 +18,22 @@ import java.util.List;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class MinecraftLocator extends AbstractModProvider implements IModLocator {
|
||||
public final class MinecraftLocator extends AbstractModProvider implements IModLocator {
|
||||
@Override
|
||||
public List<IModLocator.ModFileOrException> scanMods() {
|
||||
var minecraft = FMLLoader.getLaunchHandler().getMinecraftPaths();
|
||||
var paths = minecraft.toArray(Path[]::new);
|
||||
|
||||
// Minecraft itself.
|
||||
var meta = new ModJarMetadata();
|
||||
var mcjar = SecureJar.from(
|
||||
jar -> meta,
|
||||
minecraft.toArray(Path[]::new)
|
||||
);
|
||||
var mc = ModFileFactory.FACTORY.build(mcjar, this, this::buildMinecraftTOML);
|
||||
var mcjar = SecureJar.from(jar -> meta, paths);
|
||||
var mc = ModFileFactory.FACTORY.build(mcjar, this, MinecraftLocator::buildMinecraftTOML);
|
||||
meta.setModFile(mc);
|
||||
|
||||
return List.of(new ModFileOrException(mc, null));
|
||||
}
|
||||
|
||||
private IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
|
||||
private static IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
|
||||
// We haven't changed this in years, and I can't be asked right now to special case this one file in the path.
|
||||
final var conf = Config.inMemory();
|
||||
conf.set("modLoader", "minecraft");
|
||||
@@ -52,7 +50,7 @@ public class MinecraftLocator extends AbstractModProvider implements IModLocator
|
||||
conf.set("mods", List.of(mods));
|
||||
|
||||
var configWrapper = new NightConfigWrapper(conf);
|
||||
return new ModFileInfo((ModFile)iModFile, configWrapper, configWrapper::setFile, List.of());
|
||||
return new ModFileInfo((ModFile) iModFile, configWrapper, configWrapper::setFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+13
-12
@@ -6,7 +6,6 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager;
|
||||
@@ -28,6 +27,7 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -49,17 +49,19 @@ public class ModDiscoverer {
|
||||
modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
|
||||
dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
|
||||
modLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> modLocators, sce->LOGGER.error("Failed to load mod locator list", sce)).collect(Collectors.toList());
|
||||
modLocatorList.forEach(l->l.initArguments(arguments));
|
||||
for (IModLocator iModLocator : modLocatorList) {
|
||||
iModLocator.initArguments(arguments);
|
||||
}
|
||||
dependencyLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> dependencyLocators, sce->LOGGER.error("Failed to load dependency locator list", sce)).collect(Collectors.toList());
|
||||
dependencyLocatorList.forEach(l->l.initArguments(arguments));
|
||||
for (IDependencyLocator l : dependencyLocatorList) {
|
||||
l.initArguments(arguments);
|
||||
}
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.CORE))
|
||||
{
|
||||
LOGGER.debug(LogMarkers.CORE, "Found Mod Locators : {}", modLocatorList.stream()
|
||||
.map(modLocator -> "(%s:%s)".formatted(modLocator.name(),
|
||||
modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
|
||||
}
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.CORE))
|
||||
{
|
||||
|
||||
LOGGER.debug(LogMarkers.CORE, "Found Dependency Locators : {}", dependencyLocatorList.stream()
|
||||
.map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(),
|
||||
dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
|
||||
@@ -126,7 +128,7 @@ public class ModDiscoverer {
|
||||
}
|
||||
|
||||
//First processing run of the mod list. Any duplicates will cause resolution failure and dependency loading will be skipped.
|
||||
Map<IModFile.Type, List<ModFile>> modFilesMap = Maps.newHashMap();
|
||||
Map<IModFile.Type, List<ModFile>> modFilesMap = new EnumMap<>(IModFile.Type.class);
|
||||
try {
|
||||
final UniqueModListBuilder modsUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
|
||||
final UniqueModListBuilder.UniqueModListData uniqueModsData = modsUniqueListBuilder.buildUniqueList();
|
||||
@@ -134,7 +136,7 @@ public class ModDiscoverer {
|
||||
//Grab the temporary results.
|
||||
//This allows loading to continue to a base state, in case dependency loading fails.
|
||||
modFilesMap = uniqueModsData.modFiles().stream()
|
||||
.collect(Collectors.groupingBy(IModFile::getType));
|
||||
.collect(Collectors.groupingBy(IModFile::getType, () -> new EnumMap<>(IModFile.Type.class), Collectors.toList()));
|
||||
loadedFiles = uniqueModsData.modFiles();
|
||||
}
|
||||
catch (EarlyLoadingException exception) {
|
||||
@@ -171,11 +173,11 @@ public class ModDiscoverer {
|
||||
|
||||
//We now only need the mod files map, not the list.
|
||||
modFilesMap = uniqueModsAndDependenciesData.modFiles().stream()
|
||||
.collect(Collectors.groupingBy(IModFile::getType));
|
||||
.collect(Collectors.groupingBy(IModFile::getType, () -> new EnumMap<>(IModFile.Type.class), Collectors.toList()));
|
||||
} catch (EarlyLoadingException exception) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Failed to build unique mod list after dependency discovery.", exception);
|
||||
discoveryErrorData.addAll(exception.getAllData());
|
||||
modFilesMap = loadedFiles.stream().collect(Collectors.groupingBy(IModFile::getType));
|
||||
modFilesMap = loadedFiles.stream().collect(Collectors.groupingBy(IModFile::getType, () -> new EnumMap<>(IModFile.Type.class), Collectors.toList()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -190,8 +192,7 @@ public class ModDiscoverer {
|
||||
return validator;
|
||||
}
|
||||
|
||||
private void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles)
|
||||
{
|
||||
private static void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles) {
|
||||
var locatedModFiles = locatedFiles.stream().filter(ModFile.class::isInstance).map(ModFile.class::cast).toList();
|
||||
for (IModFile mf : locatedModFiles) {
|
||||
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
|
||||
|
||||
@@ -45,7 +45,6 @@ public class ModFile implements IModFile {
|
||||
private Throwable scanError;
|
||||
private final SecureJar jar;
|
||||
private final Type modFileType;
|
||||
private final Manifest manifest;
|
||||
private final IModProvider provider;
|
||||
private IModFileInfo modFileInfo;
|
||||
private ModFileScanData fileModFileScanData;
|
||||
@@ -65,7 +64,7 @@ public class ModFile implements IModFile {
|
||||
this.jar = jar;
|
||||
this.parser = parser;
|
||||
|
||||
manifest = this.jar.moduleDataProvider().getManifest();
|
||||
var manifest = this.jar.moduleDataProvider().getManifest();
|
||||
modFileType = Type.valueOf(type);
|
||||
jarVersion = Optional.ofNullable(manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION)).orElse("0.0NONE");
|
||||
this.modFileInfo = ModFileParser.readModList(this, this.parser);
|
||||
|
||||
+9
-4
@@ -104,11 +104,11 @@ public class ModFileInfo implements IModFileInfo, IConfigurable {
|
||||
this.modFile.setFileProperties(this.properties);
|
||||
|
||||
final List<? extends IConfigurable> modConfigs = config.getConfigList("mods");
|
||||
if (modConfigs.isEmpty())
|
||||
if (modConfigs == null || modConfigs.isEmpty())
|
||||
throw new InvalidModFileException("Missing mods list", this);
|
||||
|
||||
this.mods = modConfigs.stream()
|
||||
.map(mi-> (IModInfo)new ModInfo(this, mi))
|
||||
.map(mi -> (IModInfo) ModInfo.of(this, mi))
|
||||
.toList();
|
||||
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.LOADING)) {
|
||||
@@ -158,6 +158,11 @@ public class ModFileInfo implements IModFileInfo, IConfigurable {
|
||||
return this.showAsDataPack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(final String key) {
|
||||
return this.config.getConfigElement(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(final String... key) {
|
||||
return this.config.getConfigElement(key);
|
||||
@@ -186,7 +191,7 @@ public class ModFileInfo implements IModFileInfo, IConfigurable {
|
||||
return Strings.isNullOrEmpty(license);
|
||||
}
|
||||
|
||||
private final char[] HEX = "0123456789ABCDEF".toCharArray();
|
||||
private static final char[] HEX = "0123456789ABCDEF".toCharArray();
|
||||
public Optional<String> getCodeSigningFingerprint() {
|
||||
var signers = this.modFile.getSecureJar().getManifestSigners();
|
||||
if (signers == null)
|
||||
@@ -254,7 +259,7 @@ public class ModFileInfo implements IModFileInfo, IConfigurable {
|
||||
|
||||
@Override
|
||||
public String moduleName() {
|
||||
return getMods().get(0).getModId();
|
||||
return this.modFile.getSecureJar().name();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+143
-197
@@ -27,211 +27,181 @@ import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ModInfo implements IModInfo, IConfigurable {
|
||||
public record ModInfo(
|
||||
ModFileInfo getOwningFile,
|
||||
IConfigurable getConfig,
|
||||
|
||||
String getModId,
|
||||
String getNamespace,
|
||||
ArtifactVersion getVersion,
|
||||
|
||||
String getDisplayName,
|
||||
String getDescription,
|
||||
Optional<String> getLogoFile,
|
||||
boolean getLogoBlur,
|
||||
Optional<URL> getUpdateURL,
|
||||
Optional<URL> getModURL,
|
||||
|
||||
Holder<List<? extends ModVersion>> dependencies,
|
||||
Holder<List<ForgeFeature.Bound>> forgeFeatures,
|
||||
Map<String, Object> getModProperties
|
||||
) implements IModInfo, IConfigurable {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final DefaultArtifactVersion DEFAULT_VERSION = new DefaultArtifactVersion("1");
|
||||
private static final Pattern VALID_MODID = Pattern.compile("^[a-z][a-z0-9_]{1,63}$");
|
||||
private static final Pattern VALID_NAMESPACE = Pattern.compile("^[a-z][a-z0-9_.-]{1,63}$");
|
||||
private static final Pattern VALID_VERSION = Pattern.compile("^\\d+.*");
|
||||
|
||||
private final ModFileInfo owningFile;
|
||||
private final String modId;
|
||||
private final String namespace;
|
||||
private final ArtifactVersion version;
|
||||
private final String displayName;
|
||||
private final String description;
|
||||
private final Optional<String> logoFile;
|
||||
private final boolean logoBlur;
|
||||
private final Optional<URL> updateJSONURL;
|
||||
private final List<? extends IModInfo.ModVersion> dependencies;
|
||||
|
||||
private final List<ForgeFeature.Bound> features;
|
||||
private final Map<String,Object> properties;
|
||||
private final IConfigurable config;
|
||||
private final Optional<URL> modUrl;
|
||||
|
||||
public ModInfo(final ModFileInfo owningFile, final IConfigurable config) {
|
||||
Optional<ModFileInfo> ownFile = Optional.ofNullable(owningFile);
|
||||
this.owningFile = owningFile;
|
||||
this.config = config;
|
||||
|
||||
public static ModInfo of(ModFileInfo owningFile, IConfigurable config) {
|
||||
// These are sourced from the mod specific [[mod]] entry
|
||||
this.modId = config.<String>getConfigElement("modId")
|
||||
.orElseThrow(() -> new InvalidModFileException("Missing modId", owningFile));
|
||||
String modId = config.<String>getConfigElement("modId").orElse(null);
|
||||
if (modId == null)
|
||||
throw new InvalidModFileException("Missing modId", owningFile);
|
||||
|
||||
// verify we have a valid modid
|
||||
if (!VALID_MODID.matcher(this.modId).matches()) {
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid modId found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.modId, VALID_MODID.pattern());
|
||||
throw new InvalidModFileException("Invalid modId found : " + this.modId, owningFile);
|
||||
if (!VALID_MODID.matcher(modId).matches()) {
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid modId found in file {} - {} does not match the standard: {}", owningFile.getFile().getFilePath(), modId, VALID_MODID.pattern());
|
||||
throw new InvalidModFileException("Invalid modId found : " + modId, owningFile);
|
||||
}
|
||||
|
||||
this.namespace = config.<String>getConfigElement("namespace")
|
||||
.orElse(this.modId);
|
||||
String namespace = config.<String>getConfigElement("namespace")
|
||||
.orElse(modId);
|
||||
|
||||
// verify our namespace is valid
|
||||
if (!VALID_NAMESPACE.matcher(this.namespace).matches()) {
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid override namespace found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.namespace, VALID_NAMESPACE.pattern());
|
||||
throw new InvalidModFileException("Invalid override namespace found : " + this.namespace, owningFile);
|
||||
if (!VALID_NAMESPACE.matcher(namespace).matches()) {
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid override namespace found in file {} - {} does not match the standard: {}", owningFile.getFile().getFilePath(), namespace, VALID_NAMESPACE.pattern());
|
||||
throw new InvalidModFileException("Invalid override namespace found : " + namespace, owningFile);
|
||||
}
|
||||
|
||||
this.version = config.<String>getConfigElement("version")
|
||||
.map(s -> StringSubstitutor.replace(s, ownFile.map(ModFileInfo::getFile).orElse(null)))
|
||||
ArtifactVersion version = config.<String>getConfigElement("version")
|
||||
.map(s -> StringSubstitutor.replace(s, owningFile.getFile()))
|
||||
.map(DefaultArtifactVersion::new)
|
||||
.orElse(DEFAULT_VERSION);
|
||||
|
||||
// verify we have a valid mod version
|
||||
if (!VALID_VERSION.matcher(this.version.toString()).matches())
|
||||
throw new InvalidModFileException("Illegal version number specified " + this.version, this.getOwningFile());
|
||||
if (!VALID_VERSION.matcher(version.toString()).matches())
|
||||
throw new InvalidModFileException("Illegal version number specified " + version, owningFile);
|
||||
|
||||
// The remaining properties all default to sensible values and are not essential
|
||||
this.displayName = config.<String>getConfigElement("displayName")
|
||||
.orElse(this.modId);
|
||||
this.description = config.<String>getConfigElement("description")
|
||||
String displayName = config.<String>getConfigElement("displayName")
|
||||
.orElse(modId);
|
||||
|
||||
String description = config.<String>getConfigElement("description")
|
||||
.orElse("MISSING DESCRIPTION")
|
||||
.replace("\r\n", "\n").stripIndent();
|
||||
this.logoFile = Optional.ofNullable(config.<String>getConfigElement("logoFile")
|
||||
.orElseGet(() -> ownFile.flatMap(mf -> mf.<String>getConfigElement("logoFile"))
|
||||
.orElse(null)));
|
||||
this.logoBlur = config.<Boolean>getConfigElement("logoBlur")
|
||||
.orElseGet(() -> ownFile.flatMap(mf -> mf.<Boolean>getConfigElement("logoBlur"))
|
||||
.orElse(true));
|
||||
this.updateJSONURL = config.<String>getConfigElement("updateJSONURL")
|
||||
.map(StringUtils::toURL);
|
||||
this.modUrl = config.<String>getConfigElement("modUrl")
|
||||
|
||||
Optional<String> logoFile = Optional.ofNullable(
|
||||
config.<String>getConfigElement("logoFile")
|
||||
.orElseGet(() -> owningFile.<String>getConfigElement("logoFile").orElse(null))
|
||||
);
|
||||
|
||||
Boolean logoBlur = config.<Boolean>getConfigElement("logoBlur").orElse(null);
|
||||
if (logoBlur == null)
|
||||
logoBlur = owningFile.<Boolean>getConfigElement("logoBlur").orElse(true);
|
||||
|
||||
Optional<URL> updateJSONURL = config.<String>getConfigElement("updateJSONURL")
|
||||
.map(StringUtils::toURL);
|
||||
|
||||
// These are sourced from the file rather than the mod-specific block, but with a modid tag
|
||||
if (ownFile.isEmpty()) {
|
||||
this.dependencies = Collections.emptyList();
|
||||
this.features = Collections.emptyList();
|
||||
this.properties = Collections.emptyMap();
|
||||
} else {
|
||||
var deps = this.owningFile.getConfigList("dependencies", this.modId);
|
||||
if (deps == null || deps.isEmpty()) {
|
||||
this.dependencies = Collections.emptyList();
|
||||
} else {
|
||||
var tmp = new ArrayList<ModVersion>();
|
||||
for (var dep : deps)
|
||||
tmp.add(new ModVersion(this, dep));
|
||||
this.dependencies = Collections.unmodifiableList(tmp);
|
||||
}
|
||||
Optional<URL> modUrl = config.<String>getConfigElement("modUrl")
|
||||
.map(StringUtils::toURL);
|
||||
|
||||
var feats = this.owningFile.<Map<String, Object>>getConfigElement("features", this.modId).orElse(null);
|
||||
if (feats == null) {
|
||||
this.features = Collections.emptyList();
|
||||
} else {
|
||||
var tmp = new ArrayList<ForgeFeature.Bound>();
|
||||
for (var entry : feats.entrySet()) {
|
||||
if (!(entry.getValue() instanceof String val))
|
||||
throw new InvalidModFileException("Invalid feature bound {" + entry.getValue() + "} for key {" + entry.getKey() + "} only strings are accepted", this.owningFile);
|
||||
tmp.add(new ForgeFeature.Bound(entry.getKey(), val, this));
|
||||
}
|
||||
this.features = Collections.unmodifiableList(tmp);
|
||||
}
|
||||
// dependencies and features are done after the constructor as they need to reference the ModInfo we are creating
|
||||
List<? extends ModVersion> dependencies = Collections.emptyList();
|
||||
List<ForgeFeature.Bound> forgeFeatures = Collections.emptyList();
|
||||
|
||||
var props = this.owningFile.<Map<String, Object>>getConfigElement("modproperties", this.modId).orElse(null);
|
||||
if (props == null)
|
||||
this.properties = Collections.emptyMap();
|
||||
else
|
||||
this.properties = Collections.unmodifiableMap(props);
|
||||
Map<String, Object> modProperties = owningFile.<Map<String, Object>>getConfigElement("modproperties", modId)
|
||||
.map(Collections::unmodifiableMap)
|
||||
.orElse(Collections.emptyMap());
|
||||
|
||||
return new ModInfo(
|
||||
owningFile, config,
|
||||
modId, namespace, version,
|
||||
displayName, description, logoFile, logoBlur, updateJSONURL, modUrl,
|
||||
new Holder<>(dependencies), new Holder<>(forgeFeatures), modProperties
|
||||
).setupDependencies().setupForgeFeatures();
|
||||
}
|
||||
|
||||
private ModInfo setupDependencies() {
|
||||
var deps = getOwningFile.getConfigList("dependencies", getModId);
|
||||
if (deps == null || deps.isEmpty()) {
|
||||
dependencies.value = Collections.emptyList();
|
||||
return this;
|
||||
}
|
||||
|
||||
var tmp = new ModVersion[deps.size()];
|
||||
for (int i = 0; i < deps.size(); i++) {
|
||||
tmp[i] = ModVersion.of(this, deps.get(i));
|
||||
}
|
||||
dependencies.value = List.of(tmp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModFileInfo getOwningFile() {
|
||||
return owningFile;
|
||||
}
|
||||
private ModInfo setupForgeFeatures() {
|
||||
var feats = getOwningFile.<Map<String, Object>>getConfigElement("features", getModId).orElse(null);
|
||||
if (feats == null) {
|
||||
forgeFeatures.value = Collections.emptyList();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModId() {
|
||||
return modId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArtifactVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IModInfo.ModVersion> getDependencies() {
|
||||
return this.dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return this.namespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getModProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> getUpdateURL() {
|
||||
return this.updateJSONURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getLogoFile() {
|
||||
return this.logoFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getLogoBlur() {
|
||||
return this.logoBlur;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IConfigurable getConfig() {
|
||||
var tmp = new ArrayList<ForgeFeature.Bound>();
|
||||
for (var entry : feats.entrySet()) {
|
||||
if (!(entry.getValue() instanceof String val))
|
||||
throw new InvalidModFileException("Invalid feature bound {" + entry.getValue() + "} for key {" + entry.getKey() + "} only strings are accepted", getOwningFile);
|
||||
tmp.add(new ForgeFeature.Bound(entry.getKey(), val, this));
|
||||
}
|
||||
forgeFeatures.value = List.copyOf(tmp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends ForgeFeature.Bound> getForgeFeatures() {
|
||||
return this.features;
|
||||
public <T> Optional<T> getConfigElement(String key) {
|
||||
return getConfig.getConfigElement(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(final String... key) {
|
||||
return this.config.getConfigElement(key);
|
||||
public <T> Optional<T> getConfigElement(String... key) {
|
||||
return getConfig.getConfigElement(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(final String... key) {
|
||||
public List<? extends IConfigurable> getConfigList(String... key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> getModURL() {
|
||||
return modUrl;
|
||||
public List<? extends IModInfo.ModVersion> getDependencies() {
|
||||
return dependencies.value;
|
||||
}
|
||||
|
||||
class ModVersion implements net.minecraftforge.forgespi.language.IModInfo.ModVersion {
|
||||
private IModInfo owner;
|
||||
private final String modId;
|
||||
private final VersionRange versionRange;
|
||||
private final boolean mandatory;
|
||||
private final Ordering ordering;
|
||||
private final DependencySide side;
|
||||
private final Optional<URL> referralUrl;
|
||||
@Override
|
||||
public List<? extends ForgeFeature.Bound> getForgeFeatures() {
|
||||
return forgeFeatures.value;
|
||||
}
|
||||
|
||||
public ModVersion(final IModInfo owner, final IConfigurable config) {
|
||||
this.owner = owner;
|
||||
this.modId = config.<String>getConfigElement("modId")
|
||||
.orElseThrow(()->new InvalidModFileException("Missing required field modid in dependency", getOwningFile()));
|
||||
private static final class Holder<T> {
|
||||
private T value;
|
||||
|
||||
if (this.modId.equals("forge")) {
|
||||
public Holder(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private record ModVersion(
|
||||
Holder<IModInfo> owner,
|
||||
|
||||
String getModId,
|
||||
VersionRange getVersionRange,
|
||||
boolean isMandatory,
|
||||
Ordering getOrdering,
|
||||
DependencySide getSide,
|
||||
Optional<URL> getReferralURL
|
||||
) implements IModInfo.ModVersion {
|
||||
public static ModVersion of(IModInfo owner, IConfigurable config) {
|
||||
var modId = config.<String>getConfigElement("modId").orElse(null);
|
||||
if (modId == null)
|
||||
throw new InvalidModFileException("Missing required field modid in dependency", owner.getOwningFile());
|
||||
|
||||
if (modId.equals("forge")) {
|
||||
var fileProps = owner.getOwningFile().getFileProperties();
|
||||
// Checking containsKey to avoid a possible exception if the property is not present (due to Collections.emptyMap())
|
||||
if (!fileProps.isEmpty() && fileProps.containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
|
||||
@@ -241,65 +211,41 @@ public class ModInfo implements IModInfo, IConfigurable {
|
||||
}
|
||||
}
|
||||
|
||||
var mandatory = config.<Boolean>getConfigElement("mandatory");
|
||||
if (mandatory.isPresent())
|
||||
this.mandatory = mandatory.get();
|
||||
boolean mandatory;
|
||||
var mandatoryValue = config.<Boolean>getConfigElement("mandatory");
|
||||
if (mandatoryValue.isPresent())
|
||||
mandatory = mandatoryValue.get();
|
||||
else if (owner.getOwningFile().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP))
|
||||
this.mandatory = true;
|
||||
mandatory = true;
|
||||
else
|
||||
throw new InvalidModFileException("Missing required field mandatory in dependency", getOwningFile());
|
||||
throw new InvalidModFileException("Missing required field mandatory in dependency", owner.getOwningFile());
|
||||
|
||||
this.versionRange = config.<String>getConfigElement("versionRange")
|
||||
var versionRange = config.<String>getConfigElement("versionRange")
|
||||
.map(MavenVersionAdapter::createFromVersionSpec)
|
||||
.orElse(UNBOUNDED);
|
||||
this.ordering = config.<String>getConfigElement("ordering")
|
||||
var ordering = config.<String>getConfigElement("ordering")
|
||||
.map(Ordering::valueOf)
|
||||
.orElse(Ordering.NONE);
|
||||
this.side = config.<String>getConfigElement("side")
|
||||
var side = config.<String>getConfigElement("side")
|
||||
.map(DependencySide::valueOf)
|
||||
.orElse(DependencySide.BOTH);
|
||||
this.referralUrl = config.<String>getConfigElement("referralUrl")
|
||||
var referralUrl = config.<String>getConfigElement("referralUrl")
|
||||
.map(StringUtils::toURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModId() {
|
||||
return modId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionRange getVersionRange() {
|
||||
return versionRange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMandatory() {
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ordering getOrdering() {
|
||||
return ordering;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DependencySide getSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwner(final IModInfo owner) {
|
||||
this.owner = owner;
|
||||
return new ModVersion(
|
||||
new Holder<>(owner),
|
||||
modId, versionRange, mandatory, ordering, side, referralUrl
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IModInfo getOwner() {
|
||||
return owner;
|
||||
return owner.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> getReferralURL() {
|
||||
return referralUrl;
|
||||
public void setOwner(IModInfo newOwner) {
|
||||
owner.value = newOwner;
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
-16
@@ -9,49 +9,64 @@ import cpw.mods.jarhandling.JarMetadata;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class ModJarMetadata implements JarMetadata {
|
||||
private static final String AUTOMATIC_MODULE_NAME = "Automatic-Module-Name";
|
||||
private IModFile modFile;
|
||||
private String name;
|
||||
private String version;
|
||||
private ModuleDescriptor descriptor;
|
||||
|
||||
ModJarMetadata() {
|
||||
}
|
||||
ModJarMetadata() { }
|
||||
|
||||
public void setModFile(IModFile file) {
|
||||
this.modFile = file;
|
||||
var mods = this.modFile.getModFileInfo().getMods();
|
||||
|
||||
if (!mods.isEmpty()) {
|
||||
var main = mods.get(0);
|
||||
this.name = main.getModId();
|
||||
this.version = main.getVersion().toString();
|
||||
}
|
||||
|
||||
var jar = file.getSecureJar();
|
||||
var aname = jar.moduleDataProvider().getManifest().getMainAttributes().getValue(AUTOMATIC_MODULE_NAME);
|
||||
if (aname != null)
|
||||
this.name = aname;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return modFile.getModFileInfo().moduleName();
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String version() {
|
||||
return modFile.getModFileInfo().versionString();
|
||||
return this.version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleDescriptor descriptor() {
|
||||
if (descriptor != null) return descriptor;
|
||||
if (descriptor != null)
|
||||
return descriptor;
|
||||
|
||||
var bld = ModuleDescriptor.newAutomaticModule(name())
|
||||
.version(version())
|
||||
.packages(modFile.getSecureJar().getPackages());
|
||||
modFile.getSecureJar().getProviders().stream()
|
||||
.filter(p -> !p.providers().isEmpty())
|
||||
.forEach(p -> bld.provides(p.serviceName(), p.providers()));
|
||||
modFile.getModFileInfo().usesServices().forEach(bld::uses);
|
||||
.version(version())
|
||||
.packages(modFile.getSecureJar().getPackages());
|
||||
|
||||
for (var provider : modFile.getSecureJar().getProviders()) {
|
||||
if (provider.providers().isEmpty())
|
||||
continue;
|
||||
|
||||
bld.provides(provider.serviceName(), provider.providers());
|
||||
}
|
||||
|
||||
descriptor = bld.build();
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public IModFile modFile() {
|
||||
return modFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
|
||||
+3
-3
@@ -64,7 +64,7 @@ public class ModValidator {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<ModFile> validateFiles(final List<ModFile> mods) {
|
||||
private static List<ModFile> validateFiles(final List<ModFile> mods) {
|
||||
final List<ModFile> brokenFiles = new ArrayList<>();
|
||||
for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext();) {
|
||||
ModFile modFile = iterator.next();
|
||||
@@ -106,8 +106,8 @@ public class ModValidator {
|
||||
|
||||
private List<EarlyLoadingException.ExceptionData> validateLanguages() {
|
||||
List<EarlyLoadingException.ExceptionData> errorData = new ArrayList<>();
|
||||
for (Iterator<ModFile> iterator = this.candidateMods.iterator(); iterator.hasNext(); ) {
|
||||
final ModFile modFile = iterator.next();
|
||||
for (Iterator<ModFile> iterator = this.candidateMods.iterator(); iterator.hasNext();) {
|
||||
var modFile = iterator.next();
|
||||
try {
|
||||
modFile.identifyLanguage();
|
||||
} catch (EarlyLoadingException e) {
|
||||
|
||||
+32
-19
@@ -18,11 +18,12 @@ import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
@ApiStatus.Internal
|
||||
class NightConfigWrapper implements IConfigurable {
|
||||
final class NightConfigWrapper implements IConfigurable {
|
||||
private final UnmodifiableConfig config;
|
||||
private IModFileInfo file;
|
||||
|
||||
@@ -30,39 +31,51 @@ class NightConfigWrapper implements IConfigurable {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
NightConfigWrapper setFile(IModFileInfo file) {
|
||||
private NightConfigWrapper(UnmodifiableConfig config, IModFileInfo file) {
|
||||
this.config = config;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
void setFile(IModFileInfo file) {
|
||||
this.file = file;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> getConfigElement(String key) {
|
||||
var path = List.of(key);
|
||||
return Optional.ofNullable(validate(this.config.get(path), path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(final String... key) {
|
||||
var path = asList(key);
|
||||
return this.config.getOptional(path).map(value -> {
|
||||
if (value instanceof UnmodifiableConfig cfg) {
|
||||
// New Night config doesn't implement valueMap(), so do a copy.
|
||||
var builder = ImmutableMap.builder();
|
||||
for (var e: cfg.entrySet())
|
||||
builder.put(e.getKey(), e.getValue());
|
||||
return (T)builder.build();
|
||||
} else if (value instanceof ArrayList<?> al && al.size() > 0 && al.get(0) instanceof UnmodifiableConfig) {
|
||||
throw new InvalidModFileException("The configuration path " + path + " is invalid. I wasn't expecting a multi-object list - remove one of the [[ ]]", file);
|
||||
}
|
||||
return (T) value;
|
||||
});
|
||||
return Optional.ofNullable(validate(this.config.get(path), path));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T validate(@Nullable T value, List<String> path) {
|
||||
if (value instanceof UnmodifiableConfig cfg) {
|
||||
// New Night config doesn't implement valueMap(), so do a copy.
|
||||
var entries = cfg.entrySet();
|
||||
var builder = ImmutableMap.builderWithExpectedSize(entries.size());
|
||||
for (var e : entries)
|
||||
builder.put(e.getKey(), e.getValue());
|
||||
return (T) builder.build();
|
||||
} else if (value instanceof ArrayList<?> al && !al.isEmpty() && al.getFirst() instanceof UnmodifiableConfig) {
|
||||
throw new InvalidModFileException("The configuration path " + path + " is invalid. I wasn't expecting a multi-object list - remove one of the [[ ]]", file);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(final String... key) {
|
||||
final List<String> path = asList(key);
|
||||
if (this.config.contains(path) && !(this.config.get(path) instanceof Collection)) {
|
||||
throw new InvalidModFileException("The configuration path "+path+" is invalid. Expecting a collection!", file);
|
||||
throw new InvalidModFileException("The configuration path " + path + " is invalid. Expecting a collection!", file);
|
||||
}
|
||||
final Collection<UnmodifiableConfig> nestedConfigs = this.config.getOrElse(path, ArrayList::new);
|
||||
return nestedConfigs.stream()
|
||||
.map(NightConfigWrapper::new)
|
||||
.map(cw->cw.setFile(file))
|
||||
.map(conf -> new NightConfigWrapper(conf, file))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -21,37 +21,36 @@ import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
@ApiStatus.Internal
|
||||
class Scanner {
|
||||
record Scanner(ModFile fileToScan, ModFileScanData result) {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final ModFile fileToScan;
|
||||
private static final boolean DEBUG = LOGGER.isDebugEnabled(LogMarkers.SCAN);
|
||||
|
||||
public Scanner(final ModFile fileToScan) {
|
||||
this.fileToScan = fileToScan;
|
||||
public Scanner(ModFile fileToScan) {
|
||||
this(fileToScan, new ModFileScanData());
|
||||
}
|
||||
|
||||
public ModFileScanData scan() {
|
||||
ModFileScanData result = new ModFileScanData();
|
||||
result.addModFileInfo(fileToScan.getModFileInfo());
|
||||
fileToScan.scanFile(p -> fileVisitor(p, result));
|
||||
fileToScan.scanFile(this::fileVisitor);
|
||||
final List<IModLanguageProvider> loaders = fileToScan.getLoaders();
|
||||
if (loaders != null) {
|
||||
loaders.forEach(loader -> {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scanning {} with language loader {}", fileToScan.getFilePath(), loader.name());
|
||||
for (IModLanguageProvider loader : loaders) {
|
||||
if (DEBUG) LOGGER.debug("Scanning {} with language loader {}", fileToScan.getFilePath(), loader.name());
|
||||
loader.getFileVisitor().accept(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void fileVisitor(final Path path, final ModFileScanData result) {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Scanning {} path {}", fileToScan, path);
|
||||
try (InputStream in = Files.newInputStream(path)){
|
||||
private void fileVisitor(final Path path) {
|
||||
try (InputStream in = Files.newInputStream(path)) {
|
||||
ModClassVisitor mcv = new ModClassVisitor();
|
||||
ClassReader cr = new ClassReader(in);
|
||||
cr.accept(mcv, 0);
|
||||
cr.accept(mcv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
mcv.buildData(result.getClasses(), result.getAnnotations());
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
// mark path bad
|
||||
if (DEBUG) LOGGER.warn("Failed scanning {} path {}", fileToScan, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,8 @@ import java.util.function.Supplier;
|
||||
@ApiStatus.Internal
|
||||
class ArgumentList {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private List<Supplier<String[]>> entries = new ArrayList<>();
|
||||
private Map<String, EntryValue> values = new HashMap<>();
|
||||
private final List<Supplier<String[]>> entries = new ArrayList<>();
|
||||
private final Map<String, EntryValue> values = new HashMap<>();
|
||||
|
||||
public static ArgumentList from(String... args) {
|
||||
ArgumentList ret = new ArgumentList();
|
||||
@@ -77,8 +77,8 @@ class ArgumentList {
|
||||
|
||||
public String[] getArguments() {
|
||||
return entries.stream()
|
||||
.flatMap(e -> Arrays.asList(e.get()).stream())
|
||||
.toArray(size -> new String[size]);
|
||||
.flatMap(e -> Arrays.stream(e.get()))
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public boolean hasValue(String key) {
|
||||
@@ -122,7 +122,7 @@ class ArgumentList {
|
||||
return ent.getValue();
|
||||
}
|
||||
|
||||
private class EntryValue implements Supplier<String[]> {
|
||||
private static final class EntryValue implements Supplier<String[]> {
|
||||
private final String prefix;
|
||||
private final String key;
|
||||
private final boolean split;
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
String username = args.get("username");
|
||||
if (username != null) { // Replace '#' placeholders with random numbers
|
||||
Matcher m = Pattern.compile("#+").matcher(username);
|
||||
StringBuffer replaced = new StringBuffer();
|
||||
StringBuilder replaced = new StringBuilder();
|
||||
while (m.find()) {
|
||||
m.appendReplacement(replaced, getRandomNumbers(m.group().length()));
|
||||
}
|
||||
|
||||
+2
-8
@@ -7,7 +7,6 @@ package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.ILaunchHandlerService;
|
||||
import cpw.mods.modlauncher.api.ITransformingClassLoaderBuilder;
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
@@ -19,7 +18,6 @@ import org.slf4j.Logger;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -47,10 +45,6 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
|
||||
|
||||
public abstract List<Path> getMinecraftPaths();
|
||||
|
||||
@Override
|
||||
public void configureTransformationClassLoader(final ITransformingClassLoaderBuilder builder) {
|
||||
}
|
||||
|
||||
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
|
||||
URI uri;
|
||||
try (var reader = layer.configuration().findModule("net.minecraftforge.fmlloader").orElseThrow().reference().open()) {
|
||||
@@ -79,7 +73,7 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
|
||||
protected static final LaunchType CLIENT = new LaunchType("client", "minecraft", "net.minecraft.client.main.Main", Dist.CLIENT, false);
|
||||
protected static final LaunchType DATA = new LaunchType("data", "minecraft", "net.minecraft.data.Main", Dist.CLIENT, true);
|
||||
protected static final LaunchType SERVER = new LaunchType("server", "minecraft", "net.minecraft.server.Main", Dist.DEDICATED_SERVER, false);
|
||||
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "forge", "net.minecraftforge.gametest.GameTestMain", Dist.DEDICATED_SERVER, false);
|
||||
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "net.minecraftforge.forge", "net.minecraftforge.gametest.GameTestMain", Dist.DEDICATED_SERVER, false);
|
||||
|
||||
protected void runTarget(String module, String target, final String[] arguments, final ModuleLayer layer) throws Throwable {
|
||||
var mod = layer.findModule(module).orElse(null);
|
||||
@@ -107,7 +101,7 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
|
||||
len += 2;
|
||||
}
|
||||
str = str.substring(0, str.length() - len);
|
||||
var path = Paths.get(URI.create(str));
|
||||
var path = Path.of(URI.create(str));
|
||||
return path;
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -9,7 +9,7 @@ import java.util.List;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
sealed abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
private ForgeDevLaunchHandler(LaunchType type) {
|
||||
super(type, "forge_dev_");
|
||||
}
|
||||
@@ -33,25 +33,25 @@ abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
return List.of(filtered);
|
||||
}
|
||||
|
||||
public static class Client extends ForgeDevLaunchHandler {
|
||||
public static final class Client extends ForgeDevLaunchHandler {
|
||||
public Client() {
|
||||
super(CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Data extends ForgeDevLaunchHandler {
|
||||
public static final class Data extends ForgeDevLaunchHandler {
|
||||
public Data() {
|
||||
super(DATA);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Server extends ForgeDevLaunchHandler {
|
||||
public static final class Server extends ForgeDevLaunchHandler {
|
||||
public Server() {
|
||||
super(SERVER);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerGameTest extends ForgeDevLaunchHandler {
|
||||
public static final class ServerGameTest extends ForgeDevLaunchHandler {
|
||||
public ServerGameTest() {
|
||||
super(SERVER_GAMETEST);
|
||||
}
|
||||
|
||||
+35
-19
@@ -35,7 +35,7 @@ import net.minecraftforge.fml.loading.moddiscovery.AbstractModProvider;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ForgeDevLocator extends AbstractModProvider implements IModLocator {
|
||||
public final class ForgeDevLocator extends AbstractModProvider implements IModLocator {
|
||||
private static final String PACK_META = "pack.mcmeta";
|
||||
|
||||
@Override
|
||||
@@ -60,7 +60,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
return ret;
|
||||
}
|
||||
|
||||
private List<Path> getMods() {
|
||||
private static List<Path> getMods() {
|
||||
// Forge is an exploded directory as well
|
||||
var minecraft = ForgeDevLaunchHandler.getPathFromResource("net/minecraft/client/Minecraft.class");
|
||||
var forge = ForgeDevLaunchHandler.getPathFromResource("net/minecraftforge/common/MinecraftForge.class");
|
||||
@@ -84,30 +84,31 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
return ret;
|
||||
}
|
||||
|
||||
private List<Path> explodeTestMods(Path path) {
|
||||
private static List<Path> explodeTestMods(Path path) {
|
||||
var mod = new ArrayList<Path>();
|
||||
var memory = Jimfs.newFileSystem();
|
||||
|
||||
// First lets find all class files, that have the @Mod annotation and map packages to modids.
|
||||
var packages = findTestModPackages(path);
|
||||
for (var entry : packages.entrySet()) {
|
||||
var pkg = entry.getKey();
|
||||
var pkg = entry.getKey() + '/';
|
||||
var modids = entry.getValue();
|
||||
|
||||
// Find resource directories for every mod in this group
|
||||
var resourcePaths = new LinkedHashSet<Path>();
|
||||
var paths = new LinkedHashSet<Path>();
|
||||
for (var modid : modids) {
|
||||
var rsc = path.resolve(modid);
|
||||
if (Files.exists(rsc))
|
||||
resourcePaths.add(rsc);
|
||||
paths.add(rsc);
|
||||
}
|
||||
|
||||
var root = memory.getPath(modids.iterator().next()); // use the first modid as our root
|
||||
buildModsToml(resourcePaths, modids, root);
|
||||
buildPackMeta(resourcePaths, root);
|
||||
buildModsToml(paths, modids, root);
|
||||
buildPackMeta(paths, root);
|
||||
moveModuleInfo(paths, root);
|
||||
|
||||
if (Files.exists(root))
|
||||
resourcePaths.add(root);
|
||||
paths.add(root);
|
||||
|
||||
// We want just the class files from the root of the input paths. So make a new union with a filter.
|
||||
var classes = UnionHelper.newFileSystem(
|
||||
@@ -121,14 +122,10 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
}, new Path[] { path }
|
||||
);
|
||||
|
||||
// And we want the resources, and none of the classes, just in case things get kinda fucky and somehow a package overlaps a sub package {shading?}
|
||||
var resources = UnionHelper.newFileSystem((name, base) -> !name.endsWith(".class"), resourcePaths.toArray(Path[]::new));
|
||||
paths.addFirst(classes.getRootDirectories().iterator().next());
|
||||
|
||||
// Union of unions, Yay!
|
||||
var union = UnionHelper.newFileSystem(null, new Path[] {
|
||||
classes.getRootDirectories().iterator().next(),
|
||||
resources.getRootDirectories().iterator().next()
|
||||
});
|
||||
var union = UnionHelper.newFileSystem(null, paths.stream().toArray(Path[]::new));
|
||||
mod.add(union.getRootDirectories().iterator().next());
|
||||
}
|
||||
|
||||
@@ -137,7 +134,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
|
||||
// Find all the @Mods so we can generate tomls, and so we can pick packages. Right now it doesn't allow mods in parent directories,
|
||||
// I could make it merge all the way up, but I think this would be fine.
|
||||
private Map<String, Set<String>> findTestModPackages(Path path) {
|
||||
private static Map<String, Set<String>> findTestModPackages(Path path) {
|
||||
var mods = new HashMap<String, Set<String>>();
|
||||
try (var files = Files.walk(path)) {
|
||||
var classes = files
|
||||
@@ -190,7 +187,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
}
|
||||
|
||||
// Builds or update the mods.toml file for all @Mods in this package
|
||||
private void buildModsToml(Set<Path> resources, Set<String> modids, Path root) {
|
||||
private static void buildModsToml(Set<Path> resources, Set<String> modids, Path root) {
|
||||
var toml = resources.stream()
|
||||
.map(p -> p.resolve(MODS_TOML))
|
||||
.filter(Files::exists)
|
||||
@@ -226,7 +223,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
}
|
||||
|
||||
for (var modid : modids) {
|
||||
if (!modlist.stream().anyMatch(c -> modid.equals(c.get("modId")))) {
|
||||
if (modlist.stream().noneMatch(c -> modid.equals(c.get("modId")))) {
|
||||
modified = true;
|
||||
var tmp = Config.inMemory();
|
||||
tmp.set("modId", modid);
|
||||
@@ -248,7 +245,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
}
|
||||
|
||||
// This is optional, it just hides a warning screen when starting up. I should probably remove this once I restructure how data gen for test mods work and make it generate there.
|
||||
private void buildPackMeta(Set<Path> paths, Path root) {
|
||||
private static void buildPackMeta(Set<Path> paths, Path root) {
|
||||
var existing = paths.stream()
|
||||
.map(p -> p.resolve(PACK_META))
|
||||
.filter(Files::exists)
|
||||
@@ -274,6 +271,25 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
}
|
||||
}
|
||||
|
||||
private static void moveModuleInfo(Set<Path> paths, Path root) {
|
||||
var existing = paths.stream()
|
||||
.map(p -> p.resolve("module-info.dat"))
|
||||
.filter(Files::exists)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (existing == null)
|
||||
return;
|
||||
|
||||
var target = root.resolve("module-info.class");
|
||||
try {
|
||||
Files.createDirectories(target.getParent());
|
||||
Files.copy(existing, target);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to copy module-info.dat to memory: " + target, e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Throwable, R> R sneak(Throwable e) throws E {
|
||||
throw (E)e;
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@ import java.util.List;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
|
||||
sealed abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
|
||||
protected ForgeProdLaunchHandler(LaunchType type) {
|
||||
super(type, "forge_");
|
||||
}
|
||||
@@ -18,7 +18,7 @@ abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
|
||||
@Override public String getNaming() { return "mcp"; }
|
||||
@Override public boolean isProduction() { return true; }
|
||||
|
||||
public static class Client extends ForgeProdLaunchHandler {
|
||||
public static final class Client extends ForgeProdLaunchHandler {
|
||||
public Client() {
|
||||
super(CLIENT);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public static class Server extends ForgeProdLaunchHandler {
|
||||
public static final class Server extends ForgeProdLaunchHandler {
|
||||
public Server() {
|
||||
super(SERVER);
|
||||
}
|
||||
|
||||
+5
-5
@@ -14,7 +14,7 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
|
||||
@ApiStatus.Internal
|
||||
abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
private ForgeUserdevLaunchHandler(LaunchType type) {
|
||||
super(type, "forge_userdev_");
|
||||
}
|
||||
@@ -33,25 +33,25 @@ abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
return List.of(minecraft);
|
||||
}
|
||||
|
||||
public static class Client extends ForgeUserdevLaunchHandler {
|
||||
public static final class Client extends ForgeUserdevLaunchHandler {
|
||||
public Client() {
|
||||
super(CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Data extends ForgeUserdevLaunchHandler {
|
||||
public static final class Data extends ForgeUserdevLaunchHandler {
|
||||
public Data() {
|
||||
super(DATA);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Server extends ForgeUserdevLaunchHandler {
|
||||
public static final class Server extends ForgeUserdevLaunchHandler {
|
||||
public Server() {
|
||||
super(SERVER);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerGameTest extends ForgeUserdevLaunchHandler {
|
||||
public static final class ServerGameTest extends ForgeUserdevLaunchHandler {
|
||||
public ServerGameTest() {
|
||||
super(SERVER_GAMETEST);
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import net.minecraftforge.fml.loading.moddiscovery.AbstractModProvider;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ForgeUserdevLocator extends AbstractModProvider implements IModLocator {
|
||||
public final class ForgeUserdevLocator extends AbstractModProvider implements IModLocator {
|
||||
@Override
|
||||
public String name() {
|
||||
return "forge_userdev_locator";
|
||||
|
||||
+1
-2
@@ -25,7 +25,7 @@ import java.util.Set;
|
||||
*/
|
||||
public class StronglyConnectedComponentDetector<T> {
|
||||
private final Graph<T> graph;
|
||||
private Map<T, Integer> ids;
|
||||
private final Map<T, Integer> ids = new HashMap<>();
|
||||
private T[] elements;
|
||||
private int[] dfn;
|
||||
private int[] low;
|
||||
@@ -49,7 +49,6 @@ public class StronglyConnectedComponentDetector<T> {
|
||||
private void calculate() {
|
||||
components = new HashSet<>();
|
||||
int t = 0;
|
||||
ids = new HashMap<>();
|
||||
Set<T> nodes = graph.nodes();
|
||||
elements = (T[]) new Object[nodes.size()];
|
||||
for (T node : nodes) {
|
||||
|
||||
+1
-1
@@ -14,4 +14,4 @@ MAPPING_VERSION=1.21.1
|
||||
// Set to true before the first build of a new MC version, so we don't do compatibility checks
|
||||
CHECK_COMPATIBILITY=false
|
||||
// Set to true to allow for fuzzy patching. Useful during updateing
|
||||
UPDATING=true
|
||||
UPDATING=false
|
||||
Vendored
BIN
Binary file not shown.
+2
-1
@@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -83,7 +83,8 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -130,10 +131,13 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
@@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -198,11 +202,11 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
||||
Vendored
+10
-10
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ dependencies {
|
||||
compileOnly(libs.nulls)
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(project(':fmlcore'))
|
||||
implementation(libs.unsafe)
|
||||
implementation(libs.securemodules)
|
||||
}
|
||||
|
||||
java {
|
||||
@@ -57,7 +59,7 @@ publishing {
|
||||
license PomUtils.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Automatic eventbus subscriber - reads {@link EventBusSubscriber}
|
||||
* annotations and passes the class instances to the {@link nBus}
|
||||
* annotations and passes the class instances to the {@link Bus}
|
||||
* defined by the annotation. Defaults to {@code MinecraftForge#EVENT_BUS}
|
||||
*/
|
||||
public class AutomaticEventSubscriber {
|
||||
|
||||
+2
-15
@@ -31,21 +31,8 @@ public class FMLJavaModLanguageProvider implements IModLanguageProvider
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static class FMLModTarget implements IModLanguageProvider.IModLanguageLoader {
|
||||
private record FMLModTarget(String className, String modId) implements IModLanguageProvider.IModLanguageLoader {
|
||||
private static final Logger LOGGER = FMLJavaModLanguageProvider.LOGGER;
|
||||
private final String className;
|
||||
private final String modId;
|
||||
|
||||
private FMLModTarget(String className, String modId)
|
||||
{
|
||||
this.className = className;
|
||||
this.modId = modId;
|
||||
}
|
||||
|
||||
public String getModId()
|
||||
{
|
||||
return modId;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@@ -94,7 +81,7 @@ public class FMLJavaModLanguageProvider implements IModLanguageProvider
|
||||
.filter(ad -> ad.annotationType().equals(MODANNOTATION))
|
||||
.peek(ad -> LOGGER.debug(SCAN, "Found @Mod class {} with id {}", ad.clazz().getClassName(), ad.annotationData().get("value")))
|
||||
.map(ad -> new FMLModTarget(ad.clazz().getClassName(), (String)ad.annotationData().get("value")))
|
||||
.collect(Collectors.toMap(FMLModTarget::getModId, Function.identity(), (a,b)->a));
|
||||
.collect(Collectors.toMap(FMLModTarget::modId, Function.identity(), (a,b)->a));
|
||||
scanResult.addLanguageLoader(modTargetMap);
|
||||
};
|
||||
}
|
||||
|
||||
+14
-1
@@ -8,7 +8,10 @@ package net.minecraftforge.fml.javafmlmod;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
|
||||
public class FMLJavaModLoadingContext
|
||||
/**
|
||||
* Use the context provided by your language loader in your mod's constructor
|
||||
*/
|
||||
public class FMLJavaModLoadingContext extends ModLoadingContext
|
||||
{
|
||||
private final FMLModContainer container;
|
||||
|
||||
@@ -24,11 +27,21 @@ public class FMLJavaModLoadingContext
|
||||
return container.getEventBus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link FMLModContainer} by default.
|
||||
*/
|
||||
@Override
|
||||
public FMLModContainer getContainer() {
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the right instance from the {@link ModLoadingContext} correctly.
|
||||
* @return The FMLJavaMod language specific extension from the ModLoadingContext
|
||||
*
|
||||
* @deprecated use {@link FMLJavaModLoadingContext} in your mod constructor
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
public static FMLJavaModLoadingContext get() {
|
||||
return ModLoadingContext.get().extension();
|
||||
}
|
||||
|
||||
+87
-8
@@ -16,14 +16,21 @@ import net.minecraftforge.fml.ModLoadingStage;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
import net.minecraftforge.unsafe.UnsafeHacks;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.Attributes;
|
||||
|
||||
public class FMLModContainer extends ModContainer {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
@@ -32,38 +39,110 @@ public class FMLModContainer extends ModContainer {
|
||||
private final IEventBus eventBus;
|
||||
private Object modInstance;
|
||||
private final Class<?> modClass;
|
||||
private final FMLJavaModLoadingContext context = new FMLJavaModLoadingContext(this);
|
||||
|
||||
public FMLModContainer(IModInfo info, String className, ModFileScanData modFileScanResults, ModuleLayer gameLayer) {
|
||||
super(info);
|
||||
LOGGER.debug(LOADING,"Creating FMLModContainer instance for {}", className);
|
||||
this.scanResults = modFileScanResults;
|
||||
activityMap.put(ModLoadingStage.CONSTRUCT, this::constructMod);
|
||||
this.eventBus = BusBuilder.builder().setExceptionHandler(this::onEventFailed).setTrackPhases(false).markerType(IModBusEvent.class).useModLauncher().build();
|
||||
this.eventBus = BusBuilder.builder().setExceptionHandler(FMLModContainer::onEventFailed).setTrackPhases(false).markerType(IModBusEvent.class).useModLauncher().build();
|
||||
this.configHandler = Optional.of(ce->this.eventBus.post(ce.self()));
|
||||
final FMLJavaModLoadingContext contextExtension = new FMLJavaModLoadingContext(this);
|
||||
this.contextExtension = () -> contextExtension;
|
||||
|
||||
|
||||
this.contextExtension = () -> context;
|
||||
try {
|
||||
var moduleName = info.getOwningFile().moduleName();
|
||||
var module = gameLayer.findModule(moduleName)
|
||||
.orElseThrow(() -> new IllegalStateException("Failed to find " + moduleName + " in " + gameLayer));
|
||||
|
||||
openModules(gameLayer, module, info.getOwningFile().getFile().getSecureJar());
|
||||
|
||||
modClass = Class.forName(module, className);
|
||||
LOGGER.trace(LOADING,"Loaded modclass {} with {}", modClass.getName(), modClass.getClassLoader());
|
||||
LOGGER.debug(LOADING,"Loaded modclass {}/{} with {}", modClass.getModule().getName(), modClass.getName(), modClass.getClassLoader());
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error(LOADING, "Failed to load class {}", className, e);
|
||||
throw new ModLoadingException(info, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmodclass", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void onEventFailed(IEventBus iEventBus, Event event, IEventListener[] iEventListeners, int i, Throwable throwable) {
|
||||
/**
|
||||
* <pre>
|
||||
* Reads the Add-Exports and Add-Opens attributes (see note) from a mod file and
|
||||
* attempts to apply them. This differers from the JEP in two significant ways:
|
||||
* 1) Instead of opening things to ALL-UNNAMED it instead only opens to the
|
||||
* mod's module itself. Because I don't want other mods accidently relying
|
||||
* on transitive behavior
|
||||
* 2) It is read from all mods not just the executable jars.
|
||||
*
|
||||
* From <a href = "https://openjdk.org/jeps/261">JEP 261: Module System</a>
|
||||
*
|
||||
* Two new JDK-specific JAR-file manifest attributes are defined to correspond
|
||||
* to the --add-exports and --add-opens command-line options:
|
||||
* Add-Exports: <module>/<package>( <module>/<package>)*
|
||||
* Add-Opens: <module>/<package>( <module>/<package>)*
|
||||
*
|
||||
* The value of each attribute is a space-separated list of slash-separated
|
||||
* module-name/package-name pairs. A >module</>package< pair in
|
||||
* the value of an Add-Exports attribute has the same meaning as the
|
||||
* command-line option --add-exports >module</>package<=ALL-UNNAMED.
|
||||
*
|
||||
* A >module</>package< pair in the value of an Add-Opens attribute has the
|
||||
* same meaning as the command-line option --add-opens >module</>package<=ALL-UNNAMED.
|
||||
*
|
||||
* Each attribute can occur at most once, in the main section of a MANIFEST.MF file.
|
||||
* A particular pair can be listed more than once. If a specified module was not
|
||||
* resolved, or if a specified package does not exist, then the corresponding pair
|
||||
* is ignored.
|
||||
*/
|
||||
private static void openModules(ModuleLayer layer, Module self, SecureJar jar) throws NoSuchMethodException, SecurityException, IllegalAccessException, InvocationTargetException {
|
||||
var manifest = jar.moduleDataProvider().getManifest().getMainAttributes();
|
||||
addOpenOrExports(layer, self, true, manifest);
|
||||
addOpenOrExports(layer, self, false, manifest);
|
||||
}
|
||||
|
||||
private static void addOpenOrExports(ModuleLayer layer, Module self, boolean open, Attributes attrs) throws NoSuchMethodException, SecurityException, IllegalAccessException, InvocationTargetException {
|
||||
var key = open ? "Add-Opens" : "Add-Exports";
|
||||
var entry = attrs.getValue(key);
|
||||
if (entry == null)
|
||||
return;
|
||||
|
||||
for (var pair : entry.split(" ")) {
|
||||
var pts = pair.trim().split("/");
|
||||
if (pts.length == 2) {
|
||||
var target = layer.findModule(pts[0]).orElse(null);
|
||||
if (target == null || !target.getDescriptor().packages().contains(pts[1]))
|
||||
continue;
|
||||
addOpenOrExport(target, pts[1], self, open);
|
||||
} else {
|
||||
LOGGER.warn(LOADING, "Invalid {} entry in {}: {}", key, self.getName(), pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Method implAddExportsOrOpens;
|
||||
private static void addOpenOrExport(Module target, String pkg, Module reader, boolean open) throws NoSuchMethodException, SecurityException, IllegalAccessException, InvocationTargetException {
|
||||
if (implAddExportsOrOpens == null) {
|
||||
implAddExportsOrOpens = Module.class.getDeclaredMethod("implAddExportsOrOpens", String.class, Module.class, boolean.class, boolean.class);
|
||||
UnsafeHacks.setAccessible(implAddExportsOrOpens);
|
||||
}
|
||||
|
||||
LOGGER.info(LOADING, "{} {}/{} to {}", open ? "Opening" : "Exporting", target.getName(), pkg, reader.getName());
|
||||
implAddExportsOrOpens.invoke(target, pkg, reader, open, /*syncVM*/true);
|
||||
}
|
||||
|
||||
private static void onEventFailed(IEventBus iEventBus, Event event, IEventListener[] iEventListeners, int i, Throwable throwable) {
|
||||
LOGGER.error(new EventBusErrorMessage(event, i, iEventListeners, throwable));
|
||||
}
|
||||
|
||||
private void constructMod() {
|
||||
try {
|
||||
LOGGER.trace(LOADING, "Loading mod instance {} of type {}", getModId(), modClass.getName());
|
||||
this.modInstance = modClass.getDeclaredConstructor().newInstance();
|
||||
Constructor<?> constructor;
|
||||
try {
|
||||
constructor = modClass.getDeclaredConstructor(context.getClass());
|
||||
} catch (NoSuchMethodException | SecurityException exception) {
|
||||
constructor = modClass.getDeclaredConstructor();
|
||||
}
|
||||
this.modInstance = constructor.getParameterCount() == 0 ? constructor.newInstance() : constructor.newInstance(context);
|
||||
LOGGER.trace(LOADING, "Loaded mod instance {} of type {}", getModId(), modClass.getName());
|
||||
} catch (Throwable e) {
|
||||
// When a mod constructor throws an exception, it's wrapped in an InvocationTargetException which hides the
|
||||
|
||||
+1
-8
@@ -21,14 +21,12 @@ import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
|
||||
public class LowCodeModContainer extends ModContainer
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final ModFileScanData scanResults;
|
||||
private Object modInstance;
|
||||
private final Object modInstance;
|
||||
|
||||
public LowCodeModContainer(IModInfo info, ModFileScanData modFileScanResults, ModuleLayer gameLayer)
|
||||
{
|
||||
super(info);
|
||||
LOGGER.debug(LOADING, "Creating LowCodeModContainer for {}", info.getModId());
|
||||
this.scanResults = modFileScanResults;
|
||||
this.modInstance = new Object();
|
||||
this.contextExtension = () -> null;
|
||||
this.extensionPoints.remove(IExtensionPoint.DisplayTest.class);
|
||||
@@ -45,9 +43,4 @@ public class LowCodeModContainer extends ModContainer
|
||||
{
|
||||
return modInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T extends Event & IModBusEvent> void acceptEvent(final T e)
|
||||
{
|
||||
}
|
||||
}
|
||||
-4
@@ -5,7 +5,6 @@
|
||||
|
||||
package net.minecraftforge.fml.mclanguageprovider;
|
||||
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.forgespi.language.ILifecycleEvent;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
@@ -15,14 +14,11 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider.IModLanguageLoader;
|
||||
|
||||
public class MinecraftModLanguageProvider implements IModLanguageProvider {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
@Override
|
||||
|
||||
+4
-3
@@ -141,9 +141,6 @@ dependencies {
|
||||
// For more info:
|
||||
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
|
||||
// http://www.gradle.org/docs/current/userguide/dependency_management.html
|
||||
|
||||
// Hack fix for now, force jopt-simple to be exactly 5.0.4 because Mojang ships that version, but some transitive dependencies request 6.0+
|
||||
implementation('net.sf.jopt-simple:jopt-simple:5.0.4') { version { strictly '5.0.4' } }
|
||||
}
|
||||
|
||||
// This block of code expands all declared replace properties in the specified resource targets.
|
||||
@@ -197,6 +194,10 @@ tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
|
||||
}
|
||||
|
||||
// IntelliJ no longer downloads javadocs and sources by default.
|
||||
// This tells Gradle to force IDEA to do it.
|
||||
idea.module { downloadJavadoc = downloadSources = true }
|
||||
|
||||
eclipse {
|
||||
// Run everytime eclipse builds the code
|
||||
//autoBuildTasks genEclipseRuns
|
||||
|
||||
@@ -18,7 +18,6 @@ import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
|
||||
import net.minecraftforge.event.server.ServerStartingEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
@@ -61,9 +60,9 @@ public class ExampleMod
|
||||
output.accept(EXAMPLE_ITEM.get()); // Add the example item to the tab. For your own tabs, this method is preferred over the event
|
||||
}).build());
|
||||
|
||||
public ExampleMod()
|
||||
public ExampleMod(FMLJavaModLoadingContext context)
|
||||
{
|
||||
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
IEventBus modEventBus = context.getModEventBus();
|
||||
|
||||
// Register the commonSetup method for modloading
|
||||
modEventBus.addListener(this::commonSetup);
|
||||
@@ -82,7 +81,7 @@ public class ExampleMod
|
||||
modEventBus.addListener(this::addCreative);
|
||||
|
||||
// Register our mod's ForgeConfigSpec so that Forge can create and load the config file for us
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, Config.SPEC);
|
||||
context.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
|
||||
}
|
||||
|
||||
private void commonSetup(final FMLCommonSetupEvent event)
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
@DontObfuscate
|
||||
public static String getClientModName() {
|
||||
- return "vanilla";
|
||||
+ return net.minecraftforge.internal.BrandingControl.getClientBranding();
|
||||
+ return net.minecraftforge.internal.BrandingControl.getBranding();
|
||||
}
|
||||
}
|
||||
@@ -151,6 +151,14 @@
|
||||
this.screen = p_91153_;
|
||||
if (this.screen != null) {
|
||||
this.screen.added();
|
||||
@@ -1088,6 +_,7 @@
|
||||
this.resourceManager.close();
|
||||
FreeTypeUtil.destroy();
|
||||
Util.shutdownExecutors();
|
||||
+ net.minecraftforge.fml.config.ConfigTracker.INSTANCE.forceUnload();
|
||||
} catch (Throwable throwable) {
|
||||
LOGGER.error("Shutdown failure!", throwable);
|
||||
throw throwable;
|
||||
@@ -1155,9 +_,11 @@
|
||||
this.mouseHandler.handleAccumulatedMovement();
|
||||
this.profiler.pop();
|
||||
|
||||
@@ -46,30 +46,28 @@
|
||||
this.minecraft.getProfiler().push("selectedItemName");
|
||||
if (this.toolHighlightTimer > 0 && !this.lastToolHighlight.isEmpty()) {
|
||||
MutableComponent mutablecomponent = Component.empty().append(this.lastToolHighlight.getHoverName()).withStyle(this.lastToolHighlight.getRarity().color());
|
||||
@@ -632,9 +_,10 @@
|
||||
@@ -632,9 +_,13 @@
|
||||
mutablecomponent.withStyle(ChatFormatting.ITALIC);
|
||||
}
|
||||
|
||||
- int i = this.getFont().width(mutablecomponent);
|
||||
+ Component highlightTip = this.lastToolHighlight.getHighlightTip(mutablecomponent);
|
||||
+ int i = this.getFont().width(highlightTip);
|
||||
+ Font font = net.minecraftforge.client.extensions.common.IClientItemExtensions.of(lastToolHighlight).getFont(lastToolHighlight, net.minecraftforge.client.extensions.common.IClientItemExtensions.FontContext.SELECTED_ITEM_NAME);
|
||||
+ if (font == null)
|
||||
+ font = this.getFont();
|
||||
+ int i = font.width(highlightTip);
|
||||
int j = (p_283501_.guiWidth() - i) / 2;
|
||||
- int k = p_283501_.guiHeight() - 59;
|
||||
+ int k = p_283501_.guiHeight() - Math.max(yShift, 59);
|
||||
if (!this.minecraft.gameMode.canHurtPlayer()) {
|
||||
k += 14;
|
||||
}
|
||||
@@ -646,6 +_,13 @@
|
||||
@@ -645,7 +_,7 @@
|
||||
}
|
||||
|
||||
if (l > 0) {
|
||||
p_283501_.drawStringWithBackdrop(this.getFont(), mutablecomponent, j, k, i, FastColor.ARGB32.color(l, -1));
|
||||
+ Font font = net.minecraftforge.client.extensions.common.IClientItemExtensions.of(lastToolHighlight).getFont(lastToolHighlight, net.minecraftforge.client.extensions.common.IClientItemExtensions.FontContext.SELECTED_ITEM_NAME);
|
||||
+ if (font == null) {
|
||||
+ p_283501_.drawString(this.getFont(), highlightTip, j, k, 16777215 + (l << 24));
|
||||
+ } else {
|
||||
+ j = (p_283501_.guiWidth() - font.width(highlightTip)) / 2;
|
||||
+ p_283501_.drawString(font, highlightTip, j, k, 16777215 + (l << 24));
|
||||
+ }
|
||||
- p_283501_.drawStringWithBackdrop(this.getFont(), mutablecomponent, j, k, i, FastColor.ARGB32.color(l, -1));
|
||||
+ p_283501_.drawStringWithBackdrop(font, highlightTip, j, k, i, FastColor.ARGB32.color(l, -1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,11 +46,11 @@
|
||||
}
|
||||
|
||||
- p_282860_.drawString(this.font, s, 2, this.height - 10, 16777215 | i);
|
||||
+ net.minecraftforge.internal.BrandingControl.forEachLine(true, true, (brdline, brd) ->
|
||||
+ net.minecraftforge.internal.BrandingControl.forEachLine(true, true, (brd, brdline) ->
|
||||
+ p_282860_.drawString(this.font, brd, 2, this.height - ( 10 + brdline * (this.font.lineHeight + 1)), 16777215 | i)
|
||||
+ );
|
||||
+
|
||||
+ net.minecraftforge.internal.BrandingControl.forEachAboveCopyrightLine((brdline, brd) ->
|
||||
+ net.minecraftforge.internal.BrandingControl.forEachAboveCopyrightLine((brd, brdline) ->
|
||||
+ p_282860_.drawString(this.font, brd, this.width - font.width(brd), this.height - (10 + (brdline + 1) * ( this.font.lineHeight + 1)), 16777215 | i)
|
||||
+ );
|
||||
+
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
public void handleSystemMessage(Component p_240522_, boolean p_240642_) {
|
||||
if (!this.minecraft.options.hideMatchedNames().get() || !this.minecraft.isBlocked(this.guessChatUUID(p_240522_))) {
|
||||
+ p_240522_ = net.minecraftforge.client.ForgeHooksClient.onClientSystemChat(p_240522_, p_240642_, this.minecraft.level.registryAccess());
|
||||
+ p_240522_ = net.minecraftforge.client.ForgeHooksClient.onClientSystemMessage(p_240522_, p_240642_);
|
||||
+ if (p_240522_ == null) return;
|
||||
if (p_240642_) {
|
||||
this.minecraft.gui.setOverlayMessage(p_240522_, false);
|
||||
|
||||
@@ -9,6 +9,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,8 +_,11 @@
|
||||
public void processBlurEffect(float p_333718_) {
|
||||
float f = (float)this.minecraft.options.getMenuBackgroundBlurriness();
|
||||
if (this.blurEffect != null && f >= 1.0F) {
|
||||
+ // FORGE: Blending the blur was removed in 1.21. This is necessary for screen layering to work properly. https://github.com/MinecraftForge/MinecraftForge/issues/10114
|
||||
+ RenderSystem.enableBlend();
|
||||
this.blurEffect.setUniform("Radius", f);
|
||||
this.blurEffect.process(p_333718_);
|
||||
+ RenderSystem.disableBlend();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,6 +_,7 @@
|
||||
Pair.of(new ShaderInstance(p_250719_, "rendertype_gui_ghost_recipe_overlay", DefaultVertexFormat.POSITION_COLOR), p_286147_ -> rendertypeGuiGhostRecipeOverlayShader = p_286147_)
|
||||
);
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
public static RenderType getRenderType(ItemStack p_109280_, boolean p_109281_) {
|
||||
Item item = p_109280_.getItem();
|
||||
if (item instanceof BlockItem) {
|
||||
@@ -390,11 +_,74 @@
|
||||
@@ -390,11 +_,79 @@
|
||||
}
|
||||
|
||||
public static RenderType getRenderLayer(FluidState p_109288_) {
|
||||
@@ -65,6 +65,11 @@
|
||||
renderCutout = p_109292_;
|
||||
+ }
|
||||
+
|
||||
+ /** Forge: Check if we are running in {@linkplain net.minecraft.client.Minecraft#useFancyGraphics() fancy graphics} to account for fast graphics render types */
|
||||
+ public static boolean isFancy() {
|
||||
+ return renderCutout;
|
||||
+ }
|
||||
+
|
||||
+ private static final net.minecraftforge.client.ChunkRenderTypeSet CUTOUT_MIPPED = net.minecraftforge.client.ChunkRenderTypeSet.of(RenderType.cutoutMipped());
|
||||
+ private static final net.minecraftforge.client.ChunkRenderTypeSet SOLID = net.minecraftforge.client.ChunkRenderTypeSet.of(RenderType.solid());
|
||||
+ private static final Map<net.minecraft.core.Holder.Reference<Block>, net.minecraftforge.client.ChunkRenderTypeSet> BLOCK_RENDER_TYPES = Util.make(new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(TYPE_BY_BLOCK.size(), 0.5F), map -> {
|
||||
@@ -81,7 +86,7 @@
|
||||
+ });
|
||||
+
|
||||
+ /** @deprecated Use {@link net.minecraft.client.resources.model.BakedModel#getRenderTypes(BlockState, net.minecraft.util.RandomSource, net.minecraftforge.client.model.data.ModelData)}. */
|
||||
+ @Deprecated(since = "1.19")
|
||||
+ @Deprecated(since = "1.21.4", forRemoval = true)
|
||||
+ public static net.minecraftforge.client.ChunkRenderTypeSet getRenderLayers(BlockState state) {
|
||||
+ Block block = state.getBlock();
|
||||
+ if (block instanceof LeavesBlock) {
|
||||
@@ -92,20 +97,20 @@
|
||||
+ }
|
||||
+
|
||||
+ /** @deprecated Set your render type in your block model's JSON (eg. {@code "render_type": "cutout"}) or override {@link net.minecraft.client.resources.model.BakedModel#getRenderTypes(BlockState, net.minecraft.util.RandomSource, net.minecraftforge.client.model.data.ModelData)} */
|
||||
+ @Deprecated(since = "1.19")
|
||||
+ @Deprecated(since = "1.21.4", forRemoval = true)
|
||||
+ public static void setRenderLayer(Block block, RenderType type) {
|
||||
+ com.google.common.base.Preconditions.checkArgument(type.getChunkLayerId() >= 0, "The argument must be a valid chunk render type returned by RenderType#chunkBufferLayers().");
|
||||
+ setRenderLayer(block, net.minecraftforge.client.ChunkRenderTypeSet.of(type));
|
||||
+ }
|
||||
+
|
||||
+ /** @deprecated Set your render type in your block model's JSON (eg. {@code "render_type": "cutout"}) or override {@link net.minecraft.client.resources.model.BakedModel#getRenderTypes(BlockState, net.minecraft.util.RandomSource, net.minecraftforge.client.model.data.ModelData)} */
|
||||
+ @Deprecated(since = "1.19")
|
||||
+ @Deprecated(since = "1.21.4", forRemoval = true)
|
||||
+ public static synchronized void setRenderLayer(Block block, java.util.function.Predicate<RenderType> predicate) {
|
||||
+ setRenderLayer(block, createSetFromPredicate(predicate));
|
||||
+ }
|
||||
+
|
||||
+ /** @deprecated Set your render type in your block model's JSON (eg. {@code "render_type": "cutout"}) or override {@link net.minecraft.client.resources.model.BakedModel#getRenderTypes(BlockState, net.minecraft.util.RandomSource, net.minecraftforge.client.model.data.ModelData)} */
|
||||
+ @Deprecated(since = "1.19")
|
||||
+ @Deprecated(since = "1.21.4", forRemoval = true)
|
||||
+ public static synchronized void setRenderLayer(Block block, net.minecraftforge.client.ChunkRenderTypeSet layers) {
|
||||
+ checkClientLoading();
|
||||
+ BLOCK_RENDER_TYPES.put(net.minecraftforge.registries.ForgeRegistries.BLOCKS.getDelegateOrThrow(block), layers);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
int k1 = Mth.log2(j1);
|
||||
int l1;
|
||||
- if (k1 < p_261919_) {
|
||||
+ if (false) { // Forge: Do not lower the mipmap level
|
||||
+ if (k1 < p_261919_ && net.minecraftforge.common.ForgeConfig.CLIENT.allowMipmapLowering()) { // Forge: Do not lower the mipmap level
|
||||
LOGGER.warn("{}: dropping miplevel from {} to {}, because of minimum power of two: {}", this.location, p_261919_, k1, j1);
|
||||
l1 = k1;
|
||||
} else {
|
||||
|
||||
+92
-23
@@ -1,74 +1,142 @@
|
||||
--- a/net/minecraft/client/resources/model/SimpleBakedModel.java
|
||||
+++ b/net/minecraft/client/resources/model/SimpleBakedModel.java
|
||||
@@ -26,6 +_,15 @@
|
||||
@@ -26,7 +_,32 @@
|
||||
protected final TextureAtlasSprite particleIcon;
|
||||
protected final ItemTransforms transforms;
|
||||
protected final ItemOverrides overrides;
|
||||
-
|
||||
+ /** Forge: Block render types to be used with {@linkplain net.minecraft.client.GraphicsStatus#FANCY fancy graphics} */
|
||||
+ protected final net.minecraftforge.client.ChunkRenderTypeSet blockRenderTypes;
|
||||
+ /** Forge: Block render types to be used with {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics} */
|
||||
+ protected final net.minecraftforge.client.ChunkRenderTypeSet blockRenderTypesFast;
|
||||
+ /** Forge: Item render types to be used with {@linkplain net.minecraft.client.GraphicsStatus#FANCY fancy graphics} */
|
||||
+ protected final List<net.minecraft.client.renderer.RenderType> itemRenderTypes;
|
||||
+ /** Forge: Item render types to be used with {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics} */
|
||||
+ protected final List<net.minecraft.client.renderer.RenderType> itemRenderTypesFast;
|
||||
+ /** Forge: Item render types to be used with {@linkplain net.minecraft.client.GraphicsStatus#FABULOUS fabulous graphics} */
|
||||
+ protected final List<net.minecraft.client.renderer.RenderType> fabulousItemRenderTypes;
|
||||
+ /** Forge: If this model's {@linkplain #blockRenderTypes block render types} are rendering cutout, to account for older leaves model JSONs */
|
||||
+ protected final boolean isRenderingCutout;
|
||||
+
|
||||
+ /** @deprecated Forge: Use {@linkplain #SimpleBakedModel(List, Map, boolean, boolean, boolean, TextureAtlasSprite, ItemTransforms, ItemOverrides, net.minecraftforge.client.RenderTypeGroup) variant with RenderTypeGroup} **/
|
||||
+ /** @deprecated Forge: Use {@linkplain #SimpleBakedModel(List, Map, boolean, boolean, boolean, TextureAtlasSprite, ItemTransforms, ItemOverrides, net.minecraftforge.client.RenderTypeGroup, net.minecraftforge.client.RenderTypeGroup) variant with RenderTypeGroup} **/
|
||||
+ @Deprecated
|
||||
+ public SimpleBakedModel(List<BakedQuad> p_119489_, Map<Direction, List<BakedQuad>> p_119490_, boolean p_119491_, boolean p_119492_, boolean p_119493_, TextureAtlasSprite p_119494_, ItemTransforms p_119495_, ItemOverrides p_119496_) {
|
||||
+ this(p_119489_, p_119490_, p_119491_, p_119492_, p_119493_, p_119494_, p_119495_, p_119496_, net.minecraftforge.client.RenderTypeGroup.EMPTY);
|
||||
+ }
|
||||
|
||||
+
|
||||
+ /** @deprecated Forge: Use {@linkplain #SimpleBakedModel(List, Map, boolean, boolean, boolean, TextureAtlasSprite, ItemTransforms, ItemOverrides, net.minecraftforge.client.RenderTypeGroup, net.minecraftforge.client.RenderTypeGroup) variant with RenderTypeGroup for fast graphics} **/
|
||||
+ @Deprecated(forRemoval = true, since = "1.21.4")
|
||||
+ public SimpleBakedModel(List<BakedQuad> p_119489_, Map<Direction, List<BakedQuad>> p_119490_, boolean p_119491_, boolean p_119492_, boolean p_119493_, TextureAtlasSprite p_119494_, ItemTransforms p_119495_, ItemOverrides p_119496_, net.minecraftforge.client.RenderTypeGroup renderTypes) {
|
||||
+ this(p_119489_, p_119490_, p_119491_, p_119492_, p_119493_, p_119494_, p_119495_, p_119496_, renderTypes, net.minecraftforge.client.RenderTypeGroup.EMPTY);
|
||||
+ }
|
||||
+
|
||||
+ /** Constructor with {@link net.minecraftforge.client.RenderTypeGroup RenderTypeGroup} for fancy and fast graphics. Preferred over {@link net.minecraft.client.renderer.ItemBlockRenderTypes#setRenderLayer(net.minecraft.world.level.block.Block, net.minecraft.client.renderer.RenderType) ItemBlockRenderTypes.setRenderLayer(Block, RenderType)}. */
|
||||
public SimpleBakedModel(
|
||||
List<BakedQuad> p_119489_,
|
||||
@@ -35,7 +_,8 @@
|
||||
Map<Direction, List<BakedQuad>> p_119490_,
|
||||
@@ -35,7 +_,9 @@
|
||||
boolean p_119493_,
|
||||
TextureAtlasSprite p_119494_,
|
||||
ItemTransforms p_119495_,
|
||||
- ItemOverrides p_119496_
|
||||
+ ItemOverrides p_119496_,
|
||||
+ net.minecraftforge.client.RenderTypeGroup renderTypes
|
||||
+ net.minecraftforge.client.RenderTypeGroup renderTypes,
|
||||
+ net.minecraftforge.client.RenderTypeGroup renderTypesFast
|
||||
) {
|
||||
this.unculledFaces = p_119489_;
|
||||
this.culledFaces = p_119490_;
|
||||
@@ -45,6 +_,9 @@
|
||||
@@ -45,6 +_,15 @@
|
||||
this.particleIcon = p_119494_;
|
||||
this.transforms = p_119495_;
|
||||
this.overrides = p_119496_;
|
||||
+ this.blockRenderTypes = !renderTypes.isEmpty() ? net.minecraftforge.client.ChunkRenderTypeSet.of(renderTypes.block()) : null;
|
||||
+ this.itemRenderTypes = !renderTypes.isEmpty() ? List.of(renderTypes.entity()) : null;
|
||||
+ this.fabulousItemRenderTypes = !renderTypes.isEmpty() ? List.of(renderTypes.entityFabulous()) : null;
|
||||
+
|
||||
+ boolean hasRenderTypes = renderTypes != null && !renderTypes.isEmpty();
|
||||
+ boolean hasRenderTypesFast = renderTypesFast != null && !renderTypesFast.isEmpty();
|
||||
+ this.blockRenderTypes = hasRenderTypes ? net.minecraftforge.client.ChunkRenderTypeSet.of(renderTypes.block()) : null;
|
||||
+ this.blockRenderTypesFast = hasRenderTypesFast ? net.minecraftforge.client.ChunkRenderTypeSet.of(renderTypesFast.block()) : null;
|
||||
+ this.itemRenderTypes = hasRenderTypes ? List.of(renderTypes.entity()) : null;
|
||||
+ this.itemRenderTypesFast = hasRenderTypesFast ? List.of(renderTypesFast.entity()) : null;
|
||||
+ this.fabulousItemRenderTypes = hasRenderTypes ? List.of(renderTypes.entityFabulous()) : null;
|
||||
+ this.isRenderingCutout = hasRenderTypes && (renderTypes.block() == net.minecraft.client.renderer.RenderType.cutout() || renderTypes.block() == net.minecraft.client.renderer.RenderType.cutoutMipped());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,6 +_,25 @@
|
||||
@@ -87,6 +_,38 @@
|
||||
return this.overrides;
|
||||
}
|
||||
|
||||
+ private static final net.minecraftforge.client.ChunkRenderTypeSet SOLID_BLOCK = net.minecraftforge.client.ChunkRenderTypeSet.of(net.minecraft.client.renderer.RenderType.solid());
|
||||
+ private static final List<net.minecraft.client.renderer.RenderType> SOLID_BLOCK_ITEM = List.of(net.minecraft.client.renderer.RenderType.solid());
|
||||
+
|
||||
+ @Override
|
||||
+ public net.minecraftforge.client.ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, net.minecraftforge.client.model.data.ModelData data) {
|
||||
+ return blockRenderTypes != null ? blockRenderTypes : BakedModel.super.getRenderTypes(state, rand, data);
|
||||
+ if (!net.minecraft.client.renderer.ItemBlockRenderTypes.isFancy()) {
|
||||
+ if (blockRenderTypesFast != null)
|
||||
+ return blockRenderTypesFast;
|
||||
+ if (isRenderingCutout && state.getBlock() instanceof net.minecraft.world.level.block.LeavesBlock)
|
||||
+ return SOLID_BLOCK;
|
||||
+ }
|
||||
+ return blockRenderTypes != null ? blockRenderTypes : BakedModel.super.getRenderTypes(state, rand, data);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public List<net.minecraft.client.renderer.RenderType> getRenderTypes(net.minecraft.world.item.ItemStack itemStack, boolean fabulous) {
|
||||
+ if (!fabulous) {
|
||||
+ if (itemRenderTypes != null) {
|
||||
+ return itemRenderTypes;
|
||||
+ }
|
||||
+ } else {
|
||||
+ if (fabulousItemRenderTypes != null) {
|
||||
+ return fabulousItemRenderTypes;
|
||||
+ }
|
||||
+ }
|
||||
+ return BakedModel.super.getRenderTypes(itemStack, fabulous);
|
||||
+ if (!fabulous) {
|
||||
+ if (!net.minecraft.client.renderer.ItemBlockRenderTypes.isFancy()) {
|
||||
+ if (itemRenderTypesFast != null)
|
||||
+ return itemRenderTypesFast;
|
||||
+ if (isRenderingCutout && itemStack.getItem() instanceof net.minecraft.world.item.BlockItem blockItem && blockItem.getBlock() instanceof net.minecraft.world.level.block.LeavesBlock)
|
||||
+ return SOLID_BLOCK_ITEM;
|
||||
+ }
|
||||
+ if (itemRenderTypes != null)
|
||||
+ return itemRenderTypes;
|
||||
+ } else {
|
||||
+ if (fabulousItemRenderTypes != null)
|
||||
+ return fabulousItemRenderTypes;
|
||||
+ }
|
||||
+ return BakedModel.super.getRenderTypes(itemStack, fabulous);
|
||||
+ }
|
||||
+
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static class Builder {
|
||||
private final List<BakedQuad> unculledFaces = Lists.newArrayList();
|
||||
@@ -133,12 +_,18 @@
|
||||
@@ -133,12 +_,49 @@
|
||||
return this;
|
||||
}
|
||||
|
||||
+ private net.minecraftforge.client.RenderTypeGroup renderTypes = net.minecraftforge.client.RenderTypeGroup.EMPTY;
|
||||
+ private net.minecraftforge.client.RenderTypeGroup renderTypesFast = net.minecraftforge.client.RenderTypeGroup.EMPTY;
|
||||
+
|
||||
+ /**
|
||||
+ * Sets the render type to be used for this model, and will be used for any graphics setting.
|
||||
+ * <p>
|
||||
+ * If you need to set a specific render type for
|
||||
+ * {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics}, consider using
|
||||
+ * {@link #renderTypes(net.minecraftforge.client.RenderTypeGroup, net.minecraftforge.client.RenderTypeGroup)
|
||||
+ * renderTypes(RenderTypeGroup, RenderTypeGroup)} instead which allows choosing render types for both fancy and
|
||||
+ * fast graphics.
|
||||
+ *
|
||||
+ * @apiNote If this model is for {@linkplain net.minecraft.world.level.block.LeavesBlock leaves} and if the
|
||||
+ * given render type is either {@linkplain net.minecraft.client.renderer.RenderType#cutout() cutout} or
|
||||
+ * {@linkplain net.minecraft.client.renderer.RenderType#cutoutMipped() cutout mipped}, it will be overridden
|
||||
+ * with {@linkplain net.minecraft.client.renderer.RenderType#solid() solid} when fast graphics is enabled.
|
||||
+ * @see #renderTypes(net.minecraftforge.client.RenderTypeGroup, net.minecraftforge.client.RenderTypeGroup)
|
||||
+ * renderTypes(RenderTypeGroup, RenderTypeGroup)
|
||||
+ */
|
||||
+ public SimpleBakedModel.Builder renderTypes(net.minecraftforge.client.RenderTypeGroup renderTypes) {
|
||||
+ return this.renderTypes(renderTypes, net.minecraftforge.client.RenderTypeGroup.EMPTY);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Sets the render types to be used for this model: one for
|
||||
+ * {@linkplain net.minecraft.client.GraphicsStatus#FANCY fancy graphics} and one for
|
||||
+ * {@linkplain net.minecraft.client.GraphicsStatus#FAST fast graphics}.
|
||||
+ *
|
||||
+ * @see #renderTypes(net.minecraftforge.client.RenderTypeGroup)
|
||||
+ */
|
||||
+ public SimpleBakedModel.Builder renderTypes(net.minecraftforge.client.RenderTypeGroup renderTypes, net.minecraftforge.client.RenderTypeGroup renderTypesFast) {
|
||||
+ this.renderTypes = renderTypes;
|
||||
+ this.renderTypesFast = renderTypesFast;
|
||||
+ return this;
|
||||
+ }
|
||||
+
|
||||
@@ -78,7 +146,8 @@
|
||||
} else {
|
||||
return new SimpleBakedModel(
|
||||
- this.unculledFaces, this.culledFaces, this.hasAmbientOcclusion, this.usesBlockLight, this.isGui3d, this.particleIcon, this.transforms, this.overrides
|
||||
+ this.unculledFaces, this.culledFaces, this.hasAmbientOcclusion, this.usesBlockLight, this.isGui3d, this.particleIcon, this.transforms, this.overrides, this.renderTypes
|
||||
+ // Forge: Account for render types in model JSONs
|
||||
+ this.unculledFaces, this.culledFaces, this.hasAmbientOcclusion, this.usesBlockLight, this.isGui3d, this.particleIcon, this.transforms, this.overrides, this.renderTypes, this.renderTypesFast
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
--- a/net/minecraft/core/Direction.java
|
||||
+++ b/net/minecraft/core/Direction.java
|
||||
@@ -47,6 +_,11 @@
|
||||
private final Direction.AxisDirection axisDirection;
|
||||
private final Vec3i normal;
|
||||
private static final Direction[] VALUES = values();
|
||||
+ // In vanilla updates are notified only on the horizontal plane, in Forge we also notify up and down
|
||||
+ private static final List<Direction> UPDATE_ORDER = java.util.Collections.unmodifiableList(Stream.concat(Plane.HORIZONTAL.stream(), Plane.VERTICAL.stream()).toList());
|
||||
+ public static final List<Direction> getUpdateOrder() {
|
||||
+ return UPDATE_ORDER;
|
||||
+ }
|
||||
private static final Direction[] BY_3D_DATA = Arrays.stream(VALUES)
|
||||
.sorted(Comparator.comparingInt(p_235687_ -> p_235687_.data3d))
|
||||
.toArray(Direction[]::new);
|
||||
@@ -9,3 +9,66 @@
|
||||
private final GameTestInfo testInfo;
|
||||
private boolean finalCheckAdded;
|
||||
|
||||
@@ -870,26 +_,30 @@
|
||||
}
|
||||
|
||||
public BlockPos absolutePos(BlockPos p_177450_) {
|
||||
- BlockPos blockpos = this.testInfo.getStructureBlockPos();
|
||||
+ // Forge: Use IForgeGameTestInfo#getTestOrigin to account for rotations and to mimic 1.21.3 behavior
|
||||
+ BlockPos blockpos = this.testInfo.getTestOrigin();
|
||||
BlockPos blockpos1 = blockpos.offset(p_177450_);
|
||||
return StructureTemplate.transform(blockpos1, Mirror.NONE, this.testInfo.getRotation(), blockpos);
|
||||
}
|
||||
|
||||
public BlockPos relativePos(BlockPos p_177453_) {
|
||||
- BlockPos blockpos = this.testInfo.getStructureBlockPos();
|
||||
+ // Forge: Use IForgeGameTestInfo#getTestOrigin to account for rotations and to mimic 1.21.3 behavior
|
||||
+ BlockPos blockpos = this.testInfo.getTestOrigin();
|
||||
Rotation rotation = this.testInfo.getRotation().getRotated(Rotation.CLOCKWISE_180);
|
||||
BlockPos blockpos1 = StructureTemplate.transform(p_177453_, Mirror.NONE, rotation, blockpos);
|
||||
return blockpos1.subtract(blockpos);
|
||||
}
|
||||
|
||||
public Vec3 absoluteVec(Vec3 p_177228_) {
|
||||
- Vec3 vec3 = Vec3.atLowerCornerOf(this.testInfo.getStructureBlockPos());
|
||||
- return StructureTemplate.transform(vec3.add(p_177228_), Mirror.NONE, this.testInfo.getRotation(), this.testInfo.getStructureBlockPos());
|
||||
+ // Forge: Use IForgeGameTestInfo#getTestOrigin to account for rotations and to mimic 1.21.3 behavior
|
||||
+ Vec3 vec3 = Vec3.atLowerCornerOf(this.testInfo.getTestOrigin());
|
||||
+ return StructureTemplate.transform(vec3.add(p_177228_), Mirror.NONE, this.testInfo.getRotation(), this.testInfo.getTestOrigin());
|
||||
}
|
||||
|
||||
public Vec3 relativeVec(Vec3 p_251543_) {
|
||||
- Vec3 vec3 = Vec3.atLowerCornerOf(this.testInfo.getStructureBlockPos());
|
||||
- return StructureTemplate.transform(p_251543_.subtract(vec3), Mirror.NONE, this.testInfo.getRotation(), this.testInfo.getStructureBlockPos());
|
||||
+ // Forge: Use IForgeGameTestInfo#getTestOrigin to account for rotations and to mimic 1.21.3 behavior
|
||||
+ Vec3 vec3 = Vec3.atLowerCornerOf(this.testInfo.getTestOrigin());
|
||||
+ return StructureTemplate.transform(p_251543_.subtract(vec3), Mirror.NONE, this.testInfo.getRotation(), this.testInfo.getTestOrigin());
|
||||
}
|
||||
|
||||
public Rotation getTestRotation() {
|
||||
@@ -954,5 +_,25 @@
|
||||
if (either.right().isPresent()) {
|
||||
this.fail("Failed to set biome for test");
|
||||
}
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Adds a cleanup handler that will be called when the test is done, pass or fail.
|
||||
+ */
|
||||
+ public void addCleanup(Consumer<Boolean> handler) {
|
||||
+ this.testInfo.addListener(new GameTestListener() {
|
||||
+ @Override
|
||||
+ public void testPassed(GameTestInfo info, GameTestRunner runner) {
|
||||
+ handler.accept(true);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void testFailed(GameTestInfo info, GameTestRunner runner) {
|
||||
+ handler.accept(false);
|
||||
+ }
|
||||
+
|
||||
+ @Override public void testStructureLoaded(GameTestInfo info) { }
|
||||
+ @Override public void testAddedForRerun(GameTestInfo oldInfo, GameTestInfo newInfo, GameTestRunner runner) { }
|
||||
+ });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
--- a/net/minecraft/gametest/framework/GameTestInfo.java
|
||||
+++ b/net/minecraft/gametest/framework/GameTestInfo.java
|
||||
@@ -261,7 +_,7 @@
|
||||
BlockPos blockpos = this.getOrCalculateNorthwestCorner();
|
||||
this.structureBlockEntity = StructureUtils.prepareTestStructure(this, blockpos, this.getRotation(), this.level);
|
||||
this.structureBlockPos = this.structureBlockEntity.getBlockPos();
|
||||
- StructureUtils.addCommandBlockAndButtonToStartTest(this.structureBlockPos, new BlockPos(1, 0, -1), this.getRotation(), this.level);
|
||||
+ StructureUtils.addCommandBlockAndButtonToStartTest(this.structureBlockPos, new BlockPos(1, 0, -1), Rotation.NONE, this.level); // Forge: The control blocks are always in the North West Corner
|
||||
StructureUtils.encaseStructure(this.getStructureBounds(), this.level, !this.testFunction.skyAccess());
|
||||
this.listeners.forEach(p_127630_ -> p_127630_.testStructureLoaded(this));
|
||||
return this;
|
||||
@@ -345,5 +_,25 @@
|
||||
|
||||
public void setNorthWestCorner(BlockPos p_328918_) {
|
||||
this.northWestCorner = p_328918_;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ // FORGE START
|
||||
+
|
||||
+ private final boolean useTestOrigin = net.minecraftforge.common.ForgeConfig.COMMON.fixGameTestBlockPosAsserts.get();
|
||||
+
|
||||
+ /**
|
||||
+ * Forge: Gets the origin of the test structure based on the
|
||||
+ * {@linkplain #getStructureBlockEntity() structure block entity}.
|
||||
+ * <p>
|
||||
+ * This is a backported method from 1.21.3 to fix how block positions are asserted.
|
||||
+ *
|
||||
+ * @return The origin of the test structure
|
||||
+ *
|
||||
+ * @see net.minecraftforge.common.ForgeConfig.Common#fixGameTestBlockPosAsserts
|
||||
+ */
|
||||
+ @SuppressWarnings("DataFlowIssue") // Nullability moment
|
||||
+ public BlockPos getTestOrigin() {
|
||||
+ return this.useTestOrigin ? StructureUtils.getStructureOrigin(this.getStructureBlockEntity()) : this.getStructureBlockPos();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
--- a/net/minecraft/gametest/framework/GameTestServer.java
|
||||
+++ b/net/minecraft/gametest/framework/GameTestServer.java
|
||||
@@ -79,7 +_,7 @@
|
||||
} else {
|
||||
p_206609_.reload();
|
||||
WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration(
|
||||
- new DataPackConfig(new ArrayList<>(p_206609_.getAvailableIds()), List.of()), FeatureFlags.REGISTRY.allFlags()
|
||||
+ new DataPackConfig(new ArrayList<>(p_206609_.getSelectedIds()), List.of()), FeatureFlags.REGISTRY.allFlags() // Forge: Use the selected datapacks as this takes sorting into account.
|
||||
);
|
||||
LevelSettings levelsettings = new LevelSettings("Test Level", GameType.CREATIVE, false, Difficulty.NORMAL, true, TEST_GAME_RULES, worlddataconfiguration);
|
||||
WorldLoader.PackConfig worldloader$packconfig = new WorldLoader.PackConfig(p_206609_, worlddataconfiguration, false, true);
|
||||
@@ -138,6 +_,7 @@
|
||||
|
||||
@Override
|
||||
public boolean initServer() {
|
||||
+ if (!net.minecraftforge.server.ServerLifecycleHooks.handleServerAboutToStart(this)) return false;
|
||||
this.setPlayerList(new PlayerList(this, this.registries(), this.playerDataStorage, 1) {
|
||||
});
|
||||
this.loadLevel();
|
||||
@@ -0,0 +1,22 @@
|
||||
--- a/net/minecraft/gametest/framework/ReportGameListener.java
|
||||
+++ b/net/minecraft/gametest/framework/ReportGameListener.java
|
||||
@@ -151,7 +_,7 @@
|
||||
private static BlockPos getBeaconPos(GameTestInfo p_344999_) {
|
||||
BlockPos blockpos = p_344999_.getStructureBlockPos();
|
||||
BlockPos blockpos1 = new BlockPos(-1, -2, -1);
|
||||
- return StructureTemplate.transform(blockpos.offset(blockpos1), Mirror.NONE, p_344999_.getRotation(), blockpos);
|
||||
+ return blockpos.offset(blockpos1); // Forge: The control blocks are always in the North West Corner
|
||||
}
|
||||
|
||||
private static void updateBeaconGlass(GameTestInfo p_343978_, Block p_344076_) {
|
||||
@@ -167,8 +_,8 @@
|
||||
ServerLevel serverlevel = p_177739_.getLevel();
|
||||
BlockPos blockpos = p_177739_.getStructureBlockPos();
|
||||
BlockPos blockpos1 = new BlockPos(-1, 0, -1);
|
||||
- BlockPos blockpos2 = StructureTemplate.transform(blockpos.offset(blockpos1), Mirror.NONE, p_177739_.getRotation(), blockpos);
|
||||
- serverlevel.setBlockAndUpdate(blockpos2, Blocks.LECTERN.defaultBlockState().rotate(p_177739_.getRotation()));
|
||||
+ BlockPos blockpos2 = blockpos.offset(blockpos1); // Forge: The control blocks are always in the North West Corner
|
||||
+ serverlevel.setBlockAndUpdate(blockpos2, Blocks.LECTERN.defaultBlockState());
|
||||
BlockState blockstate = serverlevel.getBlockState(blockpos2);
|
||||
ItemStack itemstack = createBook(p_177739_.getTestName(), p_177739_.isRequired(), p_177740_);
|
||||
LecternBlock.tryPlaceBook(null, serverlevel, blockpos2, blockstate, itemstack);
|
||||
@@ -0,0 +1,27 @@
|
||||
--- a/net/minecraft/gametest/framework/StructureUtils.java
|
||||
+++ b/net/minecraft/gametest/framework/StructureUtils.java
|
||||
@@ -109,7 +_,8 @@
|
||||
.getSize();
|
||||
BoundingBox boundingbox = getStructureBoundingBox(p_311042_, vec3i, p_310584_);
|
||||
BlockPos blockpos;
|
||||
- if (p_310584_ == Rotation.NONE) {
|
||||
+ // Forge: The control blocks are always in the North West Corner
|
||||
+ if (true || p_310584_ == Rotation.NONE) {
|
||||
blockpos = p_311042_;
|
||||
} else if (p_310584_ == Rotation.CLOCKWISE_90) {
|
||||
blockpos = p_311042_.offset(vec3i.getZ() - 1, 0, 0);
|
||||
@@ -125,7 +_,13 @@
|
||||
|
||||
forceLoadChunks(boundingbox, p_312330_);
|
||||
clearSpaceForStructure(boundingbox, p_312330_);
|
||||
- return createStructureBlock(p_311701_, blockpos.below(), p_310584_, p_312330_);
|
||||
+ var entity = createStructureBlock(p_311701_, blockpos.below(), p_310584_, p_312330_);
|
||||
+ //Forge: We need to offset the structure so that it will load within bounds.
|
||||
+ if (p_310584_ != Rotation.NONE) {
|
||||
+ var rotated = StructureTemplate.getZeroPositionWithTransform(BlockPos.ZERO, Mirror.NONE, p_310584_, entity.getStructureSize().getX(), entity.getStructureSize().getZ());
|
||||
+ entity.setStructurePos(rotated.offset(new BlockPos(0, 1, 0)));
|
||||
+ }
|
||||
+ return entity;
|
||||
}
|
||||
|
||||
public static void encaseStructure(AABB p_330422_, ServerLevel p_331249_, boolean p_328180_) {
|
||||
@@ -0,0 +1,15 @@
|
||||
--- a/net/minecraft/gametest/framework/TestCommand.java
|
||||
+++ b/net/minecraft/gametest/framework/TestCommand.java
|
||||
@@ -311,7 +_,11 @@
|
||||
return Optional.empty();
|
||||
} else {
|
||||
TestFunction testfunction = optional.get();
|
||||
- GameTestInfo gametestinfo = new GameTestInfo(testfunction, structureblockentity.getRotation(), p_328153_, p_330368_);
|
||||
+ // Forge: The rotation is stored in the structure block, and added in the GameTestInfo constructor.
|
||||
+ // So reverse it to find the manually specified rotation so the test runs the same every time.
|
||||
+ var steps = StructureUtils.getRotationStepsForRotation(structureblockentity.getRotation()) - StructureUtils.getRotationStepsForRotation(testfunction.rotation());
|
||||
+ if (steps < 0) steps += 4;
|
||||
+ GameTestInfo gametestinfo = new GameTestInfo(testfunction, StructureUtils.getRotationForRotationSteps(steps), p_328153_, p_330368_);
|
||||
gametestinfo.setStructureBlockPos(p_332856_);
|
||||
return !verifyStructureExists(p_328153_, gametestinfo.getStructureName()) ? Optional.empty() : Optional.of(gametestinfo);
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
|
||||
-public class ResourceKey<T> {
|
||||
+public class ResourceKey<T> implements Comparable<ResourceKey<?>> {
|
||||
+public class ResourceKey<T> implements Comparable<ResourceKey<?>>, net.minecraftforge.common.extensions.IForgeResourceKey<T> {
|
||||
private static final ConcurrentMap<ResourceKey.InternKey, ResourceKey<?>> VALUES = new MapMaker().weakValues().makeMap();
|
||||
private final ResourceLocation registryName;
|
||||
private final ResourceLocation location;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
--- a/net/minecraft/server/Main.java
|
||||
+++ b/net/minecraft/server/Main.java
|
||||
@@ -83,6 +_,15 @@
|
||||
@@ -83,6 +_,17 @@
|
||||
OptionSpec<Void> optionspec13 = optionparser.accepts("jfrProfile");
|
||||
OptionSpec<Path> optionspec14 = optionparser.accepts("pidFile").withRequiredArg().withValuesConvertedBy(new PathConverter());
|
||||
OptionSpec<String> optionspec15 = optionparser.nonOptions();
|
||||
+ optionparser.accepts("allowUpdates").withRequiredArg().ofType(Boolean.class).defaultsTo(Boolean.TRUE); // Forge: allow mod updates to proceed
|
||||
+ optionparser.accepts("gameDir").withRequiredArg().ofType(File.class).defaultsTo(new File(".")); //Forge: Consume this argument, we use it in the launcher, and the client side.
|
||||
+ final OptionSpec<net.minecraft.core.BlockPos> spawnPosOpt;
|
||||
+ OptionSpec<Void> uniqueWorld = null;
|
||||
+ boolean gametestEnabled = Boolean.getBoolean("forge.gameTestServer");
|
||||
+ if (gametestEnabled) {
|
||||
+ spawnPosOpt = optionparser.accepts("spawnPos").withRequiredArg().withValuesConvertedBy(new net.minecraftforge.gametest.BlockPosValueConverter()).defaultsTo(new net.minecraft.core.BlockPos(0, 60, 0));
|
||||
+ uniqueWorld = optionparser.accepts("uniqueWorld");
|
||||
+ } else {
|
||||
+ spawnPosOpt = null;
|
||||
+ }
|
||||
@@ -31,7 +33,7 @@
|
||||
Path path = optionset.valueOf(optionspec14);
|
||||
if (path != null) {
|
||||
writePidFile(path);
|
||||
@@ -105,24 +_,26 @@
|
||||
@@ -105,24 +_,29 @@
|
||||
Bootstrap.validate();
|
||||
Util.startTimerHackThread();
|
||||
Path path1 = Paths.get("server.properties");
|
||||
@@ -58,6 +60,9 @@
|
||||
File file1 = new File(optionset.valueOf(optionspec9));
|
||||
Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file1);
|
||||
String s = Optional.ofNullable(optionset.valueOf(optionspec10)).orElse(dedicatedserversettings.getProperties().levelName);
|
||||
+ // Forge: make each gametest use a timestamped world name
|
||||
+ if (uniqueWorld != null && optionset.has(uniqueWorld))
|
||||
+ s = "gametest_world\\" + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss.SSS").format(java.time.LocalDateTime.now());
|
||||
+ if (s == null || s.isEmpty() || new File(file1, s).getAbsolutePath().equals(new File(s).getAbsolutePath())) {
|
||||
+ LOGGER.error("Invalid world directory specified, must not be null, empty or the same directory as your universe! " + s);
|
||||
+ return;
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
@DontObfuscate
|
||||
public String getServerModName() {
|
||||
- return "vanilla";
|
||||
+ return net.minecraftforge.internal.BrandingControl.getServerBranding();
|
||||
+ return net.minecraftforge.internal.BrandingControl.getBranding();
|
||||
}
|
||||
|
||||
public SystemReport fillSystemReport(SystemReport p_177936_) {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
p_326196_ -> {
|
||||
ReloadableServerResources reloadableserverresources = new ReloadableServerResources(p_326196_.compositeAccess(), p_250212_, p_249301_, p_251126_);
|
||||
+ var listeners = new java.util.ArrayList<>(reloadableserverresources.listeners());
|
||||
+ listeners.addAll(net.minecraftforge.event.ForgeEventFactory.onResourceReload(reloadableserverresources, p_330376_.compositeAccess()));
|
||||
+ listeners.addAll(net.minecraftforge.event.ForgeEventFactory.onResourceReload(reloadableserverresources, reloadableserverresources.registryLookup, p_330376_.compositeAccess()));
|
||||
return SimpleReloadInstance.create(
|
||||
- p_248588_, reloadableserverresources.listeners(), p_249136_, p_249601_, DATA_RELOAD_INITIAL_TASK, LOGGER.isDebugEnabled()
|
||||
+ p_248588_, listeners, p_249136_, p_249601_, DATA_RELOAD_INITIAL_TASK, LOGGER.isDebugEnabled()
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +_,11 @@
|
||||
@@ -293,6 +_,13 @@
|
||||
if (this.queryThreadGs4 != null) {
|
||||
this.queryThreadGs4.stop();
|
||||
}
|
||||
@@ -54,6 +54,8 @@
|
||||
+ this.dediLanPinger.interrupt();
|
||||
+ this.dediLanPinger = null;
|
||||
+ }
|
||||
+
|
||||
+ net.minecraftforge.fml.config.ConfigTracker.INSTANCE.forceUnload();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,6 +8,14 @@
|
||||
|
||||
public PlayerList(MinecraftServer p_203842_, LayeredRegistryAccess<RegistryLayer> p_251844_, PlayerDataStorage p_203844_, int p_203845_) {
|
||||
this.server = p_203842_;
|
||||
@@ -199,6 +_,7 @@
|
||||
servergamepacketlistenerimpl.send(new ClientboundChangeDifficultyPacket(leveldata.getDifficulty(), leveldata.isDifficultyLocked()));
|
||||
servergamepacketlistenerimpl.send(new ClientboundPlayerAbilitiesPacket(p_11263_.getAbilities()));
|
||||
servergamepacketlistenerimpl.send(new ClientboundSetCarriedItemPacket(p_11263_.getInventory().selected));
|
||||
+ net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.OnDatapackSyncEvent(this, p_11263_));
|
||||
servergamepacketlistenerimpl.send(new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getOrderedRecipes()));
|
||||
this.sendPlayerPermissionLevel(p_11263_);
|
||||
p_11263_.getStats().markAllDirty();
|
||||
@@ -261,6 +_,7 @@
|
||||
}
|
||||
|
||||
|
||||
@@ -515,14 +515,12 @@
|
||||
this.completeUsingItem();
|
||||
}
|
||||
}
|
||||
@@ -3029,8 +_,12 @@
|
||||
@@ -3029,8 +_,10 @@
|
||||
public void startUsingItem(InteractionHand p_21159_) {
|
||||
ItemStack itemstack = this.getItemInHand(p_21159_);
|
||||
if (!itemstack.isEmpty() && !this.isUsingItem()) {
|
||||
+ int duration = net.minecraftforge.event.ForgeEventFactory.onItemUseStart(this, itemstack, itemstack.getUseDuration(this));
|
||||
+ if (duration <= 0) {
|
||||
+ return;
|
||||
+ }
|
||||
+ if (duration < net.minecraftforge.common.ForgeConfig.SERVER.getUseItemDuration()) return;
|
||||
this.useItem = itemstack;
|
||||
- this.useItemRemaining = itemstack.getUseDuration(this);
|
||||
+ this.useItemRemaining = duration;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
--- a/net/minecraft/world/entity/npc/VillagerTrades.java
|
||||
+++ b/net/minecraft/world/entity/npc/VillagerTrades.java
|
||||
@@ -1360,6 +_,7 @@
|
||||
private final int villagerXp;
|
||||
|
||||
public EmeraldsForVillagerTypeItem(int p_35669_, int p_35670_, int p_35671_, Map<VillagerType, Item> p_35672_) {
|
||||
+ if (false) // FORGE: Modders can add custom villager types, so remove this validation
|
||||
BuiltInRegistries.VILLAGER_TYPE.stream().filter(p_35680_ -> !p_35672_.containsKey(p_35680_)).findAny().ifPresent(p_327046_ -> {
|
||||
throw new IllegalStateException("Missing trade for villager type: " + BuiltInRegistries.VILLAGER_TYPE.getKey(p_327046_));
|
||||
});
|
||||
@@ -1373,7 +_,9 @@
|
||||
@Override
|
||||
public MerchantOffer getOffer(Entity p_219685_, RandomSource p_219686_) {
|
||||
if (p_219685_ instanceof VillagerDataHolder villagerdataholder) {
|
||||
- ItemCost itemcost = new ItemCost(this.trades.get(villagerdataholder.getVillagerData().getType()), this.cost);
|
||||
+ Item item = this.trades.get(villagerdataholder.getVillagerData().getType());
|
||||
+ if (item == null) return null; // FORGE: Account for modded villager types by returning null if there is no trade
|
||||
+ ItemCost itemcost = new ItemCost(item, this.cost);
|
||||
return new MerchantOffer(itemcost, new ItemStack(Items.EMERALD), this.maxUses, this.villagerXp, 0.05F);
|
||||
} else {
|
||||
return null;
|
||||
@@ -0,0 +1,12 @@
|
||||
--- a/net/minecraft/world/entity/npc/VillagerType.java
|
||||
+++ b/net/minecraft/world/entity/npc/VillagerType.java
|
||||
@@ -69,4 +_,9 @@
|
||||
public static VillagerType byBiome(Holder<Biome> p_204074_) {
|
||||
return p_204074_.unwrapKey().map(BY_BIOME::get).orElse(PLAINS);
|
||||
}
|
||||
+
|
||||
+ /** FORGE: Registers the VillagerType that will spawn in the given biome. This method should be called during FMLCommonSetupEvent using event.enqueueWork() */
|
||||
+ public static void registerBiomeType(ResourceKey<Biome> biomeKey, VillagerType villagerType) {
|
||||
+ BY_BIOME.put(biomeKey, villagerType);
|
||||
+ }
|
||||
}
|
||||
@@ -129,7 +129,7 @@
|
||||
protected void blockUsingShield(LivingEntity p_36295_) {
|
||||
super.blockUsingShield(p_36295_);
|
||||
- if (p_36295_.canDisableShield()) {
|
||||
+ if (p_36295_.getMainHandItem().canDisableShield(this.useItem, this, p_36295_)) {
|
||||
+ if (p_36295_.getWeaponItem().canDisableShield(this.useItem, this, p_36295_)) {
|
||||
this.disableShield();
|
||||
}
|
||||
}
|
||||
@@ -142,6 +142,19 @@
|
||||
if (!this.level().isClientSide) {
|
||||
this.awardStat(Stats.ITEM_USED.get(this.useItem.getItem()));
|
||||
}
|
||||
@@ -932,8 +_,11 @@
|
||||
if (p_36383_ >= 3.0F) {
|
||||
int i = 1 + Mth.floor(p_36383_);
|
||||
InteractionHand interactionhand = this.getUsedItemHand();
|
||||
+ // FORGE: cache this.useItem -- if this.stopUsingItem() is called, it will be set to ItemStack.EMPTY
|
||||
+ ItemStack currentItem = this.useItem;
|
||||
this.useItem.hurtAndBreak(i, this, getSlotForHand(interactionhand));
|
||||
- if (this.useItem.isEmpty()) {
|
||||
+ // FORGE: use cached item since this.useItem could be ItemStack.EMPTY
|
||||
+ if (currentItem.isEmpty()) {
|
||||
if (interactionhand == InteractionHand.MAIN_HAND) {
|
||||
this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
|
||||
} else {
|
||||
@@ -950,10 +_,13 @@
|
||||
@Override
|
||||
protected void actuallyHurt(DamageSource p_36312_, float p_36313_) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
if (optional.isEmpty()) {
|
||||
return InteractionResult.PASS;
|
||||
} else {
|
||||
@@ -113,5 +_,16 @@
|
||||
@@ -113,5 +_,21 @@
|
||||
private Optional<BlockState> getStripped(BlockState p_150691_) {
|
||||
return Optional.ofNullable(STRIPPABLES.get(p_150691_.getBlock()))
|
||||
.map(p_150689_ -> p_150689_.defaultBlockState().setValue(RotatedPillarBlock.AXIS, p_150691_.getValue(RotatedPillarBlock.AXIS)));
|
||||
@@ -25,5 +25,10 @@
|
||||
+ public static BlockState getAxeStrippingState(BlockState originalState) {
|
||||
+ Block block = STRIPPABLES.get(originalState.getBlock());
|
||||
+ return block != null ? block.defaultBlockState().setValue(RotatedPillarBlock.AXIS, originalState.getValue(RotatedPillarBlock.AXIS)) : null;
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean canDisableShield(ItemStack stack, ItemStack shield, LivingEntity entity, LivingEntity attacker) {
|
||||
+ return true;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 822 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user