Compare commits
No commits in common. "1.20" and "1.7.0" have entirely different histories.
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
|
@ -1 +0,0 @@
|
|||
ko_fi: embeddedt
|
||||
78
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
78
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -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
|
||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
6
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -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
|
||||
140
.github/workflows/gradle.yml
vendored
140
.github/workflows/gradle.yml
vendored
|
|
@ -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
|
||||
27
.github/workflows/wiki_update.yml
vendored
27
.github/workflows/wiki_update.yml
vendored
|
|
@ -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
9
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
11
README.md
11
README.md
|
|
@ -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
|
||||
|
||||
------------
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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("\\/", ".");
|
||||
}
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
org.fury_phoenix.mixinAp.annotation.MixinProcessor,aggregating
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
plugins {
|
||||
id("java")
|
||||
}
|
||||
|
||||
version = "1.1.0"
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
|
@ -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 {
|
||||
}
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package org.embeddedt.modernfix.annotation;
|
||||
|
||||
public enum FeatureLevel {
|
||||
GA, BETA;
|
||||
|
||||
public boolean isAtLeast(FeatureLevel required) {
|
||||
return this.ordinal() >= required.ordinal();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
1
bin/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
*.jar
|
||||
167
build.gradle
Normal file
167
build.gradle
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
210
build.gradle.kts
210
build.gradle.kts
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
1
doc/generated/.gitignore
vendored
1
doc/generated/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
*.md
|
||||
208
doc/logo.svg
208
doc/logo.svg
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 |
|
|
@ -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
|
||||
|
|
@ -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}}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -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
44
gradlew
vendored
|
|
@ -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
37
gradlew.bat
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
5.27
|
||||
|
|
@ -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
|
||||
|
|
@ -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]*)?$'
|
||||
|
|
@ -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}")
|
||||
|
|
@ -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}\",")
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
10
settings.gradle
Normal 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'
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
include("annotation-processor")
|
||||
include("annotations")
|
||||
|
||||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version ("1.0.0")
|
||||
}
|
||||
|
||||
rootProject.name = "modernfix"
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/main/java/org/embeddedt/modernfix/FileWalker.java
Normal file
21
src/main/java/org/embeddedt/modernfix/FileWalker.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
59
src/main/java/org/embeddedt/modernfix/agent/Agent.java
Normal file
59
src/main/java/org/embeddedt/modernfix/agent/Agent.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package org.embeddedt.modernfix.chunk;
|
||||
|
||||
import net.minecraft.world.level.chunk.Palette;
|
||||
|
||||
public interface ExtendedPalettedContainer<T> {
|
||||
Palette<T> mfix$getPalette();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue
Block a user