Compare commits

...

22 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
Johannes Frohnmeyer 21690627d4
Update natives for new paths
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/manual/woodpecker Pipeline failed Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-01-06 11:43:12 +01:00
Johannes Frohnmeyer 1cd569d200
Ensure initialized
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-12-29 15:59:00 +01:00
Johannes Frohnmeyer 2289839966
Use new config compiler and experiment with panama (untested and disabled)
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-12-29 15:51:08 +01:00
Johannes Frohnmeyer 41b9062065
Update to 1.19.3
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2022-12-07 22:30:46 +01:00
Johannes Frohnmeyer 875901d280
Tweak build
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-12-03 16:30:16 +01:00
Johannes Frohnmeyer dd47e1008e
Simplify build script
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-12-03 16:19:36 +01:00
Johannes Frohnmeyer d10d8e36cd
Attempt to fix
ci/woodpecker/push/woodpecker Pipeline was successful Details
2022-12-02 19:03:35 +01:00
Johannes Frohnmeyer b672e8f929
Migrate to new infrastructure
ci/woodpecker/manual/woodpecker Pipeline failed Details
2022-12-02 18:59:18 +01:00
Johannes Frohnmeyer ca2e166a9d
Migrate to LibJF 3 for config instead of cloth 2022-08-28 16:27:55 +02:00
Johannes Frohnmeyer 69b46c124e Attempt to fix CI pt.2 2022-07-28 16:08:04 +00:00
Johannes Frohnmeyer a82187b296 Attempt to fix CI 2022-07-28 15:53:48 +00:00
39 changed files with 513 additions and 414 deletions

3
.gitignore vendored
View File

@ -118,4 +118,5 @@ run/
!gradle-wrapper.jar
# No temporary natives!
src/main/c/natives.dll
src/main/c/natives.dll
src/main/java/io/gitlab/jfronny/breakme/crash/KnownProviders.java

View File

@ -1,58 +0,0 @@
stages:
- compile_native
- build
- deploy
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.jvmargs=-Xmx2G"
build_natives:
tags:
- windows
stage: compile_native
script:
- .\src\main\c\build.bat
artifacts:
paths:
- src/main/resources/native/natives.dll
only:
- master
build_test:
image: gradle:jdk17
stage: deploy
script:
- gradle --build-cache deployDebug -Pmaven="$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/maven"
- mv build/libs/* ./
- mv build/devlibs/*-dev.jar ./
- rm *-maven.jar *-sources.jar
- mv *-dev.jar dev-free.zip
- mv *.jar latest.zip
- gradle --build-cache -Pflavor=curseforge build
- cp build/libs/* ./
- cp build/devlibs/*-dev.jar ./
- rm *-maven.jar *-sources.jar
- mv *-dev.jar dev.zip
- mv *.jar latest-cf.jar
- mv latest.zip latest.jar
- mv dev-free.zip latest-dev.jar
- mv dev.zip latest-cf-dev.jar
artifacts:
paths:
- latest.jar
- latest-cf.jar
- latest-dev.jar
- latest-cf-dev.jar
only:
- master
deploy:
image: gradle:jdk17
rules:
- if: $CI_COMMIT_TAG && '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^master/'
stage: deploy
script:
- gradle --build-cache build publish modrinth modrinthSyncBody -Prelease -Pmaven="$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/maven"
- rm build/libs/*
- rm build/devlibs/*
- gradle --build-cache -Pflavor=curseforge build curseforge -Prelease

32
.woodpecker.yml Normal file
View File

@ -0,0 +1,32 @@
#include https://pages.frohnmeyer-wds.de/scripts/clone.yml
steps:
build_test:
image: gradle:latest
pull: true
commands:
- mkdir artifacts
- if [ $CI_PIPELINE_EVENT = tag ]; then
- gradle --build-cache moveArtifacts curseforge -Pflavour=curseforge -Prelease
- gradle --build-cache moveArtifacts publish modrinth modrinthSyncBody -Prelease -Pmaven="https://maven.frohnmeyer-wds.de/artifacts"
- else
- gradle --build-cache moveArtifacts -Pflavour=curseforge
- gradle --build-cache moveArtifacts deployDebug -Pmaven="https://maven.frohnmeyer-wds.de/artifacts"
- fi
secrets: [ maven_token, maven_name, modrinth_api_token, curseforge_api_token ]
artifacts:
image: woodpeckerci/plugin-s3
pull: true
settings:
bucket: pages
region: nebula
path_style: true
endpoint: https://s3.frohnmeyer-wds.de
access_key: pages
secret_key:
from_secret: pages_secret
source: build/artifacts/**/*
strip_prefix: build/
target: /${CI_REPO}
when:
- branch: ${CI_REPO_DEFAULT_BRANCH}

View File

@ -17,18 +17,21 @@ 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
BreakMe consists of two parts: The main mod and the DLL used for Unsafe_Windows_WinAPI, which are built separately.\
If you don't build on windows you can use the dll contained in the latest [CI build](https://gitlab.com/jfmods/BreakMe/-/jobs/artifacts/master/browse/build/libs?job=build_test) and place it in src/main/resources/native.\
If you want to build the dll, use [this](https://gitlab.com/jfmods/BreakMe/-/blob/master/src/main/c/build.bat) as a reference (requires VS build tools).\
The mod itself is built like any other fabric mod: using gradle. Look at the [CI config](https://gitlab.com/jfmods/BreakMe/-/blob/master/.gitlab-ci.yml#L10) for an example\
A prebuilt DLL is included in the source tree and will be removed once it is replaced by a panama implementation.\
If you want to build it yourself, use src/main/c/build.bat to build it on a windows PC.\
The mod itself is built like any other fabric mod: using gradle
<!-- modrinth_exclude.end -->

View File

@ -1,21 +0,0 @@
apply from: "https://jfmods.gitlab.io/scripts/jfmod.gradle"
ext.flavor = project.hasProperty('flavor') ? project.getProperty('flavor') : 'modrinth'
repositories {
maven { url = "https://maven.shedaniel.me/"; name = "Cloth Config" }
}
dependencies {
modRuntimeOnly "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modApi("me.shedaniel.cloth:cloth-config-fabric:7.0.73") {
exclude(group: "net.fabricmc.fabric-api")
}
modImplementation "com.terraformersmc:modmenu:4.0.5"
}
if (flavor == "curseforge") {
sourceSets.main.java.filter.exclude("**/unsafe/*")
sourceSets.main.resources.exclude("**/native/*")
}

108
build.gradle.kts Normal file
View File

@ -0,0 +1,108 @@
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.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-v2")
// Dev env
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 ->
stream
.map { it.fileName.toString() }
.map { it.substring(0, it.lastIndexOf('.')) }
.filter { it.endsWith("Provider") }
.map { ClassName.get(`package`, it) }
.toList()
}
val classes = LinkedList(list("io.gitlab.jfronny.breakme.crash.safe"))
if (flavour == "curseforge") {
sourceSets.main.get().java.filter.exclude("**/unsafe/*")
} else {
classes.addAll(list("io.gitlab.jfronny.breakme.crash.unsafe"))
}
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)
superInterface(providerType)
for (klazz in classes) {
val name = klazz.simpleName()
enumConstant(name.substring(0, name.length - "Provider".length), TypeSpec.anonymousClassBuilder("\$T::new", klazz).build())
}
field(supplierType, "provider", PRIVATE, FINAL)
constructor {
parameter(supplierType, "provider")
code {
addStatement("this.provider = new \$T<>(provider)", ClassName.get("io.gitlab.jfronny.commons", "LazySupplier"))
}
}
method("crash") {
modifiers(PUBLIC)
exception(Exception::class.java)
annotation(Override::class.java)
code {
addStatement("provider.get().crash()")
}
}
}
}
}
}

View File

@ -1,13 +0,0 @@
# https://fabricmc.net/develop/
minecraft_version=1.19.1
yarn_mappings=build.1
loader_version=0.14.8
maven_group=io.gitlab.jfronny
archives_base_name=breakme
fabric_version=0.58.5+1.19.1
modrinth_id=breakme
modrinth_optional_dependencies=modmenu, cloth-config
curseforge_id=400842
curseforge_optional_dependencies=modmenu, cloth-config

9
settings.gradle.kts Normal file
View File

@ -0,0 +1,9 @@
pluginManagement {
repositories {
maven("https://maven.fabricmc.net/") // FabricMC
maven("https://maven.frohnmeyer-wds.de/artifacts") // scripts
gradlePluginPortal()
}
}
rootProject.name = "breakme"

View File

@ -1,13 +0,0 @@
package io.gitlab.jfronny.breakme;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import io.gitlab.jfronny.breakme.config.Cfg;
import me.shedaniel.autoconfig.AutoConfig;
public class ModMenuAPI implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return screen -> AutoConfig.getConfigScreen(Cfg.class, screen).get();
}
}

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,14 +0,0 @@
@echo off
echo Setting up native env
cd "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build"
call vcvarsall x86_x64
cd %~dp0
echo Building natives
cl .\io_gitlab_jfronny_breakme_breakme_NativeCrash.cpp /I"C:\Program Files\OpenJDK\jdk-12.0.2\include" /I"C:\Program Files\OpenJDK\jdk-12.0.2\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
copy /y natives.dll ..\resources\native\natives.dll
echo Natives complete

View File

@ -1,36 +0,0 @@
/*
#include <windows.h>
#include "io_gitlab_jfronny_breakme_breakme_NativeCrash.h"
#pragma comment(lib, "ntdll.lib")
extern "C" NTSTATUS NTAPI RtlAdjustPrivilege(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN OldValue);
extern "C" NTSTATUS NTAPI NtRaiseHardError(LONG ErrorStatus, ULONG NumberOfParameters, ULONG UnicodeStringParameterMask,
PULONG_PTR Parameters, ULONG ValidResponseOptions, PULONG Response);
JNIEXPORT void JNICALL Java_io_gitlab_jfronny_breakme_breakme_NativeCrash_CrashWindows_1Native(JNIEnv* env, jobject thisObject)
{
BOOLEAN bl;
ULONG Response;
RtlAdjustPrivilege(19, TRUE, FALSE, &bl); // Enable SeShutdownPrivilege
NtRaiseHardError(STATUS_ASSERTION_FAILURE, 0, 0, NULL, 6, &Response); // Shutdown
}
*/
#include <iostream>
#include <Windows.h>
#include <winternl.h>
#include "io_gitlab_jfronny_breakme_breakme_NativeCrash.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_breakme_NativeCrash_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_breakme_NativeCrash */
#ifndef _Included_io_gitlab_jfronny_breakme_breakme_NativeCrash
#define _Included_io_gitlab_jfronny_breakme_breakme_NativeCrash
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: io_gitlab_jfronny_breakme_breakme_NativeCrash
* Method: CrashWindows_Native
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_io_gitlab_jfronny_breakme_breakme_NativeCrash_CrashWindows_1Native
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,71 +1,47 @@
package io.gitlab.jfronny.breakme;
import io.gitlab.jfronny.breakme.config.Cfg;
import io.gitlab.jfronny.breakme.config.CrashCause;
import io.gitlab.jfronny.breakme.crash.CrashProvider;
import me.shedaniel.autoconfig.AutoConfig;
import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.util.Language;
public class BreakMe implements ModInitializer {
public static final String MOD_ID = "breakme";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
public static final Cfg cfg;
private static final Map<String, CrashProvider> crashProviders;
static {
//Get config
AutoConfig.register(Cfg.class, JanksonConfigSerializer::new);
cfg = AutoConfig.getConfigHolder(Cfg.class).getConfig();
//Get crash providers
crashProviders = new HashMap<>();
try {
List<Class<?>> classes = ClassFinder.find(CrashProvider.class.getPackage().getName());
for (Class<?> clazz : classes) {
if (CrashProvider.class.isAssignableFrom(clazz) && !CrashProvider.class.equals(clazz)) {
try {
CrashProvider provider = (CrashProvider) clazz.getDeclaredConstructor().newInstance();
crashProviders.put(provider.getName(), provider);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
LOGGER.error("Could not initialize crash provider", e);
}
}
}
if (!crashProviders.containsKey(cfg.method)) {
cfg.method = "None";
LOGGER.warn("Could not find specified crash provider, defaulting to None");
}
} catch (IOException e) {
LOGGER.error("Could not load crash providers", e);
}
}
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 (cfg.event == CrashCause.All || cfg.event == CrashCause.Damage || (cfg.event == CrashCause.Death && player.isDead())) {
LOGGER.info("Invoking the crash");
if (!crashProviders.containsKey(cfg.method)) {
cfg.method = "None";
LOGGER.error("Could not find specified crash provider, defaulting to None");
}
crashProviders.get(cfg.method).crash();
public static void tryInvokeCrash(BreakMeConfig.Cause cause) throws Exception {
if (BreakMeConfig.event.includes(cause)) {
crash();
}
}
public static String[] getProviders() {
return crashProviders.keySet().toArray(new String[0]);
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,40 +0,0 @@
package io.gitlab.jfronny.breakme;
import io.gitlab.jfronny.breakme.config.Cfg;
import io.gitlab.jfronny.breakme.config.CrashMethod;
import me.shedaniel.autoconfig.AutoConfig;
import me.shedaniel.autoconfig.gui.registry.GuiRegistry;
import me.shedaniel.autoconfig.gui.registry.api.GuiRegistryAccess;
import me.shedaniel.clothconfig2.api.AbstractConfigListEntry;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import net.fabricmc.api.ClientModInitializer;
import net.minecraft.text.*;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import static me.shedaniel.autoconfig.util.Utils.getUnsafely;
import static me.shedaniel.autoconfig.util.Utils.setUnsafely;
public class BreakMeClient implements ClientModInitializer {
ConfigEntryBuilder builder = ConfigEntryBuilder.create();
@Override
public void onInitializeClient() {
GuiRegistry registry = AutoConfig.getGuiRegistry(Cfg.class);
registry.registerAnnotationProvider(this::get, CrashMethod.class);
registry.registerTypeProvider(this::get, String.class);
}
private List<AbstractConfigListEntry> get(String i13n, Field field, Object config, Object defaults, GuiRegistryAccess guiProvider) {
return Collections.singletonList(
builder.startSelector(
Text.translatable(i13n),
BreakMe.getProviders(),
getUnsafely(field, config, getUnsafely(field, defaults))
).setDefaultValue(() -> "None")
.setSaveConsumer(newValue -> setUnsafely(field, config, newValue))
.build()
);
}
}

View File

@ -0,0 +1,43 @@
package io.gitlab.jfronny.breakme;
import io.gitlab.jfronny.breakme.crash.Method;
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.Segfault;
public enum Cause {
Damage,
Death,
All,
None;
public boolean includes(Cause cause) {
if (cause == null || cause == None) return false;
return this == All || this == cause;
}
}
@Verifier
public static void validProvider() {
if (BreakMeConfig.method == null) {
BreakMeConfig.method = Method.None;
BreakMe.LOGGER.error("Could not find specified crash provider, defaulting to None");
}
}
@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

@ -1,44 +0,0 @@
package io.gitlab.jfronny.breakme;
import net.fabricmc.loader.api.FabricLoader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Stream;
public class ClassFinder {
public static List<Class<?>> find(String packageName) throws NoSuchElementException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Path p = FabricLoader.getInstance()
.getModContainer(BreakMe.MOD_ID)
.orElseThrow(FileNotFoundException::new)
.findPath(".")
.orElseThrow(FileNotFoundException::new)
.toAbsolutePath();
return findInternal(p, p.resolve(packageName.replace('.', '/')), classLoader);
}
private static List<Class<?>> findInternal(Path rootPath, Path path, ClassLoader classLoader) throws IOException {
List<Class<?>> result = new ArrayList<>();
try (Stream<Path> files = Files.list(path)) {
files.forEach(s -> {
try {
if (Files.isDirectory(s)) {
result.addAll(findInternal(rootPath, s, classLoader));
} else if (s.getFileName().toString().endsWith(".class")) {
String p = rootPath.relativize(s).toString().replace('/', '.');
result.add(classLoader.loadClass(p.substring(0, p.length() - ".class".length())));
}
} catch (Throwable e) {
BreakMe.LOGGER.error("Could not scan classpath for crash method", e);
}
});
}
return result;
}
}

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

@ -1,17 +0,0 @@
package io.gitlab.jfronny.breakme.config;
import io.gitlab.jfronny.breakme.BreakMe;
import me.shedaniel.autoconfig.ConfigData;
import me.shedaniel.autoconfig.annotation.Config;
import me.shedaniel.autoconfig.annotation.ConfigEntry.Gui.EnumHandler;
import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment;
@Config(name = BreakMe.MOD_ID)
public class Cfg implements ConfigData {
@Comment("What should cause a crash")
@EnumHandler(option = EnumHandler.EnumDisplayOption.BUTTON)
public CrashCause event = CrashCause.Death;
@Comment("The method used to perform the crash")
@CrashMethod
public String method = "Safe_Universal_Exception";
}

View File

@ -1,9 +0,0 @@
package io.gitlab.jfronny.breakme.config;
public enum CrashCause {
Damage,
Death,
All,
None
}

View File

@ -1,4 +0,0 @@
package io.gitlab.jfronny.breakme.config;
public @interface CrashMethod {
}

View File

@ -2,5 +2,4 @@ package io.gitlab.jfronny.breakme.crash;
public interface CrashProvider {
void crash() throws Exception;
String getName();
}

View File

@ -7,9 +7,4 @@ public class ExceptionProvider implements CrashProvider {
public void crash() throws Exception {
throw new Exception("You did bad, now die");
}
@Override
public String getName() {
return "Safe_Universal_Exception";
}
}

View File

@ -7,9 +7,4 @@ public class ExitCodeProvider implements CrashProvider {
public void crash() {
System.exit(-1);
}
@Override
public String getName() {
return "Broken_Universal_ExitCode";
}
}

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

@ -1,13 +1,10 @@
package io.gitlab.jfronny.breakme.crash;
package io.gitlab.jfronny.breakme.crash.safe;
import io.gitlab.jfronny.breakme.crash.CrashProvider;
public class NoneProvider implements CrashProvider {
@Override
public void crash() throws Exception {
}
@Override
public String getName() {
return "None";
}
}

View File

@ -7,9 +7,4 @@ public class SecurityExceptionProvider implements CrashProvider {
public void crash() throws Exception {
throw new SecurityException("You did bad, now die");
}
@Override
public String getName() {
return "SemiUnsafe_Universal_Exception";
}
}

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

@ -7,9 +7,4 @@ public class ForkbombProvider implements CrashProvider {
public void crash() {
forkbomb.main(new String[0]);
}
@Override
public String getName() {
return "Unsafe_Universal_Forkbomb";
}
}

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

@ -19,9 +19,4 @@ public class ShutdownProvider implements CrashProvider {
runtime.exec("shutdown 0");
}
}
@Override
public String getName() {
return "SemiUnsafe_Universal_Shutdown";
}
}

View File

@ -1,24 +1,128 @@
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 {
NativeUtils.loadLibraryFromJar("/native/natives.dll");
new WinApiProvider().CrashWindows_Native();
} catch (IOException e) {
BreakMe.LOGGER.error("Could not load windows native", e);
// try {
// // Alternate Panama-based implementation
// // To be tested and enabled once panama is out of preview
// System.loadLibrary("ntdll");
//
// // Anonymous class with utility methods
// var n = new Object() {
// private final Linker linker = Linker.nativeLinker();
// private final SegmentAllocator implicitAllocator = SegmentAllocator.implicitAllocator();
// private final SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
// private final SymbolLookup symbolLookup = name -> loaderLookup.lookup(name).or(() -> linker.defaultLookup().lookup(name));
//
// MethodHandle downcallHandle(String name, FunctionDescriptor fdesc) {
// return symbolLookup.lookup(name).
// map(addr -> linker.downcallHandle(addr, fdesc)).
// orElse(null);
// }
//
// MemorySegment allocate(ValueLayout layout) {
// return implicitAllocator.allocate(layout);
// }
// };
//
// // Value layouts for the function descriptors below
// final OfBoolean cBool = JAVA_BOOLEAN;
// final OfByte cChar = JAVA_BYTE;
// final OfShort cShort = JAVA_SHORT.withBitAlignment(16);
// final OfInt cInt = JAVA_INT.withBitAlignment(32);
// final OfInt cLong = JAVA_INT.withBitAlignment(32);
// final OfLong cLongLong = JAVA_LONG.withBitAlignment(64);
// final OfFloat cFloat = JAVA_FLOAT.withBitAlignment(32);
// final OfDouble cDouble = JAVA_DOUBLE.withBitAlignment(64);
// final OfAddress cPointer = ADDRESS.withBitAlignment(64);
//
// // Function definitions for rtlAdjustPrivilege and ntRaiseHardError
// // IntPtr RtlAdjustPrivilege(int Privilege, bool bEnablePrivilege, bool IsThreadPrivilege, out bool PreviousValue);
// // typedef NTSTATUS(NTAPI *pdef_RtlAdjustPrivilege)(ULONG Privilege, BOOLEAN Enable, BOOLEAN CurrentThread, PBOOLEAN Enabled);
// final FunctionDescriptor rtlAdjustPrivilege$fd = FunctionDescriptor.of(cInt, cInt, cBool, cPointer);
// final MethodHandle rtlAdjustPrivilege = n.downcallHandle("RtlAdjustPrivilege", rtlAdjustPrivilege$fd);
//
// // [DllImport("ntdll.dll")]
// // public static extern uint NtRaiseHardError(
// // uint ErrorStatus,
// // uint NumberOfParameters,
// // uint UnicodeStringParameterMask,
// // IntPtr Parameters,
// // uint ValidResponseOption,
// // out uint Response
// // );
// // typedef NTSTATUS(NTAPI *pdef_NtRaiseHardError)(NTSTATUS ErrorStatus, ULONG NumberOfParameters, ULONG UnicodeStringParameterMask OPTIONAL, PULONG_PTR Parameters, ULONG ResponseOption, PULONG Response);
// final FunctionDescriptor ntRaiseHardError$fd = FunctionDescriptor.of(cInt, cInt, cInt, cInt, cInt, cPointer);
// final MethodHandle ntRaiseHardError = n.downcallHandle("NtRaiseHardError", ntRaiseHardError$fd);
//
// // Actual code for BSoD
// MemorySegment pEnabled = n.allocate(cBool);
// rtlAdjustPrivilege.invokeExact(19, true, false, pEnabled);
//
// MemorySegment pResponse = n.allocate(cInt);
// ntRaiseHardError.invokeExact(0xc0000022, 0, 0, 0, 6, pResponse);
// } catch (Throwable e) {
// BreakMe.LOGGER.error("Could not create BSoD", e);
// return;
// }
// 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;
}
}
@Override
public String getName() {
return "Unsafe_Windows_WinAPI";
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

@ -1,5 +1,22 @@
{
"text.autoconfig.breakme.title": "BreakMe",
"text.autoconfig.breakme.option.event": "Event",
"text.autoconfig.breakme.option.method": "Method"
"breakme.jfconfig.title": "BreakMe",
"breakme.jfconfig.event": "Event",
"breakme.jfconfig.event.tooltip": "What should cause a crash",
"breakme.jfconfig.enum.Cause.Damage": "Damage",
"breakme.jfconfig.enum.Cause.Death": "Death",
"breakme.jfconfig.enum.Cause.All": "Damage or Death",
"breakme.jfconfig.enum.Cause.None": "None",
"breakme.jfconfig.method": "Method",
"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

View File

@ -1,29 +1,22 @@
{
"schemaVersion": 1,
"id": "breakme",
"version": "${version}",
"name": "BreakMe",
"version": "${version}",
"description": "Crashes if you take damage",
"authors": [
"JFronny"
],
"authors": ["JFronny"],
"contact": {
"website": "jfronny.gitlab.io",
"repo": "https://gitlab.com/jfmods/BreakMe"
"email": "projects.contact@frohnmeyer-wds.de",
"homepage": "https://jfronny.gitlab.io",
"issues": "https://git.frohnmeyer-wds.de/JfMods/BreakMe/issues",
"sources": "https://git.frohnmeyer-wds.de/JfMods/BreakMe"
},
"license": "MIT",
"icon": "assets/breakme/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"io.gitlab.jfronny.breakme.BreakMe"
],
"client": [
"io.gitlab.jfronny.breakme.BreakMeClient"
],
"modmenu": [
"io.gitlab.jfronny.breakme.ModMenuAPI"
]
"main": ["io.gitlab.jfronny.breakme.BreakMe"],
"libjf:config": ["io.gitlab.jfronny.breakme.JFC_BreakMeConfig"]
},
"mixins": [
"breakme.mixins.json"
@ -31,6 +24,6 @@
"depends": {
"fabricloader": ">=0.12.0",
"minecraft": "*",
"cloth-config": "*"
"libjf-config-core-v1": "*"
}
}

View File

@ -1 +0,0 @@
natives.dll