Compare commits

...

11 Commits

Author SHA1 Message Date
Johannes Frohnmeyer de00f51b06
fix: build in gradle:latest
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2024-04-25 22:21:50 +02:00
Johannes Frohnmeyer 3fa30b834d
chore: update to 1.20.5
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/tag/woodpecker Pipeline failed Details
2024-04-25 21:51:15 +02:00
Johannes Frohnmeyer 0e036599cf
fix: don't respond to events for other players if on client
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2024-03-31 14:13:42 +02:00
Johannes Frohnmeyer 3b5e262bea
fix: hook additional methods for a better chance to catch damage
ci/woodpecker/tag/woodpecker Pipeline is pending Details
ci/woodpecker/push/woodpecker Pipeline was successful Details
2024-03-31 13:58:58 +02:00
Johannes Frohnmeyer 26ef532b30
chore: update to 1.20.4
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-12-07 21:16:27 +01:00
Johannes Frohnmeyer 70758f3966
fix: switch default crash provider. Someone had reported issues with the previous default, this one might work better.
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-11-02 17:16:12 +01:00
Johannes Frohnmeyer d924c522a7
chore: update to 1.20.2
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-09-22 21:16:51 +02:00
Johannes Frohnmeyer d074812af3
feat: Use JNA for WinApiProvider, remove natives
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-07-28 18:54:31 +02:00
Johannes Frohnmeyer 49b97ad38d
Bump to 1.20
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-06-09 17:31:37 +02:00
Johannes Frohnmeyer d9bdd56c6b
Bump to 1.19.4 and add "Hang" crash
ci/woodpecker/push/woodpecker Pipeline failed Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-03-14 22:31:09 +01:00
Johannes Frohnmeyer 2a20216d32
Additional crash providers: OOM, Segfault and StackOverflow
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-03-11 12:13:47 +01:00
21 changed files with 286 additions and 107 deletions

View File

@ -1,8 +1,8 @@
#include https://pages.frohnmeyer-wds.de/scripts/clone.yml
pipeline:
steps:
build_test:
image: gradle:alpine
image: gradle:latest
pull: true
commands:
- mkdir artifacts
@ -16,6 +16,7 @@ pipeline:
secrets: [ maven_token, maven_name, modrinth_api_token, curseforge_api_token ]
artifacts:
image: woodpeckerci/plugin-s3
pull: true
settings:
bucket: pages
region: nebula

View File

@ -17,13 +17,17 @@ The values are explained in more detail below
- Method:
- Unsafe_Universal_Forkbomb: Launch a self-multiplying process
- Unsafe_Windows_WinAPI: Do some JNI-magic to instantly produce a blue-screen on windows
- Unsafe_Universal_OOM: Causes an OOM exception
- Broken_Universal_ExitCode: Exit the integrated Server. The game still displays, but you can no longer interact with the world or quit.
- Safe_Universal_Hang: Hang both the client and integrated server thread.
- Safe_Universal_Exception: Performs an invalid operation. Behaves like every other crash and just closes the game, leaving a crash log.
- SemiUnsafe_Universal_Exception: Throws a security exceptions. This does not work properly for 1.16.2
- SemiUnsafe_Universal_Shutdown: Attempts to run a shutdown command. Since these are specific to some systems this might not always work.
- SemiUnsafe_Universal_Segfault: Causes a segmentation fault using lwjgl.
- SemiUnsafe_Universal_StackOverflow: Causes a stack overflow via infinite recursion.
- None: Do nothing
Please note that all methods marked "Unsafe" as well as the shutdown method are not available in the curseforge release
Please note that all methods marked "Unsafe" as well as the shutdown method are not available in the CurseForge release
<!-- modrinth_exclude.start -->
# Building

View File

@ -1,25 +1,55 @@
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeSpec
import io.gitlab.jfronny.scripts.*
import java.nio.file.Files
import java.util.*
import java.util.function.Supplier
import javax.lang.model.element.Modifier.*
plugins {
id("jfmod") version "1.3-SNAPSHOT"
id("jf.codegen") version "1.3-SNAPSHOT"
id("jfmod") version "1.6-SNAPSHOT"
}
allprojects { group = "io.gitlab.jfronny" }
base.archivesName = "breakme"
// https://fabricmc.net/develop/
jfMod {
minecraftVersion = "1.20.5"
yarn("build.1")
loaderVersion = "0.15.10"
libJfVersion = "3.15.5"
fabricApiVersion = "0.97.6+1.20.5"
modrinth {
projectId = "breakme"
requiredDependencies.add("libjf")
optionalDependencies.add("modmenu")
}
curseforge {
projectId = "400842"
requiredDependencies.add("libjf")
optionalDependencies.add("modmenu")
}
}
if (flavour == "") flavour = "modrinth"
dependencies {
modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v1:${prop("libjf_version")}")
modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v2")
// Dev env
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil:${prop("libjf_version")}")
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-config-ui-tiny-v1:${prop("libjf_version")}")
modLocalRuntime("net.fabricmc.fabric-api:fabric-api:${prop("fabric_version")}")
modLocalRuntime("com.terraformersmc:modmenu:5.0.2")
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil")
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-config-ui-tiny")
modLocalRuntime("net.fabricmc.fabric-api:fabric-api")
modLocalRuntime("com.terraformersmc:modmenu:10.0.0-beta.1")
}
loom {
runs {
this.named("client").get().vmArg("-Xmx2G")
}
}
fun list(`package`: String) = Files.list(projectDir.resolve("src/main/java").resolve(`package`.replace('.', '/')).toPath()).use { stream ->
@ -35,7 +65,6 @@ val classes = LinkedList(list("io.gitlab.jfronny.breakme.crash.safe"))
if (flavour == "curseforge") {
sourceSets.main.get().java.filter.exclude("**/unsafe/*")
sourceSets.main.get().resources.exclude("**/native/*")
} else {
classes.addAll(list("io.gitlab.jfronny.breakme.crash.unsafe"))
}
@ -44,6 +73,7 @@ sourceSets {
main {
generate(project) {
val providerType = ClassName.get("io.gitlab.jfronny.breakme.crash", "CrashProvider")
val supplierType = ParameterizedTypeName.get(ClassName.get(Supplier::class.java), providerType)
`enum`("io.gitlab.jfronny.breakme.crash", "Method") {
modifiers(PUBLIC)
@ -52,15 +82,15 @@ sourceSets {
for (klazz in classes) {
val name = klazz.simpleName()
enumConstant(name.substring(0, name.length - "Provider".length), TypeSpec.anonymousClassBuilder("new \$T()", klazz).build())
enumConstant(name.substring(0, name.length - "Provider".length), TypeSpec.anonymousClassBuilder("\$T::new", klazz).build())
}
field(providerType, "provider", PRIVATE, FINAL)
field(supplierType, "provider", PRIVATE, FINAL)
constructor {
parameter(providerType, "provider")
parameter(supplierType, "provider")
code {
addStatement("this.provider = provider")
addStatement("this.provider = new \$T<>(provider)", ClassName.get("io.gitlab.jfronny.commons", "LazySupplier"))
}
}
@ -69,7 +99,7 @@ sourceSets {
exception(Exception::class.java)
annotation(Override::class.java)
code {
addStatement("provider.crash()")
addStatement("provider.get().crash()")
}
}
}

View File

@ -1,17 +0,0 @@
# https://fabricmc.net/develop/
minecraft_version=1.19.3
yarn_mappings=build.5
loader_version=0.14.12
maven_group=io.gitlab.jfronny
archives_base_name=breakme
modrinth_id=breakme
modrinth_required_dependencies=libjf
modrinth_optional_dependencies=modmenu
curseforge_id=400842
curseforge_required_dependencies=libjf
curseforge_optional_dependencies=modmenu
fabric_version=0.70.0+1.19.3
libjf_version=3.4.1

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.breakme.client;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.thread.ThreadExecutor;
import java.util.Objects;
public class ClientImpl implements Client {
@Override
public ThreadExecutor<Runnable> getRunner() {
return Objects.requireNonNull(MinecraftClient.getInstance());
}
@Override
public boolean isValidPlayer(PlayerEntity player) {
MinecraftClient client = MinecraftClient.getInstance();
if (client == null || client.player == null) return false;
return client.player.getUuid().equals(player.getUuid());
}
}

View File

@ -1,13 +0,0 @@
@echo off
echo Setting up native env
cd "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build"
call vcvarsall x86_x64
cd %~dp0
echo Building natives
cl .\io_gitlab_jfronny_breakme_crash_unsafe_WinApiProvider.cpp /I"jdk\include" /I"jdk\include\win32" -Fenatives.dll -MD -LD
echo Running post-build steps
del .\natives.exp
del .\natives.lib
del .\io_gitlab_jfronny_breakme_breakme_NativeCrash.obj
del ..\resources\native\natives.dll
echo Natives complete

View File

@ -1,18 +0,0 @@
#include <iostream>
#include <Windows.h>
#include <winternl.h>
#include "io_gitlab_jfronny_breakme_crash_unsafe_WinApiProvider.h"
using namespace std;
typedef NTSTATUS(NTAPI *pdef_NtRaiseHardError)(NTSTATUS ErrorStatus, ULONG NumberOfParameters, ULONG UnicodeStringParameterMask OPTIONAL, PULONG_PTR Parameters, ULONG ResponseOption, PULONG Response);
typedef NTSTATUS(NTAPI *pdef_RtlAdjustPrivilege)(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);
JNIEXPORT void JNICALL Java_io_gitlab_jfronny_breakme_crash_unsafe_WinApiProvider_CrashWindows_1Native(JNIEnv* env, jobject thisObject)
{
BOOLEAN bEnabled;
ULONG uResp;
LPVOID lpFuncAddress = GetProcAddress(LoadLibraryA("ntdll.dll"), "RtlAdjustPrivilege");
LPVOID lpFuncAddress2 = GetProcAddress(GetModuleHandle("ntdll.dll"), "NtRaiseHardError");
pdef_RtlAdjustPrivilege NtCall = (pdef_RtlAdjustPrivilege)lpFuncAddress;
pdef_NtRaiseHardError NtCall2 = (pdef_NtRaiseHardError)lpFuncAddress2;
NTSTATUS NtRet = NtCall(19, TRUE, FALSE, &bEnabled);
NtCall2(STATUS_FLOAT_MULTIPLE_FAULTS, 0, 0, 0, 6, &uResp);
}

View File

@ -1,21 +0,0 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class io_gitlab_jfronny_breakme_crash_unsafe_WinApiProvider */
#ifndef _Included_io_gitlab_jfronny_breakme_crash_unsafe_WinApiProvider
#define _Included_io_gitlab_jfronny_breakme_crash_unsafe_WinApiProvider
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: io_gitlab_jfronny_breakme_crash_unsafe_WinApiProvider
* Method: CrashWindows_Native
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_io_gitlab_jfronny_breakme_crash_unsafe_WinApiProvider_CrashWindows_1Native
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,24 +1,47 @@
package io.gitlab.jfronny.breakme;
import io.gitlab.jfronny.commons.log.Logger;
import io.gitlab.jfronny.breakme.client.Client;
import io.gitlab.jfronny.breakme.crash.Method;
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.Language;
public class BreakMe implements ModInitializer {
public static final String MOD_ID = "breakme";
public static final Logger LOGGER = Logger.forName(MOD_ID);
public static final SystemLoggerPlus LOGGER = SystemLoggerPlus.forName(MOD_ID);
@Override
public void onInitialize() {
LOGGER.warn("Prepare for trouble");
}
public static void tryInvokeCrash(PlayerEntity player) throws Exception {
if (BreakMeConfig.event == BreakMeConfig.Cause.All
|| BreakMeConfig.event == BreakMeConfig.Cause.Damage
|| (BreakMeConfig.event == BreakMeConfig.Cause.Death && player.isDead())) {
LOGGER.info("Invoking the crash");
BreakMeConfig.method.crash();
public static void tryInvokeCrash(BreakMeConfig.Cause cause) throws Exception {
if (BreakMeConfig.event.includes(cause)) {
crash();
}
}
public static BreakMeConfig.Cause resolveEvent(PlayerEntity player) {
if (!isValidPlayer(player)) return BreakMeConfig.Cause.None;
else if (player.isDead()) return BreakMeConfig.Cause.Death;
else return BreakMeConfig.Cause.Damage;
}
public static boolean isValidPlayer(PlayerEntity player) {
if (player == null) return false;
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
return Client.INSTANCE.isValidPlayer(player);
}
return true;
}
private static void crash() throws Exception {
Method method = BreakMeConfig.method;
String name = Language.getInstance().get(MOD_ID + ".jfconfig.enum.Method." + method.name(), method.name());
LOGGER.info("Invoking the crash (using {0})", name);
method.crash();
}
}

View File

@ -1,19 +1,24 @@
package io.gitlab.jfronny.breakme;
import io.gitlab.jfronny.breakme.crash.Method;
import io.gitlab.jfronny.libjf.config.api.v1.*;
import io.gitlab.jfronny.libjf.config.api.v2.*;
@JfConfig
public class BreakMeConfig {
@Entry public static Cause event = Cause.Death;
@Entry public static Method method = Method.Exception;
@Entry public static Method method = Method.Segfault;
public enum Cause {
Damage,
Death,
All,
None
None;
public boolean includes(Cause cause) {
if (cause == null || cause == None) return false;
return this == All || this == cause;
}
}
@Verifier
@ -24,6 +29,14 @@ public class BreakMeConfig {
}
}
@Verifier
public static void validEvent() {
if (BreakMeConfig.event == null) {
BreakMeConfig.event = Cause.None;
BreakMe.LOGGER.error("Could not find specified event, defaulting to None");
}
}
static {
JFC_BreakMeConfig.ensureInitialized();
}

View File

@ -0,0 +1,12 @@
package io.gitlab.jfronny.breakme.client;
import io.gitlab.jfronny.commons.throwable.Try;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.thread.ThreadExecutor;
public interface Client {
Client INSTANCE = (Client) Try.orThrow(() -> Class.forName(Client.class.getName() + "Impl").getConstructor().newInstance());
ThreadExecutor<Runnable> getRunner();
boolean isValidPlayer(PlayerEntity player);
}

View File

@ -0,0 +1,25 @@
package io.gitlab.jfronny.breakme.crash.safe;
import io.gitlab.jfronny.breakme.client.Client;
import io.gitlab.jfronny.breakme.crash.CrashProvider;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
public class HangProvider implements CrashProvider {
@Override
public void crash() throws Exception {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
Client.INSTANCE.getRunner().send(this::hang);
}
hang();
}
private void hang() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
}

View File

@ -0,0 +1,19 @@
package io.gitlab.jfronny.breakme.crash.safe;
import io.gitlab.jfronny.breakme.crash.CrashProvider;
import org.lwjgl.BufferUtils;
import org.lwjgl.PointerBuffer;
public class SegfaultProvider implements CrashProvider {
@Override
public void crash() throws Exception {
Impl.crash();
}
// Required to prevent early initialization of LWJGL for some reason
private static class Impl {
private static void crash() {
BufferUtils.zeroBuffer(PointerBuffer.create(1, 1000));
}
}
}

View File

@ -0,0 +1,10 @@
package io.gitlab.jfronny.breakme.crash.safe;
import io.gitlab.jfronny.breakme.crash.CrashProvider;
public class StackOverflowProvider implements CrashProvider {
@Override
public void crash() throws Exception {
crash();
}
}

View File

@ -0,0 +1,15 @@
package io.gitlab.jfronny.breakme.crash.unsafe;
import io.gitlab.jfronny.breakme.crash.CrashProvider;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
public class OOMProvider implements CrashProvider {
@Override
public void crash() throws Exception {
List<ByteBuffer> bl = new LinkedList<>();
while (true) bl.add(ByteBuffer.allocate(1024 * 1024 * 1024));
}
}

View File

@ -1,16 +1,19 @@
package io.gitlab.jfronny.breakme.crash.unsafe;
import com.sun.jna.*;
import com.sun.jna.platform.win32.*;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIOptions;
import io.gitlab.jfronny.breakme.BreakMe;
import io.gitlab.jfronny.breakme.crash.CrashProvider;
import java.io.IOException;
import java.util.function.Supplier;
//import java.lang.foreign.*;
//import java.lang.invoke.MethodHandle;
//
//import static java.lang.foreign.ValueLayout.*;
public class WinApiProvider implements CrashProvider {
private native void CrashWindows_Native();
@Override
public void crash() {
// try {
@ -77,12 +80,49 @@ public class WinApiProvider implements CrashProvider {
// return;
// }
try {
// Old implementation using DLL
NativeUtils.loadLibraryFromJar("/native/natives.dll");
this.CrashWindows_Native();
} catch (IOException e) {
BreakMe.LOGGER.error("Could not load windows native", e);
// Old implementation using JNA
NtDll ntdll = Native.load("NtDll", NtDll.class, W32APIOptions.DEFAULT_OPTIONS);
int status = ntdll.RtlAdjustPrivilege(
new WinDef.ULONG(19),
true,
false,
new WinDef.BOOLByReference()
);
if (status != NTStatus.STATUS_SUCCESS) {
BreakMe.LOGGER.error("Got status: " + String.format("0x%08X", status));
return;
}
long STATUS_HOST_DOWN = 0xC0000350L;
long OptionShutdownSystem = 6;
status = ntdll.NtRaiseHardError(
new WinDef.ULONG(STATUS_HOST_DOWN),
null,
null,
null,
new WinDef.ULONG(OptionShutdownSystem),
new WinDef.ULONGByReference()
);
if (status != NTStatus.STATUS_SUCCESS) {
BreakMe.LOGGER.error("Got status: " + String.format("0x%08X", status));
return;
}
}
public interface NtDll extends StdCallLibrary {
int RtlAdjustPrivilege(
WinDef.ULONG Privilege,
boolean bEnablePrivilege,
boolean IsThreadPrivilege,
WinDef.BOOLByReference PreviousValue
);
int NtRaiseHardError(
WinDef.ULONG ErrorStatus,
WinDef.ULONG NumberOfParameters,
WinDef.ULONG UnicodeStringParameterMask,
WinDef.ULONGByReference Parameters,
WinDef.ULONG ValidResponseOption,
WinDef.ULONGByReference Response
);
}
}

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.breakme.mixin;
import io.gitlab.jfronny.breakme.BreakMe;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.player.PlayerEntity;
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(LivingEntity.class)
public class LivingEntityMixin {
// client-side damage event when playing on a server
@Inject(at = @At("TAIL"), method = "onDamaged(Lnet/minecraft/entity/damage/DamageSource;)V")
private void onDamage(DamageSource damageSource, CallbackInfo ci) throws Exception {
if (((LivingEntity)(Object)this) instanceof PlayerEntity player) {
BreakMe.tryInvokeCrash(BreakMe.resolveEvent(player));
}
}
}

View File

@ -1,19 +1,28 @@
package io.gitlab.jfronny.breakme.mixin;
import io.gitlab.jfronny.breakme.BreakMe;
import io.gitlab.jfronny.breakme.BreakMeConfig;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.player.PlayerEntity;
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;
@Mixin(PlayerEntity.class)
public class MixinPlayerEntity {
public class PlayerEntityMixin {
@Inject(at = @At("TAIL"), method = "damage(Lnet/minecraft/entity/damage/DamageSource;F)Z")
private void onDamage(DamageSource source, float amount, CallbackInfoReturnable<Boolean> info) throws Exception {
if (info.getReturnValue()) {
BreakMe.tryInvokeCrash((PlayerEntity)(Object)this);
BreakMe.tryInvokeCrash(BreakMe.resolveEvent((PlayerEntity)(Object)this));
}
}
@Inject(at = @At("TAIL"), method = "onDeath(Lnet/minecraft/entity/damage/DamageSource;)V")
private void onDeath(DamageSource damageSource, CallbackInfo ci) throws Exception {
if (BreakMe.isValidPlayer((PlayerEntity)(Object)this)) {
BreakMe.tryInvokeCrash(BreakMeConfig.Cause.Death);
}
}
}

View File

@ -10,9 +10,13 @@
"breakme.jfconfig.enum.Method.tooltip": "The method used to perform the crash",
"breakme.jfconfig.enum.Method.Exception": "Safe_Universal_Exception",
"breakme.jfconfig.enum.Method.ExitCode": "Broken_Universal_ExitCode",
"breakme.jfconfig.enum.Method.Hang": "Safe_Universal_Hang",
"breakme.jfconfig.enum.Method.SecurityException": "SemiUnsafe_Universal_Exception",
"breakme.jfconfig.enum.Method.Segfault": "SemiUnsafe_Universal_Segfault",
"breakme.jfconfig.enum.Method.StackOverflow": "SemiUnsafe_Universal_StackOverflow",
"breakme.jfconfig.enum.Method.Forkbomb": "Unsafe_Universal_Forkbomb",
"breakme.jfconfig.enum.Method.Shutdown": "SemiUnsafe_Universal_Shutdown",
"breakme.jfconfig.enum.Method.OOM": "Unsafe_Universal_OOM",
"breakme.jfconfig.enum.Method.WinApi": "Unsafe_Windows_WinAPI",
"breakme.jfconfig.enum.Method.None": "None"
}

View File

@ -4,9 +4,10 @@
"package": "io.gitlab.jfronny.breakme.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"MixinPlayerEntity"
"PlayerEntityMixin"
],
"client": [
"LivingEntityMixin"
],
"injectors": {
"defaultRequire": 1