Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,7 @@ public class BrandingPatchImpl implements ClientModInitializer {

public static String getGameVersion() {
if (gameVersion == null) {
gameVersion = MinecraftVersion.resolve().gameVersion();

if (gameVersion.charAt(0) == 'a') {
gameVersion = "Alpha v" + gameVersion.substring(1);
} else if (gameVersion.startsWith("inf")) {
gameVersion = "Infdev 0.31." + gameVersion.substring(4);
} else if (gameVersion.startsWith("in-")) {
gameVersion = "Indev 0.31." + gameVersion.substring(3);
}
gameVersion = MinecraftVersion.resolve().friendlyGameVersion();
}

return gameVersion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public static MinecraftVersion resolve() {
// semantic versions for other game versions
private final Map<String, SemanticVersion> semanticVersions = new HashMap<>();

private String friendlyGameVersion;

private MinecraftVersion(String gameVersion, SemanticVersion semanticVersion) {
this.gameVersion = gameVersion;
this.semanticVersion = semanticVersion;
Expand All @@ -48,6 +50,39 @@ public String gameVersion() {
return this.gameVersion;
}

public String friendlyGameVersion() {
if (this.friendlyGameVersion == null) {
String friendlyVersion = this.gameVersion;

int i = friendlyVersion.indexOf('+');
if (i > 0) {
friendlyVersion = friendlyVersion.substring(i);
}

char chr = friendlyVersion.charAt(0);

if (chr == 'b') {
friendlyVersion = "Beta " + friendlyVersion.substring(1);
} else if (chr == 'a') {
friendlyVersion = "Alpha v" + friendlyVersion.substring(1);
} else if (chr == 'i') {
if (friendlyVersion.charAt(2) == 'f') {
friendlyVersion = "Infdev 0.31." + friendlyVersion.substring(4);
} else {
friendlyVersion = "Indev 0.31." + friendlyVersion.substring(3);
}
} else if (chr == 'c') {
friendlyVersion = "Classic " + friendlyVersion.substring(1);
} else if (chr == 'r' || chr == 'p') {
friendlyVersion = "Pre-Classic " + friendlyVersion.substring(3);
}

this.friendlyGameVersion = friendlyVersion;
}

return this.friendlyGameVersion;
}

public String semanticVersion() {
return this.semanticVersion.toString();
}
Expand Down
41 changes: 41 additions & 0 deletions libraries/crash-reports/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Crash Reports API

The Crash Reports API adds general mod information to crash reports and adds a convenient version-independent way for mods to throw crash exceptions and add details to crash reports.

## Throwing Crash Exceptions

The `CrashExceptions` class provides a utility method for constructing crash exceptions with a given error and title. You may also pass in a crash report builder in which you can add details to the crash report.

```java
try {
cookieMonster.eatCookie(cookie);
} catch (Throwable error) {
throw CrashExceptions.from("Cookie monster encountered an error while eating a cookie", error, report -> {
CrashReportSection section = report.addSection("Cookie Monster");

section.addDetail("Name", () -> cookieMonster.getName());
section.addDetail("Cookie Recipe", () -> cookie.getRecipe());
})
}

```

## Populating Crash Reports

It is possible to add general information to the system report and level details sections of a crash report. This is done by registering a crash listener to one of several classes that implement the `CrashReportPopulator` interface (`Minecraft`, `MinecraftServer`, `World`, and `WorldData`). In a crash listener you can add details to the given crash report section or add a new section to the crash report.

```java
MinecraftClientEvents.START.register(minecraft -> {
World world = minecraft.world;

minecraft.addCrashListener((report, section) -> {
CrashReportSection section = report.addSection("Cookie Management");

section.addDetail("Cookies made", "Not enough!");
section.addDetail("Cookies eaten", "All of them!");
});
world.addCrashListener((report, section) -> {
section.addDetail("Cookie monsters", () -> world.cookieMonsters.size());
});
});
```
6 changes: 6 additions & 0 deletions libraries/crash-reports/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
setUpLibrary(project)

dependencies {
implementation 'org.jetbrains:annotations:24.0.1'
implementation 'it.unimi.dsi:fastutil:8.5.9'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
setUpModule(project)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
min_mc_version = 1.4.3
max_mc_version = 1.14.4

minecraft_version = 1.14.4
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package net.ornithemc.osl.crashreports.api;

import java.util.function.Consumer;

import net.ornithemc.osl.crashreports.api.crash.CrashReport;
import net.ornithemc.osl.crashreports.impl.CrashExceptionsImpl;

/**
* Utility methods for constructing crash exceptions.
*/
public final class CrashExceptions {

/**
* Constructs a crash exception with a crash report that is empty
* exception for system details.
*
* @param title the title of the crash report.
* @param error the error that caused the crash.
* @return a new crash exception
*/
public static RuntimeException from(String title, Throwable error) {
return CrashExceptionsImpl.from(title, error);
}

/**
* Constructs a crash exception with a crash report that is populated
* by the given builder.
*
* @param title the title of the crash report.
* @param error the error that caused the crash.
* @param builder the builder that populates the crash report.
* @return a new crash exception.
*/
public static RuntimeException from(String title, Throwable error, Consumer<CrashReport> builder) {
return CrashExceptionsImpl.from(title, error, builder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.ornithemc.osl.crashreports.impl;

import java.util.function.Consumer;

import net.minecraft.util.crash.CrashException;
import net.minecraft.util.crash.CrashReport;

public final class CrashExceptionsImpl {

public static RuntimeException from(String title, Throwable error) {
return from(title, error, report -> { });
}

public static RuntimeException from(String title, Throwable error, Consumer<net.ornithemc.osl.crashreports.api.crash.CrashReport> builder) {
CrashReport report = CrashReport.of(error, title);
builder.accept(report);

return new CrashException(report);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package net.ornithemc.osl.crashreports.impl.mixin.client;

import java.util.ArrayList;
import java.util.List;

import org.spongepowered.asm.mixin.Mixin;
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.CallbackInfoReturnable;

import net.minecraft.client.Minecraft;
import net.minecraft.util.crash.CrashReport;

import net.ornithemc.osl.crashreports.api.crash.CrashListener;
import net.ornithemc.osl.crashreports.api.crash.CrashReportPopulator;
import net.ornithemc.osl.crashreports.impl.CrashReports;

@Mixin(Minecraft.class)
public class MinecraftMixin implements CrashReportPopulator {

@Unique
private final List<CrashListener> crashListeners = new ArrayList<>();

@Override
public void addCrashListener(CrashListener listener) {
this.crashListeners.add(listener);
}

@Inject(
method = "populateCrashReport",
at = @At(
value = "TAIL"
)
)
private void osl$crashreports$populateCrashReport(CrashReport report, CallbackInfoReturnable<CrashReport> cir) {
CrashReports.notifyListeners(report, this.crashListeners);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package net.ornithemc.osl.crashreports.impl.mixin.common;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;

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;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;

import net.ornithemc.conditionalmixin.annotations.Conditional;
import net.ornithemc.conditionalmixin.annotations.Version;

import net.ornithemc.osl.core.impl.util.MinecraftVersion;
import net.ornithemc.osl.crashreports.api.crash.CrashReportDetail;
import net.ornithemc.osl.crashreports.impl.crash.ModifiableCrashReportSection;

@Mixin(net.minecraft.util.crash.CrashReportCategory.class)
public class CrashReportCategoryMixin implements ModifiableCrashReportSection {

private static final boolean CRASH_REPORT_DETAIL_EXISTS = MinecraftVersion.resolve().compareTo("16w15b") >= 0;
private static final boolean ADD_RETURNS_REPORT_CATEGORY = MinecraftVersion.resolve().compareTo("1.14.4-pre4") >= 0;

@Shadow
private List<?> entries;

@Unique
private Object2ReferenceMap<String, Object> entriesByKey;

@Conditional(minecraftVersion = @Version("<16w15b"))
@Shadow
private void m_16534273(String key, Callable<?> detail) { }
@Conditional(minecraftVersion = @Version(">=16w15b <1.14.4-pre4"))
@Shadow
private void m_16534273(String key, net.minecraft.util.crash.CrashReportDetail<?> detail) { }
@Conditional(minecraftVersion = @Version(">=1.14.4-pre4"))
@Shadow
private net.minecraft.util.crash.CrashReportCategory add(String key, net.minecraft.util.crash.CrashReportDetail<?> detail) { return null; }
@Conditional(minecraftVersion = @Version("<1.14.4-pre4"))
@Shadow
private void m_32997861(String key, Object detail) { }
@Conditional(minecraftVersion = @Version(">=1.14.4-pre4"))
@Shadow
private net.minecraft.util.crash.CrashReportCategory add(String key, Object detail) { return null; }

@Inject(
method = "<init>",
at = @At(
value = "TAIL"
)
)
private void osl$crashreports$init(CallbackInfo ci) {
this.entriesByKey = new Object2ReferenceOpenHashMap<>();
}

@Conditional(minecraftVersion = @Version("<1.14.4-pre4"))
@Inject(
method = "m_32997861",
at = @At(
value = "TAIL"
)
)
private void osl$crashreports$saveEntryIndex(String key, Object detail, CallbackInfo ci) {
this.saveLastEntry(key);
}

@Conditional(minecraftVersion = @Version(">=1.14.4-pre4"))
@Inject(
method = "m_32997861",
at = @At(
value = "TAIL"
)
)
private void osl$crashreports$saveEntryIndex(String key, Object detail, CallbackInfoReturnable<net.minecraft.util.crash.CrashReportCategory> cir) {
this.saveLastEntry(key);
}

@Unique
private void saveLastEntry(String key) {
this.entriesByKey.put(key, this.entries.get(this.entries.size() - 1));
}

@Override
public void addDetail(String key, CrashReportDetail<String> detail) {
if (!CRASH_REPORT_DETAIL_EXISTS) {
this.m_16534273(key, (Callable<?>) detail::call);
} else if (!ADD_RETURNS_REPORT_CATEGORY) {
this.m_16534273(key, (net.minecraft.util.crash.CrashReportDetail<?>) detail::call);
} else {
this.add(key, detail::call);
}
}
@Override
public void addDetail(String key, Object detail) {
if (ADD_RETURNS_REPORT_CATEGORY) {
this.add(key, detail);
} else {
this.m_32997861(key, detail);
}
}

@Override
public void addDetailError(String key, Throwable error) {
this.addDetail(key, error);
}

@Override
public void removeDetail(String key) {
Object entry = this.entriesByKey.remove(key);

if (entry != null) {
this.entries.remove(entry);
}
}

@Override
public void replaceDetail(String oldKey, String newKey, CrashReportDetail<String> detail) {
Object entry = this.entriesByKey.remove(oldKey);

if (entry != null) {
int index = this.entries.indexOf(entry);

this.addDetail(newKey, detail); // also updates the map
Collections.swap(this.entries, index, this.entries.size() - 1);
this.entries.remove(this.entries.size() - 1);
}
}
}
Loading
Loading