Compare commits

..

No commits in common. "1.20" and "1.7.0" have entirely different histories.
1.20 ... 1.7.0

478 changed files with 5911 additions and 21396 deletions

1
.github/FUNDING.yml vendored
View File

@ -1 +0,0 @@
ko_fi: embeddedt

View File

@ -1,78 +0,0 @@
name: Bug Report
description: "For reporting bugs and other defects"
body:
- type: markdown
attributes:
value: >-
**Need help?** Ask on [Discord](https://discord.gg/rN9Y7caguP) instead of opening an issue.
**Issues that do not meet the requirements below (or are otherwise impossible to address with the given info) will be closed without investigation.**
- type: checkboxes
id: confirmations
attributes:
label: Checklist
options:
- label: I am reporting a defect, not asking for help
required: true
- label: I have searched existing issues and this has not been reported
required: true
- label: I have reduced my mod list to the minimum required to reproduce this issue (see below)
required: true
- type: textarea
id: description
attributes:
label: Bug Description
description: >-
Describe the issue in detail. Be sure to include what you expected to happen and what actually happened.
validations:
required: true
- type: textarea
id: minimal-mods
attributes:
label: Minimal Mod List
description: >-
List ONLY the mods required to reproduce this issue. Maintainers have debugging tools that help them
locate problems quickly, but these generally don't work well in modpacks or large mod sets.
A minimal list should typically contain fewer than 10 mods.
Reports with large mod lists will likely be closed without investigation, unless the problem is very clear.
If you don't know which mods are causing your problem, use binary search:
1. Remove half your mods
2. Test if the issue still occurs
3. If yes, remove half again. If no, restore the last removed half and repeat from step 1.
4. Repeat until only the necessary mods remain
placeholder: "- ModernFix 5.x.x\n- SomeMod 1.2.3"
validations:
required: true
- type: textarea
id: description-reproduction-steps
attributes:
label: Reproduction Steps
description: >-
Provide clear steps to reproduce the bug. Each step should be a single concrete action.
Maintainers are busy and need to be able to quickly replicate your problem. Your reproduction steps should be
clear enough for someone who is unfamiliar with your mods to follow in 5 minutes or less (not counting time
to launch the game).
Providing vague steps is likely to result in the issue being closed.
placeholder: "1. \n2. \n3. "
validations:
required: true
- type: textarea
id: diagnostic-info
attributes:
label: Diagnostic Info
description: >-
Drag and drop `latest.log` from `.minecraft/logs/` for the session where the issue occurred.
Do not paste log text inline. Issues without a valid `latest.log` will be closed.
If a crash occurred, also attach the relevant file from `.minecraft/crash-reports/`.
validations:
required: true

View File

@ -1,6 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: For help with other issues, join our Discord community
url: https://discord.gg/rN9Y7caguP
about: This is the best option for getting help with mod installation, performance issues, and any other support inquiries
# Copied from https://github.com/CaffeineMC/sodium-fabric#community

View File

@ -1,136 +1,26 @@
name: Build ModernFix using Gradle
# This workflow will build a Java project with Gradle
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
on:
push:
branches:
- '**'
tags-ignore:
- '**'
pull_request:
name: Build mod
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-22.04
permissions:
issues: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@v4
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 21
check-latest: true
- name: Check if release branch
id: check_branch
if: github.event_name == 'push'
run: |
if [[ "${{ github.ref }}" =~ ^refs/heads/[0-9]+\. ]]; then
echo "is_release=true" >> $GITHUB_OUTPUT
else
echo "is_release=false" >> $GITHUB_OUTPUT
fi
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: ${{ steps.check_branch.outputs.is_release != 'true' }}
gradle-home-cache-cleanup: true
- name: Remove tags for release on other versions
if: steps.check_branch.outputs.is_release == 'true'
run: ./scripts/tagcleaner.sh
- name: Build ModernFix using Gradle
run: ./gradlew build
- name: Run mixin audit
run: timeout 60 xvfb-run ./gradlew runAuditClient
- name: Publish mod to CurseForge & Modrinth
if: steps.check_branch.outputs.is_release == 'true'
run: ./gradlew publishMods copyJarToBin
env:
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
- name: Capture mod version
if: steps.check_branch.outputs.is_release == 'true'
run: |
echo "MOD_VERSION=$(./gradlew properties -q | grep '^version:' | awk '{print $2}')" >> $GITHUB_ENV
echo "MC_VERSION=$(grep '^minecraft_version=' gradle.properties | cut -d= -f2)" >> $GITHUB_ENV
- name: Comment on fixed issues
if: steps.check_branch.outputs.is_release == 'true'
uses: actions/github-script@v7
with:
script: |
const { execSync } = require('child_process');
const branch = context.ref.replace('refs/heads/', '');
const { data: runs } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'gradle.yml',
branch,
status: 'success',
per_page: 1
});
const logArgs = runs.workflow_runs.length > 0
? `${runs.workflow_runs[0].head_sha}..${context.sha}`
: `-1 ${context.sha}`;
const log = execSync(`git log ${logArgs} --format=%s%n%b`, { encoding: 'utf8' });
const issueNumbers = new Set();
const pattern = /(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s+#(\d+)/gi;
let match;
while ((match = pattern.exec(log)) !== null) {
issueNumbers.add(parseInt(match[1]));
}
if (issueNumbers.size === 0) {
console.log('No fixed issues found in commits');
return;
}
const MARKER = '<!-- modernfix-fix-tracker -->';
const modVersion = process.env.MOD_VERSION;
const mcVersion = process.env.MC_VERSION;
const newLine = `- ${modVersion} for Minecraft ${mcVersion}`;
for (const issueNumber of issueNumbers) {
try {
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100
});
const existing = comments.find(c => c.body.includes(MARKER));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: existing.body + `\n${newLine}`
});
console.log(`Updated comment on issue #${issueNumber}`);
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: `${MARKER}\nThe fix for this issue has been released in the following versions of ModernFix:\n${newLine}`
});
console.log(`Created comment on issue #${issueNumber}`);
}
} catch (e) {
console.log(`Could not comment on #${issueNumber}: ${e.message}`);
}
}
- name: Upload Artifacts to GitHub
uses: actions/upload-artifact@v4
java-version: '17'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build the mod
run: ./gradlew --no-daemon build
- uses: actions/upload-artifact@v2
with:
name: Package
path: bin
path: build/libs

View File

@ -1,27 +0,0 @@
name: Update wiki using WikiGen
on:
push:
branches:
- '1.**'
jobs:
wikigen:
if: github.repository_owner == 'embeddedt'
runs-on: ubuntu-22.04
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate Markdown Patch-List
run: python3 scripts/gen-markdown-patchlist.py
- name: Very legitimate hack for wiki push race condition
run: sleep $((1 + (RANDOM % 30)))
shell: bash
- name: Upload generated file to wiki
uses: SwiftDocOrg/github-wiki-publish-action@v1
with:
path: "doc/generated"
env:
GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.WIKI_TOKEN }}

9
.gitignore vendored
View File

@ -2,15 +2,7 @@ eclipse
run
libs
media
__pycache__
*.pyc
classes/
.architectury-transformer/
fabric/fabricloader.log
fabric/test_run
# Changelog
CHANGELOG.md
# Created by https://www.gitignore.io/api/gradle,intellij,eclipse,windows,osx,linux
@ -81,6 +73,7 @@ fabric.properties
### Eclipse ###
*.pydevproject
.metadata
bin/
tmp/
*.tmp
*.bak

View File

@ -1,21 +0,0 @@
ModernFix is a standard Minecraft-style Gradle project powered by Architectury Loom. To build the mod for all platforms,
run the `build` task (e.g. via `./gradlew build`). You can also use `./gradlew forge:build` or `./gradlew fabric:build`
to build for just one loader (e.g. when debugging and wanting to rebuild quickly).
You must use Java 21 to develop ModernFix as the toolchain requires it. Nonetheless, the built 1.20.1 JAR is still
compatible with Java 17.
## Submitting pull requests
Code or documentation contributions are welcome. Please keep the following points in mind:
* This project supports many Minecraft versions. Ideally, contributions should be made to the oldest relevant MC version (currently 1.20)
and then they will be ported forward.
This somewhat unconventional policy ensures that all supported versions are treated equal when it comes to development,
rather than the onus being on other modders and players to backport changes that are needed. Changes to older versions are
quickly ported up to the latest one as part of the regular development cycle. You are still welcome to open PRs against
a newer branch if desired - but the change will likely be applied manually and not merged as a regular PR.
* Please ensure your code is reasonably neat and sufficiently documented. Remember that self-documenting code is always
better.

View File

@ -1,17 +1,12 @@
# ModernFix
A performance mod for modern Minecraft that significantly improves launch times, world load times, memory usage, etc.
A Forge 1.16 mod that uses mixins to make the game slightly more performant (and hopefully less buggy too).
In the words of asiekierka, questionable "performance improvements" that are not in Forge for probably very good reasons.
Some fixes are based on prior work in various Forge PRs (check commit history and/or code comments). The config system
is directly derived from Sodium and used under the terms of the LGPL-3.0 license.
## Development builds (generally stable, but may occasionally have bugs)
- 1.16.5: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.16/Package.zip
- 1.18.2: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.18/Package.zip
- 1.19.2: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.19.2/Package.zip
- 1.20.1: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.20/Package.zip
- 1.20.2: https://nightly.link/embeddedt/ModernFix/workflows/gradle/1.20.2/Package.zip
------------
![YourKit logo](https://www.yourkit.com/images/yklogo.png)

View File

@ -1,54 +0,0 @@
plugins {
id 'com.gradleup.shadow' version '8.3.9'
id 'java-library'
}
repositories {
mavenCentral()
maven { url uri("https://maven.fabricmc.net") }
maven { url "https://maven.neoforged.net/releases" }
}
dependencies {
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
compileOnly 'com.google.auto.service:auto-service:1.1.1'
implementation 'com.google.code.gson:gson:2.10.1'
shadow 'com.google.code.gson:gson:2.10.1'
implementation 'com.google.auto:auto-common:1.2.1'
shadow 'com.google.auto:auto-common:1.2.1'
implementation 'com.google.guava:guava:21.0'
shadow 'com.google.guava:guava:21.0'
implementation project(":annotations")
shadow project(":annotations")
// Shadow annotations
implementation 'net.fabricmc:sponge-mixin:0.12.5+'
implementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
implementation 'net.minecraftforge:mergetool:1.1.7'
implementation 'net.neoforged:mergetool:2.0.2'
}
tasks.withType(JavaCompile) {
options.release = 17
}
shadowJar {
dependencies {
include(dependency('net.fabricmc:sponge-mixin:'))
include(dependency('net.fabricmc:fabric-loader:'))
include(dependency(':mergetool:'))
}
// shadowJar bug
include '*.jar'
include 'META-INF/services/javax.annotation.processing.Processor'
include 'META-INF/gradle/incremental.annotation.processors'
include 'org/spongepowered/asm/mixin/Mixin.class'
include 'org/fury_phoenix/**/*'
include {it.getName() == 'OnlyIn.class'}
include {it.getName() == 'Dist.class'}
include {it.getName() == 'Environment.class'}
include {it.getName() == 'EnvType.class'}
}
version = '1.1.4'

View File

@ -1,184 +0,0 @@
package org.fury_phoenix.mixinAp.annotation;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import net.fabricmc.api.Environment;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.annotation.IgnoreMixin;
import org.fury_phoenix.mixinAp.util.TypedAccessorMap;
import org.spongepowered.asm.mixin.Mixin;
import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
import static java.util.AbstractMap.SimpleImmutableEntry;
public class ClientMixinValidator {
private final Messager messager;
private final Elements elemUtils;
private final Types types;
private final boolean debug;
private static final TypedAccessorMap<Annotation> markers = new TypedAccessorMap<>();
private static final Map.Entry<Class<Environment>, Function<? super Environment, ?>>
FabricAccessor = new SimpleImmutableEntry<>(Environment.class, Environment::value);
private static final Map.Entry<
Class<net.minecraftforge.api.distmarker.OnlyIn>,
Function<? super net.minecraftforge.api.distmarker.OnlyIn, ?>>
ForgeAccessor = new SimpleImmutableEntry<>(
net.minecraftforge.api.distmarker.OnlyIn.class,
net.minecraftforge.api.distmarker.OnlyIn::value
);
private static final Map.Entry<
Class<net.neoforged.api.distmarker.OnlyIn>,
Function<? super net.neoforged.api.distmarker.OnlyIn, ?>>
NeoForgeAccessor = new SimpleImmutableEntry<>(
net.neoforged.api.distmarker.OnlyIn.class,
net.neoforged.api.distmarker.OnlyIn::value
);
static {
markers.put(FabricAccessor);
markers.put(ForgeAccessor);
markers.put(NeoForgeAccessor);
}
private static final Collection<String> unannotatedClasses = new HashSet<>();
public ClientMixinValidator(ProcessingEnvironment env) {
debug = Boolean.valueOf(env.getOptions().get("org.fury_phoenix.mixinAp.validator.debug"));
messager = env.getMessager();
elemUtils = env.getElementUtils();
types = env.getTypeUtils();
}
public boolean validateMixin(TypeElement annotatedMixinClass) {
return targetsClient(annotatedMixinClass) &&
(annotatedMixinClass.getAnnotation(ClientOnlyMixin.class) == null);
}
public boolean targetsClient(TypeElement annotatedMixinClass) {
return targetsClient(getTargets(annotatedMixinClass)) &&
!isIgnored(annotatedMixinClass);
}
private boolean targetsClient(Collection<?> classTargets) {
return classTargets.stream().anyMatch(this::targetsClient);
}
private boolean targetsClient(Object classTarget) {
if (classTarget instanceof TypeElement te) {
return isClientMarked(te);
} else if (classTarget instanceof TypeMirror tm) {
var el = types.asElement(tm);
return el != null ? targetsClient(el) : warn("TypeMirror of " + tm);
} else if (classTarget instanceof String s) {
var te = elemUtils.getTypeElement(toSourceString(s.split("\\$")[0]));
return te != null ? targetsClient(te) : warn(s);
} else {
throw new IllegalArgumentException("Unhandled type: "
+ classTarget.getClass() + "\n" + "Stringified contents: "
+ classTarget.toString());
}
}
private boolean isClientMarked(TypeElement te) {
for (var entry : markers.entrySet()) {
var marker = te.getAnnotation(entry.getKey());
if(marker == null) continue;
return entry.getValue().apply(marker).toString().equals("CLIENT");
}
if(debug && unannotatedClasses.add(te.toString())) {
messager.printMessage(Diagnostic.Kind.WARNING,
"No marker annotations present on " + te + "!");
}
return false;
}
private boolean isIgnored(TypeElement te) {
if(te.getAnnotation(IgnoreMixin.class) != null) {
messager.printMessage(Diagnostic.Kind.WARNING,
toSourceString(te.toString()) + " is ignored!");
return true;
}
return false;
}
private boolean warn(Object o) {
messager.printMessage(Diagnostic.Kind.WARNING,
toSourceString(o.toString()) + " can't be loaded, so it is skipped!");
return false;
}
public Map.Entry<? extends CharSequence, ? extends CharSequence>
getClientMixinEntry(TypeElement annotatedMixinClass) {
return new SimpleImmutableEntry<>(
annotatedMixinClass.getQualifiedName(),
getTargets(annotatedMixinClass)
.stream()
.filter(this::targetsClient)
.map(Object::toString)
.map(ClientMixinValidator::toSourceString)
.collect(Collectors.joining(", "))
);
}
private Collection<Object> getTargets(TypeElement annotatedMixinClass) {
Collection<? extends TypeMirror> clzsses = Set.of();
Collection<? extends String> imaginaries = Set.of();
TypeMirror MixinElement = elemUtils.getTypeElement(Mixin.class.getName()).asType();
for (var mirror : annotatedMixinClass.getAnnotationMirrors()) {
if(!types.isSameType(mirror.getAnnotationType(), MixinElement))
continue;
@SuppressWarnings("unchecked")
var wrappedClzss = (List<? extends AnnotationValue>)
getAnnotationValue(mirror, "value").getValue();
clzsses = wrappedClzss.stream()
.map(AnnotationValue::getValue)
.map(TypeMirror.class::cast)
.collect(Collectors.toSet());
@SuppressWarnings("unchecked")
var wrappedStrings = (List<? extends AnnotationValue>)
getAnnotationValue(mirror, "targets").getValue();
imaginaries = wrappedStrings.stream()
.map(AnnotationValue::getValue)
.map(String.class::cast)
.collect(Collectors.toSet());
}
return Stream.of(clzsses, imaginaries)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
}
public static String toSourceString(String bytecodeName) {
return bytecodeName.replaceAll("\\/", ".");
}
}

View File

@ -1,118 +0,0 @@
package org.fury_phoenix.mixinAp.annotation;
import com.google.auto.service.AutoService;
import com.google.common.base.Throwables;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import org.fury_phoenix.mixinAp.config.MixinConfig;
@SupportedAnnotationTypes({"org.spongepowered.asm.mixin.Mixin", "org.embeddedt.modernfix.annotation.ClientOnlyMixin"})
@SupportedOptions({"rootProject.name", "project.name", "org.fury_phoenix.mixinAp.validator.debug"})
@AutoService(Processor.class)
public class MixinProcessor extends AbstractProcessor {
// Remember to call toString when using aliases
private static final Map<String, String> aliases = Map.of(
"Mixin", "mixins",
"ClientOnlyMixin", "client"
);
private final Map<String, List<String>> mixinConfigList = new HashMap<>();
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
if(roundEnv.processingOver()){
filterMixinSets();
// create record for serialization, compute package name
String packageName = Optional.ofNullable(mixinConfigList.get("mixins"))
.orElse(mixinConfigList.get("client"))
.get(0).split("(?<=mixin)")[0];
finalizeMixinConfig();
new MixinConfig(packageName,
mixinConfigList.get("mixins"),
mixinConfigList.get("client")
).generateMixinConfig(processingEnv);
} else {
processMixins(annotations, roundEnv);
}
} catch (Exception e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Fatal error:" +
Throwables.getStackTraceAsString(e));
throw new RuntimeException(e);
// Halt the AP to prevent nonsense errors
}
return false;
}
private void processMixins(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set<? extends Element> annotatedMixins = roundEnv.getElementsAnnotatedWith(annotation);
Stream<TypeElement> mixinStream =
annotatedMixins.stream()
.map(TypeElement.class::cast);
validateCommonMixins(annotation, mixinStream);
List<String> mixins =
annotatedMixins.stream()
.map(TypeElement.class::cast)
.map(e -> processingEnv.getElementUtils().getBinaryName(e).toString())
.collect(Collectors.toList());
mixinConfigList.putIfAbsent(aliases.get(annotation.getSimpleName().toString()), mixins);
}
}
private void filterMixinSets() {
List<String> commonSet = mixinConfigList.get("mixins");
if(commonSet == null) return;
commonSet.removeAll(mixinConfigList.get("client"));
}
private void validateCommonMixins(TypeElement annotation, Stream<TypeElement> mixins) {
if(!annotation.getSimpleName().toString().equals("Mixin"))
return;
ClientMixinValidator validator = new ClientMixinValidator(processingEnv);
// The implementation may throw a CME
mixins.sequential()
.filter(validator::validateMixin)
.map(validator::getClientMixinEntry)
.forEach(this::logClientClassTarget);
}
private void logClientClassTarget(Map.Entry<? extends CharSequence, ? extends CharSequence> mixin) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
"Mixin " + mixin.getKey() + " targets client-side classes: " + mixin.getValue());
}
private void finalizeMixinConfig() {
// relativize class names
for(var list : mixinConfigList.values()) {
list.replaceAll(className -> className.split("(?<=mixin.)")[1]);
}
}
}

View File

@ -1,65 +0,0 @@
package org.fury_phoenix.mixinAp.config;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Optional;
import javax.annotation.processing.ProcessingEnvironment;
import javax.tools.StandardLocation;
public record MixinConfig(
boolean required,
String minVersion,
@SerializedName("package")
String packageName,
String plugin,
String compatibilityLevel,
@SerializedName("mixins")
List<String> commonMixins,
@SerializedName("client")
List<String> clientMixins,
InjectorOptions injectors, OverwriteOptions overwrites
) {
public MixinConfig(String packageName, List<String> commonMixins, List<String> clientMixins) {
this(true, "0.8", packageName, "org.embeddedt.modernfix.core.ModernFixMixinPlugin", "JAVA_17",
commonMixins, clientMixins, InjectorOptions.DEFAULT, OverwriteOptions.DEFAULT);
}
public record InjectorOptions(int defaultRequire) {
public static final InjectorOptions DEFAULT = new InjectorOptions(1);
}
public record OverwriteOptions(boolean conformVisibility) {
public static final OverwriteOptions DEFAULT = new OverwriteOptions(true);
}
public void generateMixinConfig(ProcessingEnvironment env) throws IOException {
try (
Writer mixinConfigWriter = env.getFiler()
.createResource(StandardLocation.SOURCE_OUTPUT, "",
MixinConfig.computeMixinConfigPath(
Optional.of(env.getOptions().get("rootProject.name")),
Optional.ofNullable(env.getOptions().get("project.name"))
)
).openWriter()
) {
String mixinConfig = new GsonBuilder()
.setPrettyPrinting()
.create()
.toJson(this);
mixinConfigWriter.write(mixinConfig);
mixinConfigWriter.write("\n");
} catch (IOException e) { throw e; }
}
private static String computeMixinConfigPath(Optional<String> rootProjectName, Optional<String> projectName) {
return "resources/" +
rootProjectName.get() +
(projectName.isPresent() ? "-" : "") +
projectName.orElse("") +
".mixins.json";
}
}

View File

@ -1,37 +0,0 @@
package org.fury_phoenix.mixinAp.util;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import static java.util.Map.Entry;
/**
* Type-safe heterogenous map of accessors
* @author Fury_Phoenix
* @reason Type-safety since K, V of Map are non-identical
* @param <SuperType> The supertype of desired types.
* This is useful in cases such as <A extends Annotation>.
*/
public class TypedAccessorMap<SuperType> {
private final Map<Class<? extends SuperType>, Function<Object, ?>> typedAccessors = new HashMap<>();
public <T extends SuperType> void put(Class<T> key, Function<? super T, ?> func) {
Objects.requireNonNull(func);
typedAccessors.put(Objects.requireNonNull(key), o -> func.apply(key.cast(o)));
}
public <T extends SuperType> void put(Entry<Class<T>, Function<? super T, ?>> entry) {
put(entry.getKey(), entry.getValue());
}
public <T extends SuperType> Function<Object, ?> get(Class<T> key) {
return typedAccessors.get(key);
}
public Set<Entry<Class<? extends SuperType>, Function<Object, ?>>> entrySet() {
return typedAccessors.entrySet();
}
}

View File

@ -1 +0,0 @@
org.fury_phoenix.mixinAp.annotation.MixinProcessor,aggregating

View File

@ -1,10 +0,0 @@
plugins {
id("java")
}
version = "1.1.0"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@ -1,11 +0,0 @@
package org.embeddedt.modernfix.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface ClientOnlyMixin {
}

View File

@ -1,9 +0,0 @@
package org.embeddedt.modernfix.annotation;
public enum FeatureLevel {
GA, BETA;
public boolean isAtLeast(FeatureLevel required) {
return this.ordinal() >= required.ordinal();
}
}

View File

@ -1,11 +0,0 @@
package org.embeddedt.modernfix.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface IgnoreMixin {
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The config system will ignore mixins with this annotation when generating config options unless running
* in a dev environment.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface IgnoreOutsideDev {
}

View File

@ -1,12 +0,0 @@
package org.embeddedt.modernfix.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.PACKAGE})
public @interface RequiresFeatureLevel {
FeatureLevel value() default FeatureLevel.GA;
}

View File

@ -1,12 +0,0 @@
package org.embeddedt.modernfix.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.PACKAGE})
public @interface RequiresMod {
String value() default "";
}

1
bin/.gitignore vendored
View File

@ -1 +0,0 @@
*.jar

167
build.gradle Normal file
View File

@ -0,0 +1,167 @@
plugins {
id "dev.architectury.loom" version "1.0.312"
id "maven-publish"
id 'com.matthewprenger.cursegradle' version '1.4.0'
}
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8
group = 'org.embeddedt'
version = '1.7.0'
java {
archivesBaseName = 'modernfix-mc' + minecraft_version
}
loom {
// use this if you are using the official mojang mappings
// and want loom to stop warning you about their license
silentMojangMappingsLicense()
// since loom 0.10, you are **required** to use the
// "forge" block to configure forge-specific features,
// such as the mixinConfigs array or datagen
forge {
// specify the mixin configs used in this mod
// this will be added to the jar manifest as well!
mixinConfigs = [
"modernfix.mixins.json"
]
}
mixin.defaultRefmapName = "modernfix.refmap.json"
}
repositories {
maven { url 'https://modmaven.dev/' }
maven {
url "https://cursemaven.com"
content {
includeGroup "curse.maven"
}
}
maven {
name = 'ParchmentMC'
url = 'https://maven.parchmentmc.org'
}
maven {
// Shedaniel's maven (Architectury API)
url = "https://maven.architectury.dev"
content {
includeGroup "me.shedaniel"
}
}
maven {
// saps.dev Maven (KubeJS and Rhino)
url = "https://maven.saps.dev/minecraft"
content {
includeGroup "dev.latvian.mods"
}
}
}
dependencies {
// to change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
// choose what mappings you want to use here
// leave this uncommented if you want to use
// mojang's official mappings, or feel free
// to add your own mappings here (how about
// mojmap layered with parchment, for example?)
mappings loom.layered() {
officialMojangMappings()
parchment("org.parchmentmc.data:parchment-${minecraft_version}:${parchment_version}@zip")
}
// uncomment this if you want to use yarn mappings
// mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
// your forge dependency, this is **required** when using Forge Loom in forge mode!
forge "net.minecraftforge:forge:${project.forge_version}"
modRuntimeOnly "curse.maven:lazydfu-460819:${lazydfu_version}"
modCompileOnly("mezz.jei:jei-${minecraft_version}:${jei_version}")
modRuntimeOnly("mezz.jei:jei-${minecraft_version}:${jei_version}")
modCompileOnly("curse.maven:refinedstorage-243076:${refined_storage_version}")
modImplementation("dev.latvian.mods:kubejs-forge:${kubejs_version}")
}
tasks.withType(JavaCompile) {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
options.encoding = "UTF-8"
// The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too
// JDK 9 introduced a new way of specifying this that will make sure no newer classes or methods are used.
// We'll use that if it's available, but otherwise we'll use the older option.
def targetVersion = 8
if (JavaVersion.current().isJava9Compatible()) {
options.release = targetVersion
}
}
java {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
}
// Example for how to get properties into the manifest for reading at runtime.
jar {
manifest {
attributes([
"Specification-Title" : "modernfix",
"Operative-Class" : "org.embeddedt.modernfix.agent.Agent",
//"Specification-Vendor": "modernfix authors",
"Specification-Version" : "1", // We are version 1 of ourselves
"Implementation-Title" : project.name,
"Implementation-Version" : project.jar.archiveVersion,
//"Implementation-Vendor": "modernfix authors",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
])
}
}
// configure the maven publication
publishing {
publications {
mavenJava(MavenPublication) {
// add all the jars that should be included when publishing to maven
artifact(remapJar) {
builtBy remapJar
}
artifact(sourcesJar) {
builtBy remapSourcesJar
}
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
// Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
}
}
curseforge {
if (System.getenv("CURSEFORGE_TOKEN") != null) {
apiKey = System.getenv("CURSEFORGE_TOKEN")
project {
id = "790626"
changelog = '[Changelog is not currently available]'
changelogType = "markdown"
releaseType = "release"
addGameVersion "Forge"
addGameVersion minecraft_version
mainArtifact remapJar
}
}
}

View File

@ -1,210 +0,0 @@
plugins {
id("net.neoforged.moddev.legacyforge") version("2.0.134")
id("me.modmuss50.mod-publish-plugin") version("1.1.0")
}
val minecraft_version = rootProject.properties["minecraft_version"].toString()
group = "org.embeddedt"
val gitVersion = providers.of(GitVersionSource::class) {
parameters {
minecraftVersion.set(minecraft_version)
projectDir.set(rootProject.layout.projectDirectory)
}
}
version = gitVersion.get()
base.archivesName = "modernfix-forge"
legacyForge {
enable {
forgeVersion = rootProject.properties["forge_version"].toString()
isDisableRecompilation = System.getenv("CI") == "true"
}
rootProject.properties["parchment_version"]?.let { parchmentVer ->
parchment {
minecraftVersion = minecraft_version
mappingsVersion = parchmentVer.toString()
}
}
runs {
create("client") {
client()
}
create("server") {
server()
}
create("auditClient") {
client()
jvmArguments.addAll("-Dmodernfix.auditAndExit=true", "-Djava.awt.headless=true")
}
}
mods {
create("modernfix") {
sourceSet(sourceSets.main.get())
}
}
}
mixin {
add(sourceSets.main.get(), "modernfix.refmap.json")
config("modernfix-modernfix.mixins.json")
}
tasks.named<Jar>("jar") {
manifest.attributes(mapOf(
"MixinConfigs" to "modernfix-modernfix.mixins.json",
"Specification-Version" to "1",
"Implementation-Title" to project.name,
"Implementation-Version" to version
))
}
java {
val curSourceCompatLevel = JavaVersion.VERSION_17
sourceCompatibility = curSourceCompatLevel
targetCompatibility = curSourceCompatLevel
}
repositories {
exclusiveContent {
forRepository {
maven {
// location of the maven that hosts JEI files
name = "Progwml6 maven"
url = uri("https://dvs1.progwml6.com/files/maven/")
}
}
forRepository {
maven {
name = "ModMaven"
url = uri("https://modmaven.dev")
}
}
filter {
includeGroup("mezz.jei")
}
}
exclusiveContent {
forRepository {
maven("https://cursemaven.com")
}
filter {
includeGroup("curse.maven")
}
}
}
val embed by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
isTransitive = true
}
dependencies {
implementation(project(":annotations"))
embed(project(":annotations"))
"additionalRuntimeClasspath"(project(":annotations"))
annotationProcessor(project(path = ":annotation-processor", configuration = "shadow"))
val mixinextrasVersion = rootProject.properties["mixinextras_version"].toString()
implementation("io.github.llamalad7:mixinextras-common:${mixinextrasVersion}")
annotationProcessor("net.fabricmc:sponge-mixin:0.12.5+mixin.0.8.5")
annotationProcessor("io.github.llamalad7:mixinextras-common:${mixinextrasVersion}")
implementation("io.github.llamalad7:mixinextras-forge:${mixinextrasVersion}")
"jarJar"("io.github.llamalad7:mixinextras-forge:${mixinextrasVersion}")
val jei_version = rootProject.properties["jei_version"].toString()
modCompileOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}")
modCompileOnly("curse.maven:spark-361579:${rootProject.properties["spark_version"].toString()}")
modCompileOnly("curse.maven:ctm-267602:${rootProject.properties["ctm_version"].toString()}")
modCompileOnly("curse.maven:ldlib-626676:${rootProject.properties["ldlib_version"].toString()}")
modCompileOnly("curse.maven:supermartijncore-454372:4455391")
modCompileOnly("curse.maven:patchouli-306770:6164575")
modCompileOnly("curse.maven:cofhcore-69162:5374122")
modCompileOnly("curse.maven:resourcefullib-570073:5659871")
modCompileOnly("curse.maven:kubejs-238086:5853326")
modCompileOnly("curse.maven:terrablender-563928:6290448")
}
tasks.named<Jar>("jar") {
from(embed.map { if (it.isDirectory) it else zipTree(it) })
}
// For the AP
tasks.withType<JavaCompile>().configureEach {
if (!name.lowercase().contains("test")) {
options.compilerArgs.addAll(
listOf(
"-ArootProject.name=${rootProject.name}",
"-Aproject.name=${project.name}"
)
)
}
}
sourceSets {
main {
resources.srcDir(
layout.buildDirectory.dir("generated/sources/annotationProcessor/java/main/resources")
)
}
}
tasks.named<ProcessResources>("processResources") {
dependsOn(tasks.named("compileJava"))
inputs.property("version", project.version)
filesMatching("META-INF/mods.toml") {
expand("version" to project.version)
}
}
val finalJarTask = "reobfJar"
tasks.register<Copy>("copyJarNameConsistent") {
from(tasks.named<Jar>(finalJarTask).get().outputs.files)
into(project.file("build/libs"))
rename { _ -> "modernfix-" + project.name + "-latest.jar" }
}
tasks.register<Copy>("copyJarToBin") {
from(tasks.named<Jar>(finalJarTask).get().outputs.files)
into(rootProject.file("bin"))
mustRunAfter(tasks.named("copyJarNameConsistent"))
}
tasks.named("build") {
dependsOn("copyJarToBin", "copyJarNameConsistent")
}
publishMods {
file.set(tasks.named<Jar>(finalJarTask).flatMap { it.archiveFile })
displayName.set(tasks.named<Jar>(finalJarTask).flatMap { it.archiveFileName })
changelog = "Please check the [GitHub wiki](https://github.com/embeddedt/ModernFix/wiki/Changelog) for major changes."
type = STABLE
modLoaders.add("forge")
curseforge {
projectId = "790626"
projectSlug = "modernfix"
accessToken = providers.environmentVariable("CURSEFORGE_TOKEN")
minecraftVersions.add(minecraft_version)
}
modrinth {
projectId = "nmDcB62a"
accessToken = providers.environmentVariable("MODRINTH_TOKEN")
minecraftVersions.add(minecraft_version)
}
}
tasks.named("publishMods") {
dependsOn(finalJarTask)
}

View File

@ -1,7 +0,0 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}

View File

@ -1,61 +0,0 @@
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.gradle.process.ExecOperations
import java.io.ByteArrayOutputStream
import java.io.File
import javax.inject.Inject
abstract class GitVersionSource : ValueSource<String, GitVersionSource.Parameters> {
interface Parameters : ValueSourceParameters {
val minecraftVersion: Property<String>
val projectDir: DirectoryProperty
}
@get:Inject
abstract val execOperations: ExecOperations
override fun obtain(): String {
val minecraftVersion = parameters.minecraftVersion.get()
val workDir = parameters.projectDir.get().asFile
val releaseLine = workDir.resolve("release_line.txt").readText().trim()
val patch = try {
// Find the most recent first-parent commit that touched release_line.txt
val lineStartCommit = git(workDir,
"log", "--first-parent",
"-n", "1",
"--format=%H",
"--",
"release_line.txt"
).trim()
if (lineStartCommit.isEmpty()) {
// count all first-parent commits as a safe fallback
git(workDir, "rev-list", "--count", "--first-parent", "HEAD")
.trim().toIntOrNull() ?: 0
} else {
git(workDir, "rev-list", "--count", "--first-parent", "$lineStartCommit..HEAD")
.trim().toIntOrNull() ?: 0
}
} catch (_: Exception) {
// Git is unavailable or this is not a git repository
999
}
return "$releaseLine.$patch+mc$minecraftVersion"
}
private fun git(workDir: File, vararg args: String): String {
val output = ByteArrayOutputStream()
execOperations.exec {
commandLine("git", *args)
standardOutput = output
workingDir(workDir)
}
return output.toString(Charsets.UTF_8)
}
}

View File

@ -1 +0,0 @@
*.md

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@ -1,34 +1,18 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx2G
org.gradle.jvmargs=-Xmx1G
junit_version=5.10.0-M1
mixinextras_version=0.4.1
# tell architectury loom that this project is a forge project.
# this will enable us to use the "forge" dependency.
# using archloom without this is possible and will give you a
# "standard" loom installation with some extra features.
loom.platform=forge
mod_id=modernfix
minecraft_version=1.20.1
enabled_platforms=forge
forge_version=1.20.1-47.4.0
parchment_version=2023.07.09
refined_storage_version=4392788
jei_version=15.8.0.11
rei_version=11.0.597
ctm_version=5983309
ldlib_version=5927130
kubejs_version=2001.6.5-build.16
rhino_version=2001.2.3-build.10
supported_minecraft_versions=1.20.1
fabric_loader_version=0.16.10
fabric_api_version=0.86.0+1.20.1
continuity_version=3.0.0-beta.2+1.19.3
modmenu_version=7.0.0-beta.2
diagonal_fences_version=4558828
spark_version=4587310
use_fabric_api_at_runtime=true
# Look up maven coordinates when changing shadow_version
shadow_version=7.1.2
minecraft_version=1.16.5
forge_version=1.16.5-36.2.39
lazydfu_version=3249059
mekanism_version=1.16.5-10.1.2.457
parchment_version=2022.03.06
jei_version=7.7.1.153
refined_storage_version=3807951
kubejs_version=1605.3.19-build.299

View File

@ -1,9 +0,0 @@
Depending on the size of this release, there may be a human-readable changelog available on [the wiki page](https://github.com/embeddedt/ModernFix/wiki/Changelog).
## Changes since [[modernFixVersionRef]]
{{#commits}}
{{#ifMatches messageTitle "^(?!Merge).*"}}
* [{{{messageTitle}}}](https://github.com/embeddedt/ModernFix/commit/{{hashFull}}) - {{{authorName}}}
{{/ifMatches}}
{{/commits}}

Binary file not shown.

View File

@ -1,7 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

44
gradlew vendored
View File

@ -15,8 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -57,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -82,12 +80,13 @@ do
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# 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"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -134,29 +133,22 @@ location of your Java installation."
fi
else
JAVACMD=java
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.
which java >/dev/null 2>&1 || 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.
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=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -201,15 +193,11 @@ if "$cygwin" || "$msys" ; then
done
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, 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.
# 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.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
@ -217,12 +205,6 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

37
gradlew.bat vendored
View File

@ -13,10 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -27,8 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -43,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
if "%ERRORLEVEL%" == "0" goto execute
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
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.
goto fail
@ -59,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
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
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.
goto fail
@ -78,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@ -1 +0,0 @@
5.27

View File

@ -1,53 +0,0 @@
#!/bin/bash
set -e
WORK_DIR=`mktemp -d`
# deletes the temp directory
function cleanup {
cd $HOME
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
# clone ModernFix repo
echo "downloading temporary modernfix..."
cd $WORK_DIR
git clone git@github.com:embeddedt/ModernFix.git mfix &>/dev/null
cd mfix
# gather version list
readarray -t all_versions < <(scripts/branchlist.sh)
last_released_version=""
do_release() {
echo "will now make release for $1"
git checkout $1 &>/dev/null || git checkout -b $1 &>/dev/null
current_tag=$(git describe --tags --abbrev=0)
echo "we think the current tag is $current_tag"
echo "the current commit head is $(git rev-parse HEAD)"
old_version_specifier=$(echo $current_tag | awk -F+ '{print $2}')
read -e -p "new tag name (${old_version_specifier}): " -i "${last_released_version}" tag_name
if [[ $tag_name != *"+"* ]]; then
tag_name=${tag_name}+${old_version_specifier}
fi
last_released_version=$(echo $tag_name | awk -F+ '{print $1}')
git tag -a $tag_name -m "$tag_name"
git push --tags
gh release create $tag_name --target $1 --title "$tag_name" --notes ""
# now delete local tag to prevent messing up the detected tag for the next version
git tag -d $tag_name &>/dev/null
}
for version in "${all_versions[@]}"; do
read -r -p "Make release on ${version} branch? [y/N] " response
case "$response" in
[yY][eE][sS]|[yY])
do_release $version
;;
*)
;;
esac
done

View File

@ -1,2 +0,0 @@
#!/bin/bash
git ls-remote --heads origin | awk '{print $2}' | grep -E '^refs/heads/[0-9]+\.' | sed 's:.*/::' | sort -V | grep -E '^[0-9]+\.[0-9]*(\.[0-9]*)?$'

View File

@ -1,51 +0,0 @@
#!/usr/bin/python3
import argparse, json, os, subprocess, sys
# to import other scripts in same folder
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from contextlib import redirect_stdout
from modernfixlib import get_valid_mixin_options
parser = argparse.ArgumentParser(description='Generate ModernFix patch summary Markdown file.')
parser.add_argument('-p', '--langpath', default='src/main/resources/assets/modernfix/lang/en_us.json')
args = parser.parse_args()
branch_name = subprocess.check_output(['git', 'branch', '--show-current']).decode("utf-8").strip()
with open('doc/generated/' + branch_name + '-Summary-of-Patches.md', 'w') as output_file:
all_current_mixin_options = get_valid_mixin_options()
options_missing_descriptions = set()
with redirect_stdout(output_file):
with open(args.langpath) as lang_json:
lang_obj = json.loads(lang_json.read())
option_names = set()
for key, value in lang_obj.items():
if key.startswith("modernfix.option.mixin."):
option_names.add(key.replace("modernfix.option.", ""))
option_names_sorted = list(option_names)
option_names_sorted.sort()
print()
for option in option_names_sorted:
if option not in all_current_mixin_options:
continue
option_description = lang_obj.get("modernfix.option." + option)
option_friendly_name = lang_obj.get("modernfix.option.name." + option)
print(f"### `{option}`")
print()
if option_description is not None:
print(option_description)
print("")
else:
options_missing_descriptions.add(option)
options_missing_descriptions.update(all_current_mixin_options.difference(option_names))
# sort the list of missing descriptions and print them out if there are any
missing_descriptions_list = list(options_missing_descriptions)
missing_descriptions_list.sort()
num_missing = len(missing_descriptions_list)
if num_missing > 0:
print(f"Missing {num_missing} descriptions:")
for option in missing_descriptions_list:
print(f" - {option}")

View File

@ -1,13 +0,0 @@
#!/usr/bin/python3
import re
with open('Summary-of-Patches.md', 'r') as file:
data = file.read().rstrip()
matches = re.findall(r"## `(.*?)`((?:(?!\n#).)*)", data, re.DOTALL)
for m in matches:
option_name = m[0].strip()
option_desc = m[1].strip().replace("\n", "\\n").replace('"', '\\"')
print(f" \"modernfix.option.mixin.{m[0].strip()}\": \"{option_desc}\",")

View File

@ -1,22 +0,0 @@
import os
import re
def get_valid_mixin_options():
all_mixin_options = set()
# gather all mixins in mixin folders
for platform in [ "common", "forge" ]:
base_path = f"{platform}/src/main/java/org/embeddedt/modernfix/{platform}/mixin"
for root, dirs, files in os.walk(base_path):
for file in files:
if file.endswith(".java"):
mixin_name = root.replace(base_path, "").replace(os.path.sep, ".")
if mixin_name.startswith("."):
mixin_name = mixin_name[1:]
all_mixin_options.add("mixin." + mixin_name)
# gather any mixin strings referenced in ModernFixEarlyConfig
with open('src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java') as config_java:
for line in config_java:
for option in re.findall(r"\.put[A-Za-z_]*\(.*\"(mixin(?:\.[a-z_]+)+)\"", line):
all_mixin_options.add(option)
return all_mixin_options

View File

@ -1,57 +0,0 @@
#!/bin/bash
set -e
WORK_DIR=`mktemp -d`
# deletes the temp directory
function cleanup {
cd $HOME
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
trap "exit" INT
# clone ModernFix repo
echo "downloading temporary modernfix..."
cd $WORK_DIR
git clone git@github.com:embeddedt/ModernFix.git mfix &>/dev/null
cd mfix
# gather version list
readarray -t all_versions < <(scripts/branchlist.sh)
echo "found versions: ${all_versions[@]}"
# checkout base version
git checkout -b propagations/${all_versions[0]} origin/${all_versions[0]} &>/dev/null
our_version=${all_versions[0]}
restore_version=$our_version
for version in "${all_versions[@]}"; do
if ! { echo "$version"; echo "$our_version"; } | sort --version-sort --check &>/dev/null; then
echo -n "merging $our_version into ${version}... "
git checkout -b propagations/$version origin/$version &>/dev/null
if ! git merge --no-commit propagations/$our_version &>/dev/null; then
merge_failed=yes
echo "failed, this merge must be done manually using the provided shell"
else
merge_failed=no
echo -n "done"
fi
if [ "x$merge_failed" == "xyes" ]; then
git status
bash
echo -e $'Press any key to commit or Ctrl+C to completely abort...\n'
read -rs -n1
fi
if (git add . && git commit -m "Merge $our_version into $version" &>/dev/null); then
echo
git push -u origin propagations/$version:$version &>/dev/null
else
echo -e "\b\b\b\bnothing to merge"
fi
our_version=$version
fi
done

View File

@ -1,9 +0,0 @@
#!/bin/bash
echo -n "Currently on: "
git describe
echo -n "New version: "
read newtag
git tag -a $newtag -m "$newtag"
git push
git push --tags
./gradlew fabric:publishToModSites forge:publishToModSites

View File

@ -1,13 +0,0 @@
#!/bin/bash
# if current tag is 5.2.3+1.16.5, strip all other 5.2.3+* tags
current_tag=$(git describe --tags --abbrev=0)
current_tag_prefix=$(echo $current_tag | sed 's/+.*/+/g')
git tag | while read -r other_tag; do
if [ "x$other_tag" != "x$current_tag" ] ; then
if [[ $other_tag == ${current_tag_prefix}* ]]; then
git tag -d $other_tag
fi
fi
done

10
settings.gradle Normal file
View File

@ -0,0 +1,10 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven { url "https://maven.fabricmc.net/" }
maven { url "https://maven.architectury.dev/" }
maven { url "https://maven.minecraftforge.net/" }
}
}
rootProject.name = 'modernfix'

View File

@ -1,8 +0,0 @@
include("annotation-processor")
include("annotations")
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version ("1.0.0")
}
rootProject.name = "modernfix"

View File

@ -0,0 +1,213 @@
package cpw.mods.modlauncher;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import cpw.mods.modlauncher.api.ITransformer;
import cpw.mods.modlauncher.api.ITransformerActivity;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import net.minecraftforge.coremod.transformer.CoreModBaseTransformer;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.LoadingModList;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.classloading.api.IHashableTransformer;
import org.embeddedt.modernfix.classloading.hashers.CoreModTransformerHasher;
import org.embeddedt.modernfix.classloading.hashers.MixinTransformerHasher;
import org.embeddedt.modernfix.util.FileUtil;
import org.objectweb.asm.Type;
import org.spongepowered.asm.launch.MixinLaunchPluginLegacy;
import javax.lang.model.SourceVersion;
public class ModernFixCachingClassTransformer extends ClassTransformer {
public static final Logger LOGGER = LogManager.getLogger("ModernFixCachingTransformer");
public static File CLASS_CACHE_FOLDER = null;
private final LaunchPluginHandler pluginHandler;
private final Map<String, ILaunchPluginService> plugins;
private final TransformStore transformStore;
private final TransformerAuditTrail auditTrail;
private final TransformingClassLoader transformingClassLoader;
private final HashMap<String, List<ITransformer<?>>> transformersByClass;
private ConcurrentHashMap<String, Pair<List<byte[]>, byte[]>> transformationCache;
private ForkJoinPool classSaverPool = ForkJoinPool.commonPool();
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
public static ThreadLocal<MessageDigest> systemHasher = ThreadLocal.withInitial(() -> {
try {
return MessageDigest.getInstance("SHA-256");
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
});
public ModernFixCachingClassTransformer(TransformStore transformStore, LaunchPluginHandler pluginHandler, TransformingClassLoader transformingClassLoader, TransformerAuditTrail trail) {
super(transformStore, pluginHandler, transformingClassLoader, trail);
CLASS_CACHE_FOLDER = FileUtil.childFile(FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("classCacheV1").toFile());
this.transformStore = transformStore;
this.pluginHandler = pluginHandler;
this.transformingClassLoader = transformingClassLoader;
this.auditTrail = trail;
/* Build a lookup table of all transformers for a given class */
this.transformersByClass = new HashMap<>();
try {
Field pluginsField = LaunchPluginHandler.class.getDeclaredField("plugins");
pluginsField.setAccessible(true);
this.plugins = (Map<String, ILaunchPluginService>)pluginsField.get(this.pluginHandler);
Field transformersByTypeField = TransformStore.class.getDeclaredField("transformers");
transformersByTypeField.setAccessible(true);
Field transformersMapField = TransformList.class.getDeclaredField("transformers");
transformersMapField.setAccessible(true);
EnumMap<TransformTargetLabel.LabelType, TransformList<?>> transformersByType = (EnumMap<TransformTargetLabel.LabelType, TransformList<?>>)transformersByTypeField.get(this.transformStore);
for(TransformList<?> transformList : transformersByType.values()) {
Map<TransformTargetLabel, List<ITransformer<?>>> transformers = (Map<TransformTargetLabel, List<ITransformer<?>>>)transformersMapField.get(transformList);
for(Map.Entry<TransformTargetLabel, List<ITransformer<?>>> entry : transformers.entrySet()) {
String className = entry.getKey().getClassName().getClassName();
List<ITransformer<?>> transformerList = this.transformersByClass.computeIfAbsent(className, k -> new ArrayList<>());
transformerList.addAll(entry.getValue());
}
}
for(List<ITransformer<?>> transformerList : this.transformersByClass.values()) {
transformerList.sort((t1, t2) -> Comparator.<String>naturalOrder().compare(StringUtils.join(t1.labels(), " "), StringUtils.join(t2.labels(), " ")));
}
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private ArrayList<byte[]> computeHash(String className, byte[] inputClass, String reason) {
final String internalName = className.replace('.', '/');
final Type classDesc = Type.getObjectType(internalName);
ArrayList<ILaunchPluginService> pluginList = new ArrayList<>();
for(ILaunchPluginService plugin : plugins.values()) {
if(!plugin.handlesClass(classDesc, inputClass.length == 0, reason).isEmpty()) {
pluginList.add(plugin);
}
}
final boolean needsTransforming = transformStore.needsTransforming(internalName);
if (!needsTransforming && pluginList.isEmpty()) {
return null;
}
/* Now compute the hash list for the required transformers */
ArrayList<byte[]> hashList = new ArrayList<>();
pluginList.sort((service1, service2) -> Comparator.<String>naturalOrder().compare(service1.name(), service2.name()));
for(ILaunchPluginService service : pluginList) {
byte[] hash = obtainHash(service, className);
if(hash == null) {
return null;
}
hashList.add(hash);
}
if(needsTransforming) {
List<ITransformer<?>> transformers = this.transformersByClass.get(internalName);
if(transformers != null) {
for(ITransformer<?> transformer : transformers) {
byte[] hash = obtainHash(transformer, className);
if(hash == null) {
return null;
}
hashList.add(hash);
}
}
}
/* Hash the class itself last, so that we bail out early if plugins can't hash */
MessageDigest hasher = systemHasher.get();
hasher.reset();
hashList.add(hasher.digest(inputClass));
return hashList;
}
/**
* Check the hashed list of transformers and use a cached version of the class if possible. This code needs
* to be very fast as the entire point is to spend very little time doing transformation work that was done before.
* @param inputClass The bytecode to be transformed
* @param className Name of the class
* @param reason Reason for the class being loaded
* @return The transformed version of the class
*/
@Override
public byte[] transform(byte[] inputClass, String className, String reason) {
/* We only want to cache actual transformations */
if(ITransformerActivity.CLASSLOADING_REASON.equals(reason) || "mixin".equals(reason)) {
final byte[] classToHash = inputClass;
ArrayList<byte[]> hashList = computeHash(className, classToHash, reason);
if(hashList == null)
return super.transform(inputClass, className, reason);
/* Check if the cache contains a transformed class matching these hashes */
/* TODO maybe sanitize the class name? */
File cacheLocation = new File(CLASS_CACHE_FOLDER, className.replace('.', '/') + "." + reason);
boolean hashesMatch = true;
try(ObjectInputStream stream = new ObjectInputStream(new FileInputStream(cacheLocation))) {
ArrayList<byte[]> savedHash = (ArrayList<byte[]>)stream.readObject();
byte[] savedInputClass = (byte[])stream.readObject();
if(hashList != null) {
for(int i = 0; i < savedHash.size(); i++) {
if(!Arrays.equals(savedHash.get(i), hashList.get(i))) {
hashesMatch = false;
break;
}
}
} else
hashesMatch = false;
if(hashesMatch)
inputClass = savedInputClass;
} catch(IOException | ClassNotFoundException | ClassCastException e) {
if(!(e instanceof FileNotFoundException))
e.printStackTrace();
hashesMatch = false;
}
if(!hashesMatch) {
inputClass = super.transform(inputClass, className, reason);
if(hashList != null) {
final byte[] classToSave = inputClass;
final ArrayList<byte[]> hashListToSave = hashList;
classSaverPool.submit(() -> {
cacheLocation.getParentFile().mkdirs();
try(ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(cacheLocation))) {
stream.writeObject(hashListToSave);
stream.writeObject(classToSave);
} catch(IOException e) {
e.printStackTrace();
}
});
}
}
return inputClass;
}
return super.transform(inputClass, className, reason);
}
private final byte[] FORGE_HASH = LoadingModList.get().getModFileById("forge").getMods().get(0).getVersion().toString().getBytes(StandardCharsets.UTF_8);
private byte[] obtainHash(Object o, String className) {
if(o instanceof CoreModBaseTransformer) {
return CoreModTransformerHasher.obtainHash((CoreModBaseTransformer<?>)o);
} else if(o instanceof MixinLaunchPluginLegacy) {
return MixinTransformerHasher.obtainHash((MixinLaunchPluginLegacy)o, className);
} else if(o instanceof IHashableTransformer) {
return ((IHashableTransformer)o).getHashForClass(className);
} else if(o.getClass().getName().startsWith("net.minecraftforge.")) {
return FORGE_HASH;
} else {
LOGGER.warn("No hash implementation found for: " + o.getClass().getName());
return null;
}
}
}

View File

@ -0,0 +1,21 @@
package org.embeddedt.modernfix;
import com.google.common.cache.CacheLoader;
import org.apache.commons.lang3.tuple.Pair;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileWalker extends CacheLoader<Pair<Path, Integer>, List<Path>> {
public static final FileWalker INSTANCE = new FileWalker();
@Override
public List<Path> load(Pair<Path, Integer> key) throws Exception {
try(Stream<Path> stream = Files.walk(key.getLeft(), key.getRight())) {
return stream.collect(Collectors.toList());
}
}
}

View File

@ -1,24 +1,29 @@
package org.embeddedt.modernfix;
import net.minecraft.SharedConstants;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.ExtensionPoint;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.network.FMLNetworkConstants;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.modernfix.command.ModernFixCommands;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.embeddedt.modernfix.resources.ReloadExecutor;
import org.embeddedt.modernfix.util.ClassInfoManager;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import org.embeddedt.modernfix.structure.AsyncLocator;
import org.embeddedt.modernfix.util.KubeUtil;
import java.lang.management.ManagementFactory;
import java.util.concurrent.ExecutorService;
// The value here should match an entry in the META-INF/mods.toml file
@Mod(ModernFix.MODID)
public class ModernFix {
// Directly reference a log4j logger.
@ -26,72 +31,28 @@ public class ModernFix {
public static final String MODID = "modernfix";
public static String NAME = "ModernFix";
public static ModernFix INSTANCE;
// Used to skip computing the blockstate caches twice
public static boolean runningFirstInjection = false;
private static ExecutorService resourceReloadService = null;
static {
if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.dedicated_reload_executor.ReloadExecutor")) {
resourceReloadService = ReloadExecutor.createCustomResourceReloadExecutor();
} else {
resourceReloadService = Util.backgroundExecutor();
}
}
public static ExecutorService resourceReloadExecutor() {
return resourceReloadService;
}
public static void runAuditIfRequested() {
boolean auditAndExit = Boolean.getBoolean("modernfix.auditAndExit");
if (auditAndExit || Boolean.getBoolean("modernfix.auditMixinsAtStart")) {
MixinEnvironment.getCurrentEnvironment().audit();
if (auditAndExit) {
// Prevents Crash Assistant from treating mixin audit as a crash
Minecraft.getInstance().stop();
System.exit(0);
}
}
}
public ModernFix() {
INSTANCE = this;
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.snapshot_easter_egg.NameChange") && !SharedConstants.getCurrentVersion().isStable())
NAME = "PreemptiveFix";
ModernFixPlatformHooks.INSTANCE.onServerCommandRegister(ModernFixCommands::register);
// Register ourselves for server and other game events we are interested in
MinecraftForge.EVENT_BUS.register(this);
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> MinecraftForge.EVENT_BUS.register(new ModernFixClient()));
ModLoadingContext.get().registerExtensionPoint(ExtensionPoint.DISPLAYTEST, () -> Pair.of(() -> FMLNetworkConstants.IGNORESERVERONLY, (a, b) -> true));
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModernFixConfig.COMMON_CONFIG);
if(ModList.get().isLoaded("kubejs"))
MinecraftForge.EVENT_BUS.register(KubeUtil.class);
}
public void onServerStarted() {
if(ModernFixPlatformHooks.INSTANCE.isDedicatedServer()) {
@SubscribeEvent
public void onServerStarted(FMLServerStartedEvent event) {
if(FMLLoader.getDist() == Dist.DEDICATED_SERVER) {
float gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.measure_time.ServerLoad"))
ModernFix.LOGGER.warn("Dedicated server took " + gameStartTime + " seconds to load");
ModernFixPlatformHooks.INSTANCE.onLaunchComplete();
}
ClassInfoManager.clear();
}
@SuppressWarnings("ConstantValue")
public void onServerDead(MinecraftServer server) {
/* Clear as much data from the integrated server as possible, in case a mod holds on to it */
try {
for(ServerLevel level : server.getAllLevels()) {
ChunkMap chunkMap = level.getChunkSource().chunkMap;
// Null check for mods that replace chunk system
if(chunkMap.updatingChunkMap != null)
chunkMap.updatingChunkMap.clear();
if(chunkMap.visibleChunkMap != null)
chunkMap.visibleChunkMap.clear();
if(chunkMap.pendingUnloads != null)
chunkMap.pendingUnloads.clear();
}
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Couldn't clear chunk data", e);
ModernFix.LOGGER.warn("Dedicated server took " + gameStartTime + " seconds to load");
}
}
}

View File

@ -1,108 +1,45 @@
package org.embeddedt.modernfix;
import net.minecraft.client.Minecraft;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.MemoryReserve;
import org.embeddedt.modernfix.api.constants.IntegrationConstants;
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.embeddedt.modernfix.searchtree.JEIBackedSearchTree;
import org.embeddedt.modernfix.searchtree.SearchTreeProviderRegistry;
import org.embeddedt.modernfix.spark.SparkLaunchProfiler;
import org.embeddedt.modernfix.util.ClassInfoManager;
import org.embeddedt.modernfix.world.IntegratedWatchdog;
import net.minecraft.client.gui.screens.ConnectScreen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ModernFixClient {
public static ModernFixClient INSTANCE;
public static long worldLoadStartTime = -1;
public static long worldLoadStartTime;
private static int numRenderTicks;
public static float gameStartTimeSeconds = -1;
public static boolean recipesUpdated, tagsUpdated = false;
public String brandingString = null;
/**
* The list of loaded client integrations.
*/
public static List<ModernFixClientIntegration> CLIENT_INTEGRATIONS = new CopyOnWriteArrayList<>();
public ModernFixClient() {
INSTANCE = this;
// clear reserve as it's not needed
MemoryReserve.release();
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.branding.F3Screen")) {
brandingString = ModernFix.NAME + " " + ModernFixPlatformHooks.INSTANCE.getVersionString();
}
SearchTreeProviderRegistry.register(JEIBackedSearchTree.PROVIDER);
for(String className : ModernFixPlatformHooks.INSTANCE.getCustomModOptions().get(IntegrationConstants.CLIENT_INTEGRATION_CLASS)) {
try {
CLIENT_INTEGRATIONS.add((ModernFixClientIntegration)Class.forName(className).getDeclaredConstructor().newInstance());
} catch(ReflectiveOperationException | ClassCastException e) {
ModernFix.LOGGER.error("Could not instantiate integration {}", className, e);
}
}
if(ModernFixMixinPlugin.instance.isOptionEnabled("perf.dynamic_resources.FireIntegrationHook")) {
for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) {
integration.onDynamicResourcesStatusChange(true);
}
}
}
public void resetWorldLoadStateMachine() {
numRenderTicks = 0;
worldLoadStartTime = -1;
recipesUpdated = false;
tagsUpdated = false;
}
public void onGameLaunchFinish() {
if(gameStartTimeSeconds >= 0)
return;
gameStartTimeSeconds = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.measure_time.GameLoad"))
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onMultiplayerConnect(GuiOpenEvent event) {
if(event.getGui() instanceof ConnectScreen && !event.isCanceled()) {
worldLoadStartTime = System.nanoTime();
} else if (event.getGui() instanceof TitleScreen && gameStartTimeSeconds < 0) {
gameStartTimeSeconds = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
ModernFix.LOGGER.warn("Game took " + gameStartTimeSeconds + " seconds to start");
ModernFixPlatformHooks.INSTANCE.onLaunchComplete();
ClassInfoManager.clear();
}
}
public void onRecipesUpdated() {
recipesUpdated = true;
}
public void onTagsUpdated() {
tagsUpdated = true;
}
public void onRenderTickEnd() {
if(recipesUpdated
&& tagsUpdated
&& worldLoadStartTime != -1
&& Minecraft.getInstance().player != null
&& numRenderTicks++ >= 10) {
@SubscribeEvent
public void onRenderTickEnd(TickEvent.RenderTickEvent event) {
if(event.phase == TickEvent.Phase.END && worldLoadStartTime != -1 && Minecraft.getInstance().player != null && numRenderTicks++ >= 10) {
float timeSpentLoading = ((float)(System.nanoTime() - worldLoadStartTime) / 1000000000f);
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.measure_time.WorldLoad")) {
ModernFix.LOGGER.warn("Time from main menu to in-game was " + timeSpentLoading + " seconds");
ModernFix.LOGGER.warn("Total time to load game and open world was " + (timeSpentLoading + gameStartTimeSeconds) + " seconds");
}
if (ModernFixPlatformHooks.INSTANCE.modPresent("spark") && ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_world_join.WorldJoin")) {
SparkLaunchProfiler.stop("world_join");
}
ModernFix.LOGGER.warn("Time from main menu to in-game was " + timeSpentLoading + " seconds");
ModernFix.LOGGER.warn("Total time to load game and open world was " + (timeSpentLoading + gameStartTimeSeconds) + " seconds");
resetWorldLoadStateMachine();
}
}
public void onServerStarted(MinecraftServer server) {
if(!ModernFixMixinPlugin.instance.isOptionEnabled("feature.integrated_server_watchdog.IntegratedWatchdog"))
return;
IntegratedWatchdog watchdog = new IntegratedWatchdog(server);
watchdog.start();
}
}

View File

@ -0,0 +1,59 @@
package org.embeddedt.modernfix.agent;
import com.google.common.collect.ImmutableMap;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.function.Function;
public class Agent {
public static void agentmain(String args, Instrumentation instrumentation) {
instrumentation.addTransformer(new EarlyTransformer());
}
private static class EarlyTransformer implements ClassFileTransformer {
private static final ImmutableMap<String, Function<ClassNode, ClassNode>> TRANSFORMERS = ImmutableMap.<String, Function<ClassNode, ClassNode>>builder()
.put("net/minecraftforge/fml/loading/moddiscovery/Scanner", EarlyTransformer::transformScanner)
.build();
private static ClassNode transformScanner(ClassNode input) {
for(MethodNode method : input.methods) {
if(method.name.equals("fileVisitor")) {
for(int i = 0; i < method.instructions.size(); i++) {
AbstractInsnNode ainsn = method.instructions.get(i);
if(ainsn.getOpcode() == Opcodes.INVOKEVIRTUAL) {
MethodInsnNode minsn = (MethodInsnNode)ainsn;
if(minsn.name.equals("accept") && minsn.owner.equals("org/objectweb/asm/ClassReader")) {
method.instructions.set(minsn.getPrevious(), new LdcInsnNode(ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES));
return input;
}
}
}
}
}
return input;
}
@Override
public byte[] transform(ClassLoader classLoader, String s, Class<?> aClass, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
Function<ClassNode, ClassNode> func = TRANSFORMERS.get(s);
if(func != null) {
ClassReader reader = new ClassReader(bytes);
ClassNode node = new ClassNode(Opcodes.ASM9);
reader.accept(node, 0);
node = func.apply(node);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
node.accept(writer);
return writer.toByteArray();
} else
return bytes;
}
}
}

View File

@ -1,8 +0,0 @@
package org.embeddedt.modernfix.api.constants;
public class IntegrationConstants {
public static final String INTEGRATIONS_KEY = "modernfix:integration";
public static final String CLIENT_INTEGRATION_CLASS = "client_entrypoint";
public static final String INTEGRATION_CLASS = "entrypoint";
}

View File

@ -1,77 +0,0 @@
package org.embeddedt.modernfix.api.entrypoint;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.resources.ResourceLocation;
import java.util.function.Function;
/**
* Implement this interface in a mod class and add it to "modernfix:integration_v1" in your mod metadata file
* to integrate with ModernFix's features.
*/
public interface ModernFixClientIntegration {
/**
* Called when the dynamic resources status has changed during a model reload so mods know whether to run their
* normal codepath or the dynamic version.
*
* @param enabled whether dynamic resources is enabled
*/
default void onDynamicResourcesStatusChange(boolean enabled) {
}
/**
* Called to allow mods to observe the loading of an unbaked model and either make changes to it or wrap it with their
* own instance.
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
* @param originalModel the original model
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
* with dynamic resources on
* @return the model which should actually be loaded for this resource location
*/
default UnbakedModel onUnbakedModelLoad(ResourceLocation location, UnbakedModel originalModel, ModelBakery bakery) {
return originalModel;
}
/**
* Called to allow mods to observe the use of an unbaked model at bake time and either make changes to it or wrap it with their
* own instance.
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
* @param originalModel the original model
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
* with dynamic resources on
* @return the model which should actually be loaded for this resource location
*/
default UnbakedModel onUnbakedModelPreBake(ResourceLocation location, UnbakedModel originalModel, ModelBakery bakery) {
return originalModel;
}
/**
* Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their
* own instance.
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
* @param originalModel the original model
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
* with dynamic resources on
* @return the model which should actually be loaded for this resource location
*/
@Deprecated
default BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery) {
return originalModel;
}
/**
* Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their
* own instance.
* @param location the ResourceLocation of the model (this may be a ModelResourceLocation)
* @param originalModel the original model
* @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect
* with dynamic resources on
* @param textureGetter function to retrieve textures for this model
* @return the model which should actually be loaded for this resource location
*/
default BakedModel onBakedModelLoad(ResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, Function<Material, TextureAtlasSprite> textureGetter) {
return onBakedModelLoad(location, baseModel, originalModel, state, bakery);
}
}

View File

@ -1,87 +0,0 @@
package org.embeddedt.modernfix.api.helpers;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.dynamicresources.ModelBakeryHelpers;
import org.embeddedt.modernfix.util.DynamicMap;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
@SuppressWarnings("unused")
public final class ModelHelpers {
/**
* Allows converting a ModelResourceLocation back into the corresponding BlockState(s). Try to avoid calling this
* multiple times if possible.
* @param location the location of the model
* @return a list of all blockstates related to the model
*/
public static ImmutableList<BlockState> getBlockStateForLocation(ModelResourceLocation location) {
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(new ResourceLocation(location.getNamespace(), location.getPath()));
if(blockOpt.isPresent())
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), location);
else
return ImmutableList.of();
}
/**
* Allows converting a ModelResourceLocation back into the corresponding BlockState(s). Faster version of its
* companion function if and only if you know the corresponding Block already for some reason.
* @param definition the state definition for the Block
* @param location the location of the model
* @return a list of all blockstates related to the model
*/
public static ImmutableList<BlockState> getBlockStateForLocation(StateDefinition<Block, BlockState> definition, ModelResourceLocation location) {
return ModelBakeryHelpers.getBlockStatesForMRL(definition, location);
}
/**
* Compatibility helper for mods to use to get a map-like view of the model bakery.
* @param modelGetter the model getter function supplied by the integration class
* @return a fake map of the top-level models
*/
public static Map<ResourceLocation, BakedModel> createFakeTopLevelMap(BiFunction<ResourceLocation, ModelState, BakedModel> modelGetter) {
return new DynamicMap<>(location -> modelGetter.apply(location, BlockModelRotation.X0_Y0));
}
/**
* Provides a ModelBaker for mods to use.
* @param bakery the ModelBakery supplied to your integration
* @return an appropriate ModelBaker
*/
public static ModelBaker adaptBakery(ModelBakery bakery) {
return new ModelBaker() {
@Override
public UnbakedModel getModel(ResourceLocation resourceLocation) {
return bakery.getModel(resourceLocation);
}
@Nullable
@Override
public BakedModel bake(ResourceLocation resourceLocation, ModelState modelState) {
return ((IExtendedModelBakery)bakery).bakeDefault(resourceLocation, modelState);
}
@Override
public @Nullable BakedModel bake(ResourceLocation location, ModelState state, Function<Material, TextureAtlasSprite> sprites) {
throw new UnsupportedOperationException();
}
@Override
public Function<Material, TextureAtlasSprite> getModelTextureGetter() {
return Material::sprite;
}
};
}
}

View File

@ -1,185 +0,0 @@
package org.embeddedt.modernfix.benchmark;
import com.google.common.util.concurrent.MoreExecutors;
import com.mojang.datafixers.util.Either;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.*;
import net.minecraft.util.Unit;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.chunk.*;
import net.minecraft.world.level.chunk.storage.ChunkSerializer;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.embeddedt.modernfix.ModernFix;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
public class WorldgenBenchmark {
private static final TicketType<Unit> BENCHMARK_TICKET =
TicketType.create("modernfix_benchmark", (a, b) -> 0);
private static final List<ChunkStatus> ALL_STATUSES = ChunkStatus.getStatusList().stream()
.filter(s -> s.getIndex() > ChunkStatus.EMPTY.getIndex()
&& s.getIndex() < ChunkStatus.INITIALIZE_LIGHT.getIndex())
.toList();
private static final int REQUIRED_LOAD_RADIUS = ALL_STATUSES.stream().mapToInt(ChunkStatus::getRange).max().orElse(0);
public static String run(ServerLevel level, ChunkPos center, int testRadius, int iterations, ChunkStatus startStatus, ChunkStatus stopStatus) {
int startIndex = ALL_STATUSES.indexOf(startStatus);
if (startIndex < 0) {
throw new IllegalArgumentException("Invalid start status: " + startStatus);
}
int stopIndex = ALL_STATUSES.indexOf(stopStatus);
if (stopIndex < 0) {
throw new IllegalArgumentException("Invalid stop status:" + stopStatus);
}
List<ChunkStatus> setupStatuses = ALL_STATUSES.subList(0, startIndex);
List<ChunkStatus> timedStatuses = ALL_STATUSES.subList(startIndex, stopIndex + 1);
Context ctx = new Context(level, center, testRadius);
long[] timings = new long[timedStatuses.size()];
int testDiameter = 2 * testRadius + 1;
int numPositions = testDiameter * testDiameter;
ChunkPos[] testPositions = new ChunkPos[numPositions];
CompoundTag[] snapshots = new CompoundTag[numPositions];
ChunkAccess[][] neighborArrays = new ChunkAccess[numPositions][];
int idx = 0;
for (int tz = -testRadius; tz <= testRadius; tz++) {
for (int tx = -testRadius; tx <= testRadius; tx++) {
ChunkPos testPos = new ChunkPos(center.x + tx, center.z + tz);
testPositions[idx] = testPos;
neighborArrays[idx] = ctx.buildNeighborArray(testPos);
ProtoChunk setupProto = ctx.newProtoChunk(testPos);
neighborArrays[idx][ctx.centerIndex] = setupProto;
for (ChunkStatus status : setupStatuses) {
status.generate(ctx.executor, level, ctx.generator, ctx.templates,
ctx.lightEngine, ctx.noopPromotion, Arrays.asList(neighborArrays[idx])).join();
}
snapshots[idx] = ChunkSerializer.write(level, setupProto);
idx++;
ModernFix.LOGGER.info("worldgen benchmark setup progress: {}/{}", idx, numPositions);
}
}
ModernFix.LOGGER.info("worldgen benchmark setup complete");
for (int iter = 0; iter < iterations; iter++) {
ModernFix.LOGGER.info("worldgen benchmark iteration: {}/{}", iter + 1, iterations);
for (int p = 0; p < numPositions; p++) {
ProtoChunk restored = ChunkSerializer.read(
level, ctx.poiManager, testPositions[p], snapshots[p]);
neighborArrays[p][ctx.centerIndex] = restored;
List<ChunkAccess> neighborList = Arrays.asList(neighborArrays[p]);
for (int s = 0; s < timedStatuses.size(); s++) {
long t0 = System.nanoTime();
timedStatuses.get(s).generate(ctx.executor, level, ctx.generator,
ctx.templates, ctx.lightEngine, ctx.noopPromotion, neighborList).join();
timings[s] += System.nanoTime() - t0;
}
}
}
ModernFix.LOGGER.info("worldgen benchmark done");
ctx.cleanup();
return formatTimings(timedStatuses, timings, testRadius, iterations);
}
private static String formatTimings(List<ChunkStatus> statuses, long[] timings, int testRadius, int iterations) {
int totalChunks = (2 * testRadius + 1) * (2 * testRadius + 1) * iterations;
StringBuilder sb = new StringBuilder();
long total = 0;
for (int i = 0; i < timings.length; i++) {
total += timings[i];
String name = BuiltInRegistries.CHUNK_STATUS.getKey(statuses.get(i)).getPath();
sb.append(String.format(" %-22s %8.1f ms (%6.2f ms/chunk)\n",
name, timings[i] / 1e6, timings[i] / 1e6 / totalChunks));
}
sb.append(String.format(" %-22s %8.1f ms (%6.2f ms/chunk)\n",
"TOTAL", total / 1e6, total / 1e6 / totalChunks));
return sb.toString();
}
private static class Context {
final ServerLevel level;
final ServerChunkCache chunkSource;
final ChunkPos center;
final int loadRadius;
final int loadDiameter;
final ChunkAccess[] realChunks;
final int neighborDiameter;
final int centerIndex;
final Executor executor;
final ChunkGenerator generator;
final ThreadedLevelLightEngine lightEngine;
final StructureTemplateManager templates;
final PoiManager poiManager;
final Function<ChunkAccess, CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> noopPromotion;
private final net.minecraft.core.Registry<Biome> biomeRegistry;
Context(ServerLevel level, ChunkPos center, int testRadius) {
this.level = level;
this.chunkSource = level.getChunkSource();
this.center = center;
this.loadRadius = testRadius + REQUIRED_LOAD_RADIUS;
this.loadDiameter = 2 * loadRadius + 1;
this.neighborDiameter = 2 * REQUIRED_LOAD_RADIUS + 1;
this.centerIndex = neighborDiameter * neighborDiameter / 2;
this.executor = MoreExecutors.directExecutor();
this.generator = chunkSource.getGenerator();
this.lightEngine = chunkSource.getLightEngine();
this.templates = level.getStructureManager();
this.poiManager = chunkSource.getPoiManager();
this.noopPromotion = chunk -> CompletableFuture.completedFuture(Either.left(chunk));
this.biomeRegistry = level.registryAccess().registryOrThrow(Registries.BIOME);
chunkSource.addRegionTicket(BENCHMARK_TICKET, center, loadRadius, Unit.INSTANCE);
realChunks = new ChunkAccess[loadDiameter * loadDiameter];
for (int dz = -loadRadius; dz <= loadRadius; dz++) {
for (int dx = -loadRadius; dx <= loadRadius; dx++) {
LevelChunk real = level.getChunk(center.x + dx, center.z + dz);
realChunks[(dz + loadRadius) * loadDiameter + (dx + loadRadius)] =
new ImposterProtoChunk(real, false);
}
}
}
ProtoChunk newProtoChunk(ChunkPos pos) {
return new ProtoChunk(pos, UpgradeData.EMPTY, level, biomeRegistry, null);
}
ChunkAccess[] buildNeighborArray(ChunkPos testPos) {
int count = neighborDiameter * neighborDiameter;
ChunkAccess[] array = new ChunkAccess[count];
int baseX = (testPos.x - REQUIRED_LOAD_RADIUS) - (center.x - loadRadius);
int baseZ = (testPos.z - REQUIRED_LOAD_RADIUS) - (center.z - loadRadius);
for (int dz = 0; dz < neighborDiameter; dz++) {
System.arraycopy(realChunks, (baseZ + dz) * loadDiameter + baseX,
array, dz * neighborDiameter, neighborDiameter);
}
return array;
}
void cleanup() {
chunkSource.removeRegionTicket(BENCHMARK_TICKET, center, loadRadius, Unit.INSTANCE);
}
}
}

View File

@ -1,16 +1,95 @@
package org.embeddedt.modernfix.blockstate;
import com.google.common.base.Stopwatch;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import org.embeddedt.modernfix.duck.IBlockState;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.FMLLoader;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.core.config.ModernFixConfig;
import org.embeddedt.modernfix.util.BakeReason;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class BlockStateCacheHandler {
public static void invalidateCache() {
synchronized (BlockBehaviour.BlockStateBase.class) {
for (BlockState blockState : Block.BLOCK_STATE_REGISTRY) {
((IBlockState)blockState).clearCache();
private static RebuildThread currentRebuildThread = null;
private static boolean needToBake() {
BakeReason reason = BakeReason.getCurrentBakeReason();
return !(reason == BakeReason.FREEZE /* startup */
|| reason == BakeReason.REVERT /* crash, in which case cache likely doesn't matter, or exiting world */
|| reason == BakeReason.REMOTE_SNAPSHOT_INJECT /* will be handled when tags are reloaded */
|| (reason == BakeReason.LOCAL_SNAPSHOT_INJECT && FMLLoader.getDist() == Dist.CLIENT /* will be handled when tags are reloaded */));
}
public static void rebuildParallel(boolean force) {
if(currentRebuildThread != null) {
if(currentRebuildThread.isAlive())
ModernFix.LOGGER.warn("Interrupting previous blockstate cache rebuild");
currentRebuildThread.stopRebuild();
try {
currentRebuildThread.join(10000);
if(currentRebuildThread.isAlive())
throw new IllegalStateException("Blockstate cache rebuild thread has hung");
} catch(InterruptedException e) {
throw new RuntimeException("Don't interrupt Minecraft threads", e);
}
ModernFix.LOGGER.debug("Rebuild thread exited");
currentRebuildThread = null;
}
if(force || needToBake()) {
ArrayList<BlockState> stateList = new ArrayList<>(Block.BLOCK_STATE_REGISTRY.size());
for (BlockState blockState : Block.BLOCK_STATE_REGISTRY) {
stateList.add(blockState);
}
currentRebuildThread = new RebuildThread(stateList);
if(ModernFixConfig.REBUILD_BLOCKSTATES_ASYNC.get())
currentRebuildThread.start();
else {
currentRebuildThread.run();
currentRebuildThread = null;
}
} else {
ModernFix.LOGGER.warn("Deferred blockstate cache rebuild");
}
}
private static class RebuildThread extends Thread {
private boolean stopRebuild = false;
private final List<BlockState> blockStateList;
public RebuildThread(List<BlockState> statesToInit) {
this.setName("ModernFix blockstate cache rebuild thread");
this.setPriority(Thread.MIN_PRIORITY + 1);
this.blockStateList = statesToInit;
}
public void stopRebuild() {
this.stopRebuild = true;
}
private void rebuildCache() {
Iterator<BlockState> stateIterator = blockStateList.iterator();
while(!stopRebuild && stateIterator.hasNext()) {
BlockState state = stateIterator.next();
try {
state.initCache();
} catch(Exception e) {
ModernFix.LOGGER.warn("Exception encountered while initializing cache", e);
}
}
}
@Override
public void run() {
Stopwatch realtimeStopwatch = Stopwatch.createStarted();
rebuildCache();
realtimeStopwatch.stop();
if(!stopRebuild)
ModernFix.LOGGER.info("Blockstate cache rebuilt in " + realtimeStopwatch.elapsed(TimeUnit.MILLISECONDS)/1000f + " seconds");
}
}
}

View File

@ -1,162 +0,0 @@
package org.embeddedt.modernfix.blockstate;
import com.google.common.collect.Iterators;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.world.level.block.state.properties.Property;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Fake "map" implementation used to hold the states.
*
* Intentionally throws on methods that would be inefficient so that we know
* if an incompatible mod is present.
*/
public class FakeStateMap<S> implements Map<Map<Property<?>, Comparable<?>>, S> {
private final Map<Property<?>, Comparable<?>>[] keys;
private Map<Map<Property<?>, Comparable<?>>, S> fastLookup;
private final Object[] values;
private int usedSlots;
public FakeStateMap(int numStates) {
this.keys = new Map[numStates];
this.values = new Object[numStates];
this.usedSlots = 0;
}
@Override
public int size() {
return usedSlots;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public boolean containsKey(Object o) {
return getFastLookup().containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return getFastLookup().containsValue(o);
}
@SuppressWarnings("unchecked")
private Map<Map<Property<?>, Comparable<?>>, S> getFastLookup() {
if(fastLookup == null) {
var map = new Object2ObjectOpenHashMap<Map<Property<?>, Comparable<?>>, S>(usedSlots);
Map<Property<?>, Comparable<?>>[] keys = this.keys;
Object[] values = this.values;
for(int i = 0; i < usedSlots; i++) {
map.put(keys[i], (S)values[i]);
}
fastLookup = map;
}
return fastLookup;
}
@Override
public S get(Object o) {
return getFastLookup().get(o);
}
@Nullable
@Override
public S put(Map<Property<?>, Comparable<?>> propertyComparableMap, S s) {
if(fastLookup != null) {
throw new IllegalStateException("Cannot populate map after fast lookup is built");
}
keys[usedSlots] = propertyComparableMap;
values[usedSlots] = s;
usedSlots++;
return null;
}
@Override
public S remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(@NotNull Map<? extends Map<Property<?>, Comparable<?>>, ? extends S> map) {
for(Entry<? extends Map<Property<?>, Comparable<?>>, ? extends S> entry : map.entrySet()) {
this.put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear() {
for(int i = 0; i < usedSlots; i++) {
this.keys[i] = null;
this.values[i] = null;
}
this.usedSlots = 0;
}
private <T> List<T> asList(T... array) {
var list = Arrays.asList(array);
if(usedSlots < array.length) {
list = list.subList(0, usedSlots);
}
return list;
}
@NotNull
@Override
public Set<Map<Property<?>, Comparable<?>>> keySet() {
return new AbstractSet<>() {
@Override
public Iterator<Map<Property<?>, Comparable<?>>> iterator() {
return keys.length == usedSlots ? Iterators.forArray(keys) : asList(keys).iterator();
}
@Override
public int size() {
return usedSlots;
}
};
}
@NotNull
@Override
public Collection<S> values() {
return (Collection<S>)asList(values);
}
@NotNull
@Override
public Set<Entry<Map<Property<?>, Comparable<?>>, S>> entrySet() {
return new AbstractSet<>() {
@Override
public int size() {
return usedSlots;
}
@NotNull
@Override
public Iterator<Entry<Map<Property<?>, Comparable<?>>, S>> iterator() {
return new Iterator<>() {
int currentIdx = 0;
@Override
public boolean hasNext() {
return currentIdx < usedSlots;
}
@Override
public Entry<Map<Property<?>, Comparable<?>>, S> next() {
if (currentIdx >= usedSlots)
throw new IndexOutOfBoundsException();
Entry<Map<Property<?>, Comparable<?>>, S> entry = new AbstractMap.SimpleImmutableEntry<>(keys[currentIdx], (S) values[currentIdx]);
currentIdx++;
return entry;
}
};
}
};
}
}

View File

@ -1,7 +0,0 @@
package org.embeddedt.modernfix.chunk;
import net.minecraft.world.level.chunk.Palette;
public interface ExtendedPalettedContainer<T> {
Palette<T> mfix$getPalette();
}

View File

@ -0,0 +1,114 @@
package org.embeddedt.modernfix.classloading;
import com.google.common.collect.Lists;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.moddiscovery.*;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Stream;
public class ModernFixResourceFinder {
private static HashMap<String, List<URL>> urlsForClass = null;
private static final Class<? extends IModLocator> MINECRAFT_LOCATOR;
private static Field explodedDirModsField = null;
private static final Logger LOGGER = LogManager.getLogger("ModernFixResourceFinder");
static {
try {
MINECRAFT_LOCATOR = (Class<? extends IModLocator>)Class.forName("net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer$MinecraftLocator");
} catch(ClassNotFoundException e) {
/* that shouldn't happen */
throw new RuntimeException(e);
}
}
public static synchronized void init() throws ReflectiveOperationException {
urlsForClass = new HashMap<>();
LOGGER.info("Start building list of class locations...");
for(ModFileInfo fileInfo : LoadingModList.get().getModFiles()) {
ModFile file = fileInfo.getFile();
IModLocator locator = file.getLocator();
Iterable<Path> rootPath = getRootPathForLocator(locator, file);
for(Path root : rootPath) {
try(Stream<Path> stream = Files.walk(root)) {
stream
.map(root::relativize)
.forEach(path -> {
String strPath = path.toString();
URL url = (URL)LamdbaExceptionUtils.uncheck(() -> {
return new URL("modjar://" + fileInfo.getMods().get(0).getModId() + "/" + strPath);
});
List<URL> urlList = urlsForClass.get(strPath);
if(urlList != null) {
if(urlList.size() > 1)
urlList.add(url);
else {
/* Convert singleton to real list */
ArrayList<URL> newList = new ArrayList<>(urlList);
newList.add(url);
urlsForClass.put(strPath, newList);
}
} else {
/* Use a singleton list initially to keep memory usage down */
urlsForClass.put(strPath, Collections.singletonList(url));
}
});
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
for(List<URL> list : urlsForClass.values()) {
if(list instanceof ArrayList)
((ArrayList<URL>)list).trimToSize();
}
LOGGER.info("Finish building");
}
private static Iterable<Path> getRootPathForLocator(IModLocator locator, ModFile file) throws ReflectiveOperationException {
if(locator instanceof AbstractJarFileLocator) {
FileSystem modFs = locator.findPath(file, ".").getFileSystem();
return modFs.getRootDirectories();
} else if (locator instanceof ExplodedDirectoryLocator) {
if(explodedDirModsField == null) {
explodedDirModsField = ExplodedDirectoryLocator.class.getDeclaredField("mods");
explodedDirModsField.setAccessible(true);
}
Map<IModFile, Pair<Path, List<Path>>> mods = (Map<IModFile, Pair<Path, List<Path>>>)explodedDirModsField.get(locator);
return mods.get(file).getRight();
} else if(MINECRAFT_LOCATOR.isAssignableFrom(locator.getClass())) {
Path mcJar = FMLLoader.getMCPaths()[0];
if(Files.isDirectory(mcJar)) {
return mcJar;
} else {
return locator.findPath(file, ".").getFileSystem().getRootDirectories();
}
} else
throw new UnsupportedOperationException("Unknown ModLocator type: " + locator.getClass().getName());
}
private static final Pattern SLASH_REPLACER = Pattern.compile("/+");
public static Enumeration<URL> findAllURLsForResource(String input) {
input = SLASH_REPLACER.matcher(input).replaceAll("/");
List<URL> urlList = urlsForClass.get(input);
if(urlList != null)
return Collections.enumeration(urlList);
else {
return Collections.emptyEnumeration();
}
}
}

View File

@ -0,0 +1,11 @@
package org.embeddedt.modernfix.classloading.api;
public interface IHashableTransformer {
/**
* Called on an ILaunchPluginService or ITransformer to obtain a unique hash of the transformations that will be applied.
* Used to invalidate the transformation cache when needed.
* @param className Name of class being transformed
* @return A unique hash of the transformations that will be applied
*/
byte[] getHashForClass(String className);
}

View File

@ -0,0 +1,49 @@
package org.embeddedt.modernfix.classloading.hashers;
import cpw.mods.modlauncher.ModernFixCachingClassTransformer;
import net.minecraftforge.coremod.CoreMod;
import net.minecraftforge.coremod.transformer.CoreModBaseTransformer;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.ConcurrentHashMap;
public class CoreModTransformerHasher {
private static final ConcurrentHashMap<CoreMod, byte[]> hashForCoremod;
private static Field coremodField;
static {
hashForCoremod = new ConcurrentHashMap<>();
try {
coremodField = CoreModBaseTransformer.class.getDeclaredField("coreMod");
coremodField.setAccessible(true);
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private static byte[] hashCoreMod(CoreMod coreMod) {
byte[] coreModContents;
try {
coreModContents = Files.readAllBytes(coreMod.getPath());
} catch(IOException e) {
throw new RuntimeException(e);
}
MessageDigest hasher = ModernFixCachingClassTransformer.systemHasher.get();
hasher.reset();
return hasher.digest(coreModContents);
}
public static byte[] obtainHash(CoreModBaseTransformer<?> transformer) {
CoreMod coremod;
try {
coremod = (CoreMod)coremodField.get(transformer);
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
return hashForCoremod.computeIfAbsent(coremod, CoreModTransformerHasher::hashCoreMod);
}
}

View File

@ -0,0 +1,154 @@
package org.embeddedt.modernfix.classloading.hashers;
import com.google.common.io.Resources;
import cpw.mods.modlauncher.ModernFixCachingClassTransformer;
import org.spongepowered.asm.launch.IClassProcessor;
import org.spongepowered.asm.launch.MixinLaunchPluginLegacy;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import org.spongepowered.asm.mixin.injection.invoke.arg.ArgsClassGenerator;
import org.spongepowered.asm.mixin.transformer.ext.Extensions;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Stream;
public class MixinTransformerHasher {
private static HashMap<String, byte[]> hashesByClass = null;
private final static MessageDigest hasher;
private static Field processorsListField, transformerField, processorField, environmentField;
private static boolean fixedArgsClassCount = false;
private static final byte[] NO_MIXINS = new byte[] {(byte)0xde, (byte)0xad, (byte)0xbe, (byte)0xef};
static {
try {
hasher = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static byte[] obtainHash(MixinLaunchPluginLegacy plugin, String className) {
/* FIXME runs too early right now, and therefore doesn't pick up the list of mixins correctly */
synchronized (MixinTransformerHasher.class) {
if(hashesByClass == null) {
try {
if(processorsListField == null) {
processorsListField = MixinLaunchPluginLegacy.class.getDeclaredField("processors");
processorsListField.setAccessible(true);
}
List<IClassProcessor> processors = (List<IClassProcessor>) processorsListField.get(plugin);
Object transformHandler = null;
for(IClassProcessor processor : processors) {
if(processor.getClass().getName().equals("org.spongepowered.asm.service.modlauncher.MixinTransformationHandler")) {
transformHandler = processor;
break;
}
}
if(transformHandler == null)
throw new IllegalStateException("Mixin transform handler not found");
if(transformerField == null) {
transformerField = transformHandler.getClass().getDeclaredField("transformer");
transformerField.setAccessible(true);
}
Object transformer = transformerField.get(transformHandler);
if(!fixedArgsClassCount) {
Path syntheticFolderPath = ModernFixCachingClassTransformer.CLASS_CACHE_FOLDER.toPath().resolve("org").resolve("spongepowered").resolve("asm").resolve("synthetic");
if(Files.exists(syntheticFolderPath)) {
Field extensionsField = transformer.getClass().getDeclaredField("extensions");
extensionsField.setAccessible(true);
Extensions extensions = (Extensions)extensionsField.get(transformer);
ArgsClassGenerator argsGen = extensions.getGenerator(ArgsClassGenerator.class);
Field nextIndexField = ArgsClassGenerator.class.getDeclaredField("nextIndex");
try(Stream<Path> argsStream = Files.find(syntheticFolderPath, 1, (path, attr) -> path.getFileName().toString().startsWith("Args$"))) {
int[] startIndex = new int[1];
startIndex[0] = 1;
argsStream.forEach(path -> {
String fileName = path.getFileName().toString();
try {
int idx = Integer.parseInt(fileName.replace("Args$", ""));
startIndex[0] = Math.max(startIndex[0], idx + 1);
} catch(NumberFormatException e) {
ModernFixCachingClassTransformer.LOGGER.warn("Unexpected classname: " + fileName);
}
});
nextIndexField.setAccessible(true);
nextIndexField.set(argsGen, startIndex[0]);
ModernFixCachingClassTransformer.LOGGER.debug("Patched ArgsClassGenerator to start at index " + startIndex[0]);
} catch(IOException e) {
ModernFixCachingClassTransformer.LOGGER.error("Failed to adjust Mixin synthetic args");
}
}
fixedArgsClassCount = true;
}
if(processorField == null) {
processorField = transformer.getClass().getDeclaredField("processor");
processorField.setAccessible(true);
}
Object processor = processorField.get(transformer);
if(environmentField == null) {
environmentField = processor.getClass().getDeclaredField("currentEnvironment");
environmentField.setAccessible(true);
}
MixinEnvironment currentEnv = (MixinEnvironment)environmentField.get(processor);
if(currentEnv == null || currentEnv.getPhase() != MixinEnvironment.Phase.DEFAULT) {
return null; /* no hash obtained until mixin is ready */
}
Field configsField = processor.getClass().getDeclaredField("configs");
configsField.setAccessible(true);
List<?> configs = (List<?>)configsField.get(processor);
Field mixinsField = Class.forName("org.spongepowered.asm.mixin.transformer.MixinConfig").getDeclaredField("mixins");
mixinsField.setAccessible(true);
/* getTargetClasses can't be used because it's package-private */
Field classNamesField = Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo").getDeclaredField("targetClassNames");
classNamesField.setAccessible(true);
HashMap<String, ArrayList<IMixinInfo>> mixinsByClass = new HashMap<>();
for(Object config : configs) {
List<? extends IMixinInfo> mixins = (List<? extends IMixinInfo>)mixinsField.get(config);
for(IMixinInfo mixin : mixins) {
List<String> targetClassNames = (List<String>)classNamesField.get(mixin);
for(String s : targetClassNames) {
mixinsByClass.computeIfAbsent(s, k -> new ArrayList<>()).add(mixin);
}
}
}
for(ArrayList<IMixinInfo> infos : mixinsByClass.values()) {
infos.sort((info1, info2) -> Comparator.<String>naturalOrder().compare(info1.getClassName(), info2.getClassName()));
}
/* Now go through each class name and hash it */
HashMap<String, byte[]> hashesByClassInit = new HashMap<>();
for(Map.Entry<String, ArrayList<IMixinInfo>> mixinsForClass : mixinsByClass.entrySet()) {
hasher.reset();
for(IMixinInfo mixin : mixinsForClass.getValue()) {
URL url = Thread.currentThread().getContextClassLoader().getResource(mixin.getClassName().replace('.', '/') + ".class");
if(url == null)
throw new IllegalStateException("Can't find " + mixin.getClassName());
byte[] bytecode;
try {
bytecode = Resources.asByteSource(url).read();
} catch(IOException e) {
throw new RuntimeException(e);
}
hasher.update(bytecode);
}
hashesByClassInit.put(mixinsForClass.getKey().replace('/', '.'), hasher.digest());
}
hashesByClass = hashesByClassInit;
} catch(ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
return hashesByClass.getOrDefault(className, NO_MIXINS);
}
}

View File

@ -1,79 +0,0 @@
package org.embeddedt.modernfix.command;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager;
import org.embeddedt.modernfix.structure.CachingStructureManager;
import java.io.InputStream;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static net.minecraft.commands.Commands.literal;
public class ModernFixCommands {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(literal("modernfix")
.then(literal("upgradeStructures")
.requires(source -> source.hasPermission(3))
.executes(context -> {
ServerLevel level = context.getSource().getLevel();
if(level == null) {
context.getSource().sendFailure(Component.literal("Couldn't find server level"));
return 0;
}
ResourceManager manager = level.getServer().resources.resourceManager();
Map<ResourceLocation, Resource> structures = manager.listResources("structures", p -> p.getPath().endsWith(".nbt"));
int upgradedNum = 0;
Pattern pathPattern = Pattern.compile("^structures/(.*)\\.nbt$");
for(Map.Entry<ResourceLocation, Resource> entry : structures.entrySet()) {
upgradedNum++;
ResourceLocation found = entry.getKey();
Matcher matcher = pathPattern.matcher(found.getPath());
if(!matcher.matches())
continue;
ResourceLocation structureLocation = new ResourceLocation(found.getNamespace(), matcher.group(1));
try(InputStream resource = entry.getValue().open()) {
CachingStructureManager.readStructureTag(structureLocation, level.getServer().getFixerUpper(), resource);
Component msg = Component.literal("checked " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")");
context.getSource().sendSuccess(() -> msg, false);
} catch(Throwable e) {
ModernFix.LOGGER.error("Couldn't upgrade structure " + found, e);
context.getSource().sendFailure(Component.literal("error reading " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")"));
}
}
context.getSource().sendSuccess(() -> Component.literal("All structures upgraded"), false);
return 1;
}))
.then(literal("mcfunctions").requires(source -> source.hasPermission(3))
.executes(context -> {
ServerLevel level = context.getSource().getLevel();
if(level == null) {
context.getSource().sendFailure(Component.literal("Couldn't find server level"));
return 0;
}
if (level.getServer().getFunctions() instanceof IProfilingServerFunctionManager profiler) {
context.getSource().sendSuccess(() -> Component.literal("mcfunction runtime breakdown:"), false);
for(String line : profiler.mfix$getProfilingResults().split("\n")) {
context.getSource().sendSuccess(() -> Component.literal(line), false);
}
return 1;
} else {
context.getSource().sendFailure(Component.literal("ModernFix mcfunction profiling is not enabled on this server."));
return 0;
}
}))
);
}
}

View File

@ -1,56 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.buffer_builder_leak;
import com.mojang.blaze3d.vertex.BufferBuilder;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.render.UnsafeBufferHelper;
import org.spongepowered.asm.mixin.Dynamic;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.nio.ByteBuffer;
@Mixin(value = BufferBuilder.class, priority = 1500)
@ClientOnlyMixin
public class BufferBuilderMixin {
@Shadow private ByteBuffer buffer;
private static boolean leakReported = false;
private boolean mfix$shouldFree = true;
@Dynamic
@Inject(method = "flywheel$injectForRender", at = @At("RETURN"), remap = false, require = 0)
private void preventFree(CallbackInfo ci) {
mfix$shouldFree = false;
}
/**
* Ensure UnsafeBufferHelper is classloaded early, to avoid Forge's event transformer showing an error in the log.
*/
@Inject(method = "<clinit>", at = @At(value = "RETURN"))
private static void initUnsafeBufferHelper(CallbackInfo ci) {
UnsafeBufferHelper.init();
}
@Override
protected void finalize() throws Throwable {
try {
ByteBuffer buf = buffer;
// can be null if a mod already tried to free the buffer
if(buf != null && mfix$shouldFree) {
if(!leakReported) {
leakReported = true;
ModernFix.LOGGER.warn("One or more BufferBuilders have been leaked, ModernFix will attempt to correct this.");
}
UnsafeBufferHelper.free(buf);
buffer = null;
}
} finally {
super.finalize();
}
}
}

View File

@ -1,27 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.buffer_builder_leak;
import com.mojang.blaze3d.vertex.BufferBuilder;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.renderer.RenderType;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(RenderBuffers.class)
@ClientOnlyMixin
public class RenderBuffersMixin {
/**
* @author embeddedt
* @reason put() may be called for multiple instances of the same render type (e.g. signSheet and hangingSignSheet
* in 1.20.1). This leaks the previous BufferBuilder if one is already in the map.
*/
@Inject(method = "put", at = @At("HEAD"), cancellable = true)
private static void mfix$preventBufferLeak(Object2ObjectLinkedOpenHashMap<RenderType, BufferBuilder> mapBuilders, RenderType renderType, CallbackInfo ci) {
if (mapBuilders.containsKey(renderType)) {
ci.cancel();
}
}
}

View File

@ -1,163 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.datafixers.util.Either;
import net.minecraft.CrashReport;
import net.minecraft.ReportedException;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.util.thread.BlockableEventLoop;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import org.embeddedt.modernfix.ModernFix;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.lang.reflect.Field;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.function.Function;
@Mixin(ChunkMap.class)
public abstract class ChunkMapLoadMixin {
@Shadow
@Nullable
protected abstract ChunkHolder getVisibleChunkIfPresent(long l);
@Shadow
@Final
private BlockableEventLoop<Runnable> mainThreadExecutor;
@Unique
private static final ThreadLocal<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> MFIX_SURROGATE_FUTURE = new ThreadLocal<>();
@Unique
private final ConcurrentLinkedQueue<Throwable> mfix$promotionExceptions = new ConcurrentLinkedQueue<>();
/**
* @author embeddedt
* @reason This redirect makes several changes to how full chunk promotion works. First of all, promotion runs
* directly in the context of the main thread executor, rather than going through the priority sorter.
* This change allows attempts to load other chunks from within the promotion lambda to succeed (important
* for bad EntityJoinLevelEvent implementations to not deadlock the game). Second, it slightly alters the
* semantics of protoChunkToFullChunk so that the FULL chunk future will be completed before postload
* callbacks finish running. This change allows attempts to load the _same_ chunk in the promotion lambda to
* succeed, as otherwise the future would block waiting for itself to complete.
*
* <p>This is a cleaner version of a similar trick used in ModernFix versions for 1.16, which deferred specifically
* entity addition to happen outside the futures.
*/
@Redirect(method = "protoChunkToFullChunk", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 0))
private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> createSurrogateFuture(
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> previousFuture,
Function<? super Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>, ? extends Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> fn,
Executor executor) {
var surrogate = new CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>();
// Unlike vanilla, we execute the promotion lambda in mainThreadExecutor, rather than within the context
// of the task sorter. Doing this avoids deadlocking the sorter if a blocking chunk load is attempted
// during chunk promotion. We still initially compose the future through the sorter's executor to stop promotion
// from running earlier than it would in vanilla.
previousFuture.thenComposeAsync(CompletableFuture::completedFuture, executor).thenApplyAsync(either -> {
// running on thread that executes lambda body
MFIX_SURROGATE_FUTURE.set(surrogate);
try {
return fn.apply(either);
} finally {
MFIX_SURROGATE_FUTURE.remove();
}
}, this.mainThreadExecutor).whenComplete((either, throwable) -> {
if (throwable != null) {
if (!surrogate.isDone()) {
surrogate.completeExceptionally(throwable);
} else {
// The chunk has already become visible at FULL status, so we
// track the exception ourselves and manually rethrow it at the right point
// to trigger a server crash
this.mfix$promotionExceptions.add(throwable);
}
} else {
surrogate.complete(either);
}
});
// Return the surrogate
return surrogate;
}
/**
* @author embeddedt
* @reason Complete the surrogate future as soon as basic promotion is done, and before we start loading entities
* & block entities. This allows EntityJoinLevelEvent to read the current chunk.
*/
@Inject(method = "lambda$protoChunkToFullChunk$34", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/LevelChunk;runPostLoad()V"))
private void completeSurrogateFuture(ChunkHolder holder, ChunkAccess p_214856_, CallbackInfoReturnable<ChunkAccess> cir,
@Local(ordinal = 0) LevelChunk levelChunk) {
var future = MFIX_SURROGATE_FUTURE.get();
if (future != null) {
future.complete(Either.left(levelChunk));
}
}
@Inject(method = "tick()V", at = @At("HEAD"))
private void reportDeferredPromotionException(CallbackInfo ci) {
var throwable = this.mfix$promotionExceptions.poll();
if (throwable == null) {
return;
}
if (throwable instanceof ReportedException e) {
throw e;
} else {
throw new ReportedException(CrashReport.forThrowable(throwable, "Exception during promotion of chunk to FULL status"));
}
}
// we also preserve the legacy currentlyLoading field to keep Forge parity
private static final Field currentlyLoadingField = ObfuscationReflectionHelper.findField(ChunkHolder.class, "currentlyLoading");
private static void setCurrentlyLoading(ChunkHolder holder, LevelChunk value) {
try {
currentlyLoadingField.set(holder, value);
} catch(ReflectiveOperationException e) {
e.printStackTrace();
}
}
/**
* Set currentlyLoading before calling runPostLoad and restore its old value afterwards. We track the old value
* to avoid conflicting with Forge if/when this feature is added.
*/
@WrapOperation(method = "lambda$protoChunkToFullChunk$34", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/chunk/LevelChunk;runPostLoad()V"))
private void setCurrentLoadingThenPostLoad(LevelChunk chunk, Operation<Void> operation) {
ChunkHolder holder = this.getVisibleChunkIfPresent(chunk.getPos().toLong());
if(holder != null) {
LevelChunk prevLoading = null;
try {
prevLoading = (LevelChunk)currentlyLoadingField.get(holder);
} catch(ReflectiveOperationException e) {
e.printStackTrace();
}
try {
setCurrentlyLoading(holder, chunk);
operation.call(chunk);
} finally {
setCurrentlyLoading(holder, prevLoading);
}
} else {
ModernFix.LOGGER.warn("Unable to find chunk holder for loading chunk");
operation.call(chunk);
}
}
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.gameevent.GameEvent;
import org.embeddedt.modernfix.ModernFix;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(Entity.class)
public class EntityMixin {
/**
* @author embeddedt
* @reason When an entity is added to the world via the worldgen load path (ChunkMap#postLoadProtoChunk calling
* ServerLevel#addWorldGenChunkEntities), attempts to add a passenger result in a deadlock when the sculk event
* tries to raytrace blocks. To fix this, we skip firing the sculk event if the chunk the entity is within is not
* loaded.
*/
@WrapWithCondition(method = "addPassenger", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;gameEvent(Lnet/minecraft/world/level/gameevent/GameEvent;Lnet/minecraft/world/entity/Entity;)V"))
private boolean onlyAddIfSelfChunkLoaded(Entity instance, GameEvent event, Entity entity) {
var chunkPos = instance.chunkPosition();
if (instance.level() instanceof ServerLevel serverLevel && serverLevel.getChunkSource().getChunkNow(chunkPos.x, chunkPos.z) == null) {
ModernFix.LOGGER.warn("Skipped emitting ENTITY_MOUNT game event for entity {} as it would cause deadlock", instance.toString());
return false;
} else {
return true;
}
}
}

View File

@ -1,57 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.chunk_deadlock;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
@Mixin(value = ServerChunkCache.class, priority = 1100)
public abstract class ServerChunkCache_CurrentLoadingMixin {
@Shadow @Nullable protected abstract ChunkHolder getVisibleChunkIfPresent(long l);
private static final MethodHandle CURRENTLY_LOADING;
static {
try {
Field currentlyLoadingField = ObfuscationReflectionHelper.findField(ChunkHolder.class, "currentlyLoading");
currentlyLoadingField.setAccessible(true);
CURRENTLY_LOADING = MethodHandles.lookup().unreflectGetter(currentlyLoadingField);
} catch(Exception e) {
throw new RuntimeException("Failed to get currentlyLoading field", e);
}
}
/**
* Check the currentlyLoading field before going to the future chain, as was done in 1.16. In 1.18 upstream seems
* to have only applied this to getChunkNow().
*/
@Inject(method = "getChunk", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerChunkCache;getChunkFutureMainThread(IILnet/minecraft/world/level/chunk/ChunkStatus;Z)Ljava/util/concurrent/CompletableFuture;"), cancellable = true, require = 0)
private void checkCurrentlyLoading(int chunkX, int chunkZ, ChunkStatus requiredStatus, boolean load, CallbackInfoReturnable<ChunkAccess> cir) {
long i = ChunkPos.asLong(chunkX, chunkZ);
ChunkHolder holder = this.getVisibleChunkIfPresent(i);
if(holder != null) {
LevelChunk c;
try {
c = (LevelChunk)CURRENTLY_LOADING.invokeExact(holder);
} catch(Throwable e) {
e.printStackTrace();
c = null;
}
if(c != null)
cir.setReturnValue(c);
}
}
}

View File

@ -1,55 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.cofh_core_crash;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
/**
* Fix getOrCreateFlag accessing the FLAGS map without synchronization by wrapping all calls to it
* in a synchronized block.
*/
@Pseudo
@Mixin(targets = { "cofh/lib/util/flags/FlagManager" }, remap = false)
@RequiresMod("cofh_core")
public class FlagManagerMixin {
@Shadow @Final
private static Object2ObjectOpenHashMap<String, ?> FLAGS;
@Unique
private static final MethodHandle mfix$getOrCreateFlag;
static {
// use this reflection dance to avoid depending on whether it's implemented via BooleanSupplier or Supplier<Boolean>
try {
Method m = MethodHandles.lookup().lookupClass().getDeclaredMethod("getOrCreateFlag", String.class);
m.setAccessible(true);
mfix$getOrCreateFlag = MethodHandles.lookup().unreflect(m);
} catch(ReflectiveOperationException e) {
throw new AssertionError(e);
}
}
@Redirect(method = "*", at = @At(value = "INVOKE", target = "getOrCreateFlag"), require = 0)
@Coerce
private Object getFlag(@Coerce Object flagHandler, String flag) {
if(flagHandler != this)
throw new AssertionError("Redirect targeted bad getOrCreateFlag invocation");
synchronized (FLAGS) {
try {
return mfix$getOrCreateFlag.invoke((Object)this, flag);
} catch(Throwable e) {
if(e instanceof RuntimeException)
throw (RuntimeException)e;
else
throw new RuntimeException(e);
}
}
}
}

View File

@ -1,31 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.tags.TagKey;
import net.minecraftforge.registries.tags.ITag;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Map;
@Mixin(targets = {"net/minecraftforge/registries/ForgeRegistryTagManager"})
public class ForgeRegistryTagManagerMixin<V> {
@Shadow private volatile Map<TagKey<V>, ITag<V>> tags;
/**
* @author embeddedt (issue found by Uncandango)
* @reason vanilla does not use the correct double-checked locking paradigm, which leads to race conditions
*/
@WrapMethod(method = "getTag", remap = false)
private ITag<V> getTagSafe(@NotNull TagKey<V> name, Operation<ITag<V>> original) {
ITag<V> tag = this.tags.get(name);
if (tag == null) {
synchronized (this) {
tag = original.call(name);
}
}
return tag;
}
}

View File

@ -1,40 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
import net.minecraft.core.HolderSet;
import net.minecraft.core.MappedRegistry;
import net.minecraft.tags.TagKey;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.IdentityHashMap;
import java.util.Map;
@Mixin(value = MappedRegistry.class, priority = 500)
public abstract class MappedRegistryMixin<T> {
@Shadow private volatile Map<TagKey<T>, HolderSet.Named<T>> tags;
@Shadow protected abstract HolderSet.Named<T> createTag(TagKey<T> key);
/**
* @author embeddedt (issue found by Uncandango)
* @reason vanilla does not use the correct double-checked locking paradigm, which leads to race conditions
*/
@Overwrite
public HolderSet.Named<T> getOrCreateTag(TagKey<T> key) {
HolderSet.Named<T> named = this.tags.get(key);
if (named == null) {
// synchronize and check again - this is the bugfix
synchronized (this) {
named = this.tags.get(key);
if (named == null) {
named = this.createTag(key);
Map<TagKey<T>, HolderSet.Named<T>> map = new IdentityHashMap<>(this.tags);
map.put(key, named);
this.tags = map;
}
}
}
return named;
}
}

View File

@ -1,39 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
import net.minecraft.core.HolderSet;
import net.minecraft.tags.TagKey;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.IdentityHashMap;
import java.util.Map;
@Mixin(targets = {"net/minecraftforge/registries/NamespacedWrapper"}, priority = 500)
public abstract class NamespacedWrapperMixin<T> {
@Shadow(aliases = {"tags"}) private volatile Map<TagKey<T>, HolderSet.Named<T>> tags;
@Shadow(aliases = {"createTag"}) protected abstract HolderSet.Named<T> m_211067_(TagKey<T> key);
/**
* @author embeddedt (issue found by Uncandango)
* @reason vanilla does not use the correct double-checked locking paradigm, which leads to race conditions
*/
@Overwrite
public HolderSet.Named<T> getOrCreateTag(TagKey<T> key) {
HolderSet.Named<T> named = this.tags.get(key);
if (named == null) {
// synchronize and check again - this is the bugfix
synchronized (this) {
named = this.tags.get(key);
if (named == null) {
named = this.m_211067_(key);
Map<TagKey<T>, HolderSet.Named<T>> map = new IdentityHashMap<>(this.tags);
map.put(key, named);
this.tags = map;
}
}
}
return named;
}
}

View File

@ -1,49 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.concurrency;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.client.Minecraft;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ReloadableResourceManager;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingStage;
import net.minecraftforge.registries.ForgeRegistries;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.forge.init.ModernFixForge;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(ReloadableResourceManager.class)
@ClientOnlyMixin
public abstract class ReloadableResourceManagerMixin {
@Shadow
@Final
private PackType type;
@Shadow
public abstract void registerReloadListener(PreparableReloadListener listener);
/**
* @author embeddedt
* @reason complain loudly when reload listeners are being registered too late in a way that would cause
* concurrency issues, and prevent them from crashing the game
*/
@WrapMethod(method = "registerReloadListener")
private void checkCallingThread(PreparableReloadListener listener, Operation<Void> original) {
if (ModernFixForge.registryEventsFired && this.type == PackType.CLIENT_RESOURCES
&& (Object)this == Minecraft.getInstance().getResourceManager()
&& !Minecraft.getInstance().isSameThread()) {
ModernFix.LOGGER.error("A mod is calling registerReloadListener at the wrong time. This will cause random concurrency crashes when ModernFix is not installed. Please report this to them. If you are a modder, refer to https://github.com/embeddedt/ModernFix/wiki/registerReloadListener-called-on-wrong-thread for more information.", new Exception("registerReloadListener called on wrong thread"));
// Defer the call onto the main client thread. There is a decent chance the mod's listener will be
// ignored in this case, but it is more predictable than allowing them to randomly crash the game.
Minecraft.getInstance().tell(() -> this.registerReloadListener(listener));
return;
}
original.call(listener);
}
}

View File

@ -1,35 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.ctm_resourceutil_cme;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import team.chisel.ctm.client.util.ResourceUtil;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
@Mixin(ResourceUtil.class)
@RequiresMod("ctm")
@ClientOnlyMixin
@SuppressWarnings({"rawtypes", "unchecked"})
public class ResourceUtilMixin {
@Shadow(remap = false) @Final @Mutable
private static Map metadataCache;
/**
* @author embeddedt
* @reason quick fix to prevent rare CMEs
*/
@Inject(method = "<clinit>", at = @At("RETURN"))
private static void synchronizeMetadataCache(CallbackInfo ci) {
if(!(metadataCache instanceof ConcurrentMap))
metadataCache = Collections.synchronizedMap(metadataCache);
}
}

View File

@ -1,24 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.ender_dragon_leak;
import net.minecraft.client.renderer.entity.EnderDragonRenderer;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(EnderDragonRenderer.class)
@ClientOnlyMixin
public abstract class EnderDragonRendererMixin {
@Shadow @Final private EnderDragonRenderer.DragonModel model;
/**
* Prevent leaking the client world through the entity reference.
*/
@Inject(method = "render(Lnet/minecraft/world/entity/boss/enderdragon/EnderDragon;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At("RETURN"))
private void clearDragonEntityReference(CallbackInfo ci) {
this.model.entity = null;
}
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraftforge.client.event.RenderLivingEvent;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.IEventBus;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(LivingEntityRenderer.class)
@ClientOnlyMixin
public class LivingEntityRendererMixin {
@Redirect(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/eventbus/api/IEventBus;post(Lnet/minecraftforge/eventbus/api/Event;)Z", ordinal = 0))
private boolean fireCheckingPoseStack(IEventBus instance, Event event) {
PoseStack stack = ((RenderLivingEvent)event).getPoseStack();
int size = ((PoseStackAccessor)stack).getPoseStack().size();
if (instance.post(event)) {
// Pop the stack if someone pushed it in the event
while (((PoseStackAccessor)stack).getPoseStack().size() > size) {
stack.popPose();
}
return true;
} else {
return false;
}
}
}

View File

@ -1,30 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraftforge.client.event.RenderPlayerEvent;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.IEventBus;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(PlayerRenderer.class)
@ClientOnlyMixin
public class PlayerRendererMixin {
@Redirect(method = "render(Lnet/minecraft/client/player/AbstractClientPlayer;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/eventbus/api/IEventBus;post(Lnet/minecraftforge/eventbus/api/Event;)Z", ordinal = 0))
private boolean fireCheckingPoseStack(IEventBus instance, Event event) {
PoseStack stack = ((RenderPlayerEvent)event).getPoseStack();
int size = ((PoseStackAccessor)stack).getPoseStack().size();
if (instance.post(event)) {
// Pop the stack if someone pushed it in the event
while (((PoseStackAccessor)stack).getPoseStack().size() > size) {
stack.popPose();
}
return true;
} else {
return false;
}
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.entity_pose_stack;
import com.mojang.blaze3d.vertex.PoseStack;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Deque;
@Mixin(PoseStack.class)
@ClientOnlyMixin
public interface PoseStackAccessor {
@Accessor
Deque<PoseStack.Pose> getPoseStack();
}

View File

@ -1,26 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.extra_experimental_screen;
import com.mojang.serialization.Lifecycle;
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen;
import net.minecraft.world.level.storage.PrimaryLevelData;
import net.minecraft.world.level.storage.WorldData;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(CreateWorldScreen.class)
@ClientOnlyMixin
public class CreateWorldScreenMixin {
/**
* Fix experimental world dialog still being shown the first time you reopen a world that was created
* as experimental.
*/
@ModifyArg(method = "createNewWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/worldselection/WorldOpenFlows;createLevelFromExistingSettings(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;Lnet/minecraft/server/ReloadableServerResources;Lnet/minecraft/core/LayeredRegistryAccess;Lnet/minecraft/world/level/storage/WorldData;)V"), index = 3)
private WorldData setExperimentalFlag(WorldData data) {
if(data instanceof PrimaryLevelData pld && data.worldGenSettingsLifecycle() != Lifecycle.stable()) {
pld.withConfirmedWarning(true);
}
return data;
}
}

View File

@ -1,36 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.forge_vehicle_packets;
import net.minecraft.network.protocol.game.ServerboundMoveVehiclePacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(ServerGamePacketListenerImpl.class)
public class ServerGamePacketListenerImplMixin {
@Shadow public ServerPlayer player;
@Redirect(method = "handleMoveVehicle", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerPlayer;absMoveTo(DDDFF)V"), require = 0)
private void movePlayerUsingPositionRider(ServerPlayer player, double x, double y, double z, float yRot, float xRot, ServerboundMoveVehiclePacket packet) {
if(player == this.player) {
// use positionRider
Vec3 oldPos = this.player.position();
yRot = this.player.getYRot();
xRot = this.player.getXRot();
float yHeadRot = this.player.getYHeadRot();
this.player.getRootVehicle().positionRider(this.player);
// keep old rotation
this.player.setYRot(yRot);
this.player.setXRot(xRot);
this.player.setYHeadRot(yHeadRot);
// save old position
this.player.xo = oldPos.x;
this.player.yo = oldPos.y;
this.player.zo = oldPos.z;
} else
player.absMoveTo(x, y, z, yRot, xRot);
}
}

View File

@ -1,95 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.missing_block_entities;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Hypixel (and possibly other buggy servers) send chunks to the client that are missing some block entity data, which
* causes these entities to be invisible. We "fix" this by recreating the block entity on the client with default data,
* which is hopefully what the legacy server also expects.
*/
@Mixin(LevelChunk.class)
@ClientOnlyMixin
public abstract class LevelChunkMixin extends ChunkAccess {
@Shadow @Final private Level level;
@Shadow @Nullable public abstract BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType);
public LevelChunkMixin(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable BlendingData blendingData) {
super(chunkPos, upgradeData, levelHeightAccessor, biomeRegistry, inhabitedTime, sections, blendingData);
}
@Inject(method = "replaceWithPacketData", at = @At("RETURN"))
private void validateBlockEntitiesInChunk(CallbackInfo ci) {
// No reason to check in singleplayer or on the integrated server
if (this.level.isClientSide && !Minecraft.getInstance().isLocalServer()) {
for (int i = 0; i < this.sections.length; i++) {
var section = this.sections[i];
try {
if (!section.hasOnlyAir() && section.maybeHas(BlockBehaviour.BlockStateBase::hasBlockEntity)) {
scanSectionForBlockEntities(section, i);
}
} catch(Exception e) {
ModernFix.LOGGER.error("Exception validating data in chunk", e);
return;
}
}
}
}
@Unique
private void scanSectionForBlockEntities(LevelChunkSection section, int i) {
int chunkXOff = this.chunkPos.x * 16;
int chunkZOff = this.chunkPos.z * 16;
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
int sectionYOff = this.getSectionYFromSectionIndex(i) * 16;
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
var state = section.getBlockState(x, y, z);
if (state.hasBlockEntity()) {
cursor.set(chunkXOff + x, sectionYOff + y, chunkZOff + z);
makeBlockEntityIfNotExists(state, cursor);
}
}
}
}
}
@Unique
private void makeBlockEntityIfNotExists(BlockState state, BlockPos.MutableBlockPos pos) {
if (this.blockEntities.containsKey(pos) || this.pendingBlockEntities.containsKey(pos)) {
return;
}
BlockEntity blockEntity = this.getBlockEntity(pos.immutable(), LevelChunk.EntityCreationType.IMMEDIATE);
if (blockEntity != null && ModernFix.LOGGER.isDebugEnabled()) {
String blockName = state.getBlock().toString();
ModernFix.LOGGER.debug("Created missing block entity for {} at {}", blockName, pos.toShortString());
}
}
}

View File

@ -1,55 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.model_data_manager_cme;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
import net.minecraftforge.client.model.data.ModelDataManager;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* Fix several concurrency issues in the default ModelDataManager.
*/
@Mixin(ModelDataManager.class)
@ClientOnlyMixin
public abstract class ModelDataManagerMixin {
@Shadow protected abstract void refreshAt(ChunkPos chunk);
@Shadow @Final private Map<ChunkPos, Set<BlockPos>> needModelDataRefresh;
/**
* Make the set of positions to refresh a real concurrent hash set rather than relying on synchronizedSet,
* because the returned iterator won't be thread-safe otherwise. See https://github.com/AppliedEnergistics/Applied-Energistics-2/issues/7511
*/
@ModifyArg(method = "requestRefresh", at = @At(value = "INVOKE", target = "Ljava/util/Map;computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;", ordinal = 0), index = 1, remap = false)
private Function<ChunkPos, Set<BlockPos>> changeTypeOfSetUsed(Function<ChunkPos, Set<BlockPos>> mappingFunction) {
return pos -> ConcurrentHashMap.newKeySet();
}
@Redirect(method = "getAt(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/Map;", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/client/model/data/ModelDataManager;refreshAt(Lnet/minecraft/world/level/ChunkPos;)V"), remap = false)
private void onlyRefreshOnMainThread(ModelDataManager instance, ChunkPos pos) {
// Only refresh model data on the main thread. This prevents calling getBlockEntity from worker threads
// which could cause weird CMEs or other behavior.
// Avoid the loop if no model data needs to be refreshed, to prevent unnecessary allocation.
if(Minecraft.getInstance().isSameThread() && !needModelDataRefresh.isEmpty()) {
// Refresh the given chunk, and all its neighbors. This is less efficient than the default code
// but we have no choice since we need to not do refreshing on workers, and blocks might
// try to access model data in neighboring chunks.
for(int x = -1; x <= 1; x++) {
for(int z = -1; z <= 1; z++) {
refreshAt(new ChunkPos(pos.x + x, pos.z + z));
}
}
}
}
}

View File

@ -1,52 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.datafixers.util.Either;
import net.minecraft.server.level.*;
import net.minecraft.util.thread.BlockableEventLoop;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@Mixin(ChunkMap.class)
public abstract class ChunkMapMixin {
@Shadow @Final private BlockableEventLoop<Runnable> mainThreadExecutor;
/* https://github.com/PaperMC/Paper/blob/ver/1.17.1/patches/server/0752-Fix-chunks-refusing-to-unload-at-low-TPS.patch */
@ModifyArg(method = "prepareAccessibleChunk", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"), index = 1)
private Executor useMainThreadExecutor(Executor executor) {
return this.mainThreadExecutor;
}
/**
* @author embeddedt
* @reason 1.17+ uses getNow to check if the parent future is ready, and calls scheduleChunkGeneration as soon as
* it is found to not be ready. In the latter scenario, a massive number of extra CompletableFutures are allocated
* even if they are not actually necessary if the future is waited for. To prevent this, if the parent future
* is not done, we wait for it to complete and then retry schedule(). This will either detect an adequate
* status and return a loading future, or re-enter this injector with the parent future completed, in which case
* we proceed to schedule generation as originally requested.
*/
@WrapOperation(method = "schedule", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkMap;scheduleChunkGeneration(Lnet/minecraft/server/level/ChunkHolder;Lnet/minecraft/world/level/chunk/ChunkStatus;)Ljava/util/concurrent/CompletableFuture;"))
private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> mfix$avoidSchedulingGenerationPrematurely(ChunkMap map, ChunkHolder holder, ChunkStatus status, Operation<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> original) {
if (!status.hasLoadDependencies()) {
var parentFuture = holder.getOrScheduleFuture(status.getParent(), map);
if (!parentFuture.isDone()) {
return parentFuture.thenComposeAsync(
either -> map.schedule(holder, status),
this.mainThreadExecutor
);
}
}
return original.call(map, holder, status);
}
}

View File

@ -1,53 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.paper_chunk_patches;
import net.minecraft.util.SortedArraySet;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.function.Predicate;
@Mixin(SortedArraySet.class)
@RequiresMod("!moonrise")
public abstract class SortedArraySetMixin<T> extends AbstractSet<T> {
@Shadow private int size;
@Shadow private T[] contents;
// Paper start - optimise removeIf
@Override
public boolean removeIf(Predicate<? super T> filter) {
// prev. impl used an iterator, which could be n^2 and creates garbage
int i = 0, len = this.size;
T[] backingArray = this.contents;
for (;;) {
if (i >= len) {
return false;
}
if (!filter.test(backingArray[i])) {
++i;
continue;
}
break;
}
// we only want to write back to backingArray if we really need to
int lastIndex = i; // this is where new elements are shifted to
for (; i < len; ++i) {
T curr = backingArray[i];
if (!filter.test(curr)) { // if test throws we're screwed
backingArray[lastIndex++] = curr;
}
}
// cleanup end
Arrays.fill(backingArray, lastIndex, len, null);
this.size = lastIndex;
return true;
}
}

View File

@ -1,47 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.recipe_book_type_desync;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.stats.RecipeBookSettings;
import net.minecraft.world.inventory.RecipeBookType;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.forge.packet.NetworkUtils;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
@Mixin(RecipeBookSettings.class)
@ClientOnlyMixin
public class RecipeBookSettingsMixin {
private static int mfix$maxVanillaOrdinal;
static {
int ord = 0;
for(Field f : RecipeBookType.class.getDeclaredFields()) {
if(RecipeBookType.class.isAssignableFrom(f.getType()) && Modifier.isStatic(f.getModifiers()) && Modifier.isPublic(f.getModifiers())) {
try {
f.setAccessible(true);
RecipeBookType type = (RecipeBookType)f.get(null);
ord = Math.max(type.ordinal(), ord);
} catch(Exception e) {
e.printStackTrace();
ord = Integer.MAX_VALUE - 1;
break;
}
}
}
mfix$maxVanillaOrdinal = ord;
}
@Redirect(method = "read(Lnet/minecraft/network/FriendlyByteBuf;)Lnet/minecraft/stats/RecipeBookSettings;", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/FriendlyByteBuf;readBoolean()Z"))
private static boolean useDefaultBooleanIfVanilla(FriendlyByteBuf buf, @Local(ordinal = 0) RecipeBookType type) {
if(type.ordinal() >= (mfix$maxVanillaOrdinal + 1) && NetworkUtils.isCurrentlyVanilla) {
ModernFix.LOGGER.warn("Not reading recipe book data for type '{}' as we are using vanilla connection", type.name());
return false; // skip actually reading buffer
}
return buf.readBoolean();
}
}

View File

@ -1,27 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.registry_ops_cme;
import net.minecraft.core.Registry;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@Mixin(targets = {"net/minecraft/resources/RegistryOps$1"})
public class RegistryOpsMemoizedMixin {
@Shadow @Final @Mutable
private Map<ResourceKey<? extends Registry<?>>, Optional<? extends RegistryOps.RegistryInfo<?>>> lookups;
@Inject(method = "<init>", at = @At("RETURN"))
private void useConcurrentMap(RegistryOps.RegistryInfoLookup registryInfoLookup, CallbackInfo ci) {
this.lookups = new ConcurrentHashMap<>(this.lookups);
}
}

View File

@ -1,14 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.removed_dimensions;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(LevelStorageSource.class)
public class LevelStorageSourceMixin {
@ModifyArg(method = "*", at = @At(value = "INVOKE", target = "Lcom/mojang/serialization/DataResult;getOrThrow(ZLjava/util/function/Consumer;)Ljava/lang/Object;", ordinal = 0), index = 0)
private static boolean alwaysAllowPartialDimensions(boolean flag) {
return true;
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.restore_old_dragon_movement;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Slice;
@Mixin(EnderDragon.class)
public class EnderDragonMixin {
/**
* @author embeddedt (regression identified by Jukitsu in MC-272431)
* @reason Revert dragon vertical movement behavior to how it worked in 1.13 and older. Note: this patches techniques
* that rely on the predictable vertical descent like one-cycling.
*/
@ModifyArg(method = "aiStep",
slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/boss/enderdragon/phases/DragonPhaseInstance;getFlyTargetLocation()Lnet/minecraft/world/phys/Vec3;")),
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;add(DDD)Lnet/minecraft/world/phys/Vec3;", ordinal = 0), index = 1)
private double fixVerticalVelocityScale(double y) {
return y * 10;
}
}

View File

@ -1,27 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.unsafe_modded_shape_caches;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* see {@link ShapeCacheRSMixin}
*/
@Pseudo
@Mixin(targets = { "com/lothrazar/cyclic/block/cable/ShapeCache" }, remap = false)
@RequiresMod("cyclic")
@SuppressWarnings("rawtypes")
public class ShapeCacheCyclicMixin {
@Shadow @Final @Mutable private static final Map CACHE = new ConcurrentHashMap();
@Inject(method = "<clinit>", at = @At("RETURN"))
private static void mfix$notify(CallbackInfo ci) {
ModernFix.LOGGER.info("Made Cyclic shape cache map thread-safe");
}
}

View File

@ -1,29 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.unsafe_modded_shape_caches;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.RequiresMod;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Some mods use a custom shape cache with a non-thread-safe map. There is no reason why this wouldn't cause crashes
* in vanilla as well if getShape was called on two threads at once. It seems more likely to happen with ModernFix
* installed due to the dynamic blockstate cache generation, so we solve it by making the maps thread-safe.
*/
@Pseudo
@Mixin(targets = { "com/refinedmods/refinedstorage/block/shape/ShapeCache" }, remap = false)
@RequiresMod("refinedstorage")
@SuppressWarnings("rawtypes")
public class ShapeCacheRSMixin {
@Shadow @Final @Mutable private static final Map CACHE = new ConcurrentHashMap();
@Inject(method = "<clinit>", at = @At("RETURN"))
private static void mfix$notify(CallbackInfo ci) {
ModernFix.LOGGER.info("Made Refined Storage shape cache map thread-safe");
}
}

View File

@ -1,43 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.world_leaks;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.concurrent.atomic.AtomicReferenceArray;
@Mixin(Minecraft.class)
@ClientOnlyMixin
public class MinecraftMixin {
@Shadow @Nullable public ClientLevel level;
/**
* To mitigate the effect of leaked client worlds, clear most of the data structures that waste memory.
*/
@Inject(method = "clearLevel(Lnet/minecraft/client/gui/screens/Screen;)V", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraft/client/Minecraft;level:Lnet/minecraft/client/multiplayer/ClientLevel;"))
private void clearLevelDataForLeaks(CallbackInfo ci) {
if(this.level != null) {
try {
AtomicReferenceArray<LevelChunk> chunks = this.level.getChunkSource().storage.chunks;
for(int i = 0; i < chunks.length(); i++) {
chunks.set(i, null);
}
this.level.getChunkSource().lightEngine = new LevelLightEngine(this.level.getChunkSource(), false, false);
// clear BE list otherwise they will hold chunks
this.level.blockEntityTickers.clear();
} catch(RuntimeException e) {
ModernFix.LOGGER.error("Exception clearing level data", e);
}
}
}
}

View File

@ -1,24 +0,0 @@
package org.embeddedt.modernfix.common.mixin.bugfix.world_screen_skipped;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen;
import net.minecraft.client.gui.screens.worldselection.WorldSelectionList;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldSelectionList.WorldListEntry.class)
@ClientOnlyMixin
public class WorldSelectionListMixin {
@Shadow @Final private Minecraft minecraft;
@Inject(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/worldselection/WorldSelectionList$WorldListEntry;doDeleteWorld()V", ordinal = 0, shift = At.Shift.AFTER), cancellable = true)
private void preventClosingCreateScreenAfterDelete(CallbackInfo ci) {
if(minecraft.screen instanceof CreateWorldScreen)
ci.cancel();
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.common.mixin.core;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.server.Bootstrap;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Bootstrap.class)
@ClientOnlyMixin
public class BootstrapClientMixin {
/**
* Hack to workaround RenderStateShard deadlock (by loading it early on a single thread). We use validate
* here to ensure Forge registries are initialized.
*/
@Inject(method = "validate", at = @At("HEAD"))
private static void loadClientClasses(CallbackInfo ci) {
RenderType.solid();
}
}

View File

@ -1,38 +0,0 @@
package org.embeddedt.modernfix.common.mixin.core;
import net.minecraft.server.Bootstrap;
import net.minecraftforge.network.NetworkConstants;
import org.embeddedt.modernfix.forge.classloading.ManifestCompactor;
import org.slf4j.Logger;
import org.embeddedt.modernfix.forge.load.ModWorkManagerQueue;
import org.embeddedt.modernfix.util.TimeFormatter;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.lang.management.ManagementFactory;
@Mixin(Bootstrap.class)
public class BootstrapMixin {
@Shadow private static boolean isBootstrapped;
@Shadow @Final private static Logger LOGGER;
@Inject(method = "bootStrap", at = @At("HEAD"))
private static void doModernFixBootstrap(CallbackInfo ci) {
if(!isBootstrapped) {
LOGGER.info("ModernFix reached bootstrap stage ({} after launch)", TimeFormatter.formatNanos(ManagementFactory.getRuntimeMXBean().getUptime() * 1000L * 1000L));
ModWorkManagerQueue.replace();
ManifestCompactor.compactManifests();
}
}
/* for https://github.com/MinecraftForge/MinecraftForge/issues/9505 */
@Inject(method = "bootStrap", at = @At("RETURN"))
private static void doClassloadHack(CallbackInfo ci) {
NetworkConstants.init();
}
}

View File

@ -1,31 +0,0 @@
package org.embeddedt.modernfix.common.mixin.core;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.logging.CrashReportAnalyser;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
@Mixin(CrashReportAnalyser.class)
public class CrashReportAnalyserMixin {
@Shadow @Final private static Map<IModInfo, String[]> SUSPECTED_MODS;
/**
* @author embeddedt
* @reason Remove ModernFix from the list of suspected mods when a crash happens. Otherwise, we get blamed
* for "registry object not present" crashes if users don't interpret the crash before reporting
* it.
*
* It seems unlikely ModernFix will simultaneously cause a crash while it's not obvious it caused it.
*/
@Inject(method = "buildSuspectedModsSection", at = @At("HEAD"), require = 0, remap = false)
private static void removeOurselvesFromSuspectedMods(StringBuilder stringBuilder, CallbackInfo ci) {
SUSPECTED_MODS.keySet().removeIf(iModInfo -> iModInfo.getModId().equals("modernfix"));
}
}

View File

@ -1,16 +0,0 @@
package org.embeddedt.modernfix.common.mixin.core;
import net.minecraftforge.registries.GameData;
import org.embeddedt.modernfix.forge.init.ModernFixForge;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = GameData.class, remap = false)
public class GameDataMixin {
@Inject(method = "postRegisterEvents", at = @At("RETURN"))
private static void markPosted(CallbackInfo ci) {
ModernFixForge.registryEventsFired = true;
}
}

View File

@ -1,43 +0,0 @@
package org.embeddedt.modernfix.common.mixin.core;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.Util;
import net.minecraft.server.MinecraftServer;
import org.embeddedt.modernfix.duck.ITimeTrackingServer;
import org.embeddedt.modernfix.forge.load.MinecraftServerReloadTracker;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin implements ITimeTrackingServer {
@Inject(method = "reloadResources", at = @At("HEAD"))
private void startReloadTrack(Collection<String> selectedIds, CallbackInfoReturnable<CompletableFuture<Void>> cir) {
MinecraftServerReloadTracker.ACTIVE_RELOADS++;
}
@ModifyExpressionValue(method = "reloadResources", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenAcceptAsync(Ljava/util/function/Consumer;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 0))
private CompletableFuture<Void> mfix$endReloadTrack(CompletableFuture<Void> original) {
return original.thenAcceptAsync(val -> {
MinecraftServerReloadTracker.ACTIVE_RELOADS--;
}, (Executor)this);
}
private long mfix$lastTickStartTime = -1L;
@Override
public long mfix$getLastTickStartTime() {
return mfix$lastTickStartTime;
}
@Inject(method = "runServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;tickServer(Ljava/util/function/BooleanSupplier;)V"))
private void trackTickTime(CallbackInfo ci) {
mfix$lastTickStartTime = Util.getMillis();
}
}

View File

@ -1,22 +0,0 @@
package org.embeddedt.modernfix.common.mixin.core;
import net.minecraft.network.Connection;
import net.minecraftforge.network.NetworkHooks;
import org.embeddedt.modernfix.forge.packet.NetworkUtils;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(NetworkHooks.class)
public abstract class NetworkHooksMixin {
@Shadow public static boolean isVanillaConnection(Connection manager) {
throw new AssertionError();
}
@Inject(method = "handleClientLoginSuccess", at = @At("RETURN"), remap = false)
private static void setVanillaGlobalFlag(Connection manager, CallbackInfo ci) {
NetworkUtils.isCurrentlyVanilla = isVanillaConnection(manager);
}
}

View File

@ -1,18 +0,0 @@
package org.embeddedt.modernfix.common.mixin.core;
import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.PalettedContainer;
import org.embeddedt.modernfix.chunk.ExtendedPalettedContainer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(PalettedContainer.class)
public class PalettedContainerMixin<T> implements ExtendedPalettedContainer<T> {
@Shadow
private volatile PalettedContainer.Data<T> data;
@Override
public Palette<T> mfix$getPalette() {
return this.data.palette();
}
}

View File

@ -1,29 +0,0 @@
package org.embeddedt.modernfix.common.mixin.core;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.WorldLoader;
import org.embeddedt.modernfix.forge.load.MinecraftServerReloadTracker;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@Mixin(WorldLoader.class)
public class WorldLoaderMixin {
@Inject(method = "load", at = @At("HEAD"))
private static void trackStartReload(CallbackInfoReturnable<CompletableFuture<?>> cir) {
MinecraftServerReloadTracker.ACTIVE_RELOADS++;
}
@ModifyReturnValue(method = "load", at = @At("RETURN"))
private static <V> CompletableFuture<V> trackEndReload(CompletableFuture<V> original, @Local(ordinal = 1, argsOnly = true) Executor syncExecutor) {
return original.thenApplyAsync(val -> {
MinecraftServerReloadTracker.ACTIVE_RELOADS--;
return val;
}, syncExecutor);
}
}

Some files were not shown because too many files have changed in this diff Show More