Skip to content
Merged
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
79 changes: 79 additions & 0 deletions common/src/main/java/com/lambda/mixin/CrashReportMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2025 Lambda
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.lambda.mixin;

import com.lambda.Lambda;
import com.lambda.module.Module;
import com.lambda.module.ModuleRegistry;
import com.lambda.util.DynamicException;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Util;
import net.minecraft.util.crash.CrashReport;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

// Modify the crash report behavior for dynamic remapping, Easter egg and github issue link
@Mixin(CrashReport.class)
public class CrashReportMixin {
@Mutable
@Shadow @Final private Throwable cause;

@Inject(method = "<init>(Ljava/lang/String;Ljava/lang/Throwable;)V", at = @At("TAIL"))
void injectConstructor(String message, Throwable cause, CallbackInfo ci) {
if (!Lambda.INSTANCE.isDebug() && MinecraftClient.getInstance() != null) {
this.cause = new DynamicException(cause);
}
}

@Redirect(method = "asString()Ljava/lang/String;", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/crash/CrashReport;addStackTrace(Ljava/lang/StringBuilder;)V"))
void injectString(CrashReport instance, StringBuilder stringBuilder) {
stringBuilder.append("If this issue is related to Lambda, check if other users have experienced this too, or create a new issue at https://github.com/lambda-client/lambda/issues.\n\n");

if (MinecraftClient.getInstance() != null) {
stringBuilder.append("Enabled modules:\n");

ModuleRegistry.INSTANCE.getModules()
.stream().filter(Module::isEnabled)
.forEach(m -> stringBuilder.append("\t").append(m.getName()).append("\n"));
}

stringBuilder.append("\n");
stringBuilder.append("-".repeat(43));
stringBuilder.append("\n\n");

instance.addStackTrace(stringBuilder);
}

@Inject(method = "generateWittyComment()Ljava/lang/String;", at = @At("HEAD"), cancellable = true)
private static void generateWittyComment(CallbackInfoReturnable<String> cir) {
String[] strings = new String[]{"Who set us up the TNT?", "Everything's going to plan. No, really, that was supposed to happen.", "Uh... Did I do that?", "Oops.", "Why did you do that?", "I feel sad now :(", "My bad.", "I'm sorry, Dave.", "I let you down. Sorry :(", "On the bright side, I bought you a teddy bear!", "Daisy, daisy...", "Oh - I know what I did wrong!", "Hey, that tickles! Hehehe!", "I blame Dinnerbone.", "You should try our sister game, Minceraft!", "Don't be sad. I'll do better next time, I promise!", "Don't be sad, have a hug! <3", "I just don't know what went wrong :(", "Shall we play a game?", "Quite honestly, I wouldn't worry myself about that.", "I bet Cylons wouldn't have this problem.", "Sorry :(", "Surprise! Haha. Well, this is awkward.", "Would you like a cupcake?", "Hi. I'm Minecraft, and I'm a crashaholic.", "Ooh. Shiny.", "This doesn't make any sense!", "Why is it breaking :(", "Don't do that.", "Ouch. That hurt :(", "You're mean.", "This is a token for 1 free hug. Redeem at your nearest Mojangsta: [~~HUG~~]", "There are four lights!", "But it works on my machine.", "Popbob was here.", "The oldest anarchy server in Minecraft.", "Better luck next time..", "Fatal error occurred user is too based.", "Running premium software on a potato is not advised", "I don't know, ask that kilab guy", "Ah shit, here we go again.", "I will uhh, fix that sometime.", "Not a bug, a feature!", "You should try out Lambda on Windows XP.", "Blade did that."};

try {
cir.setReturnValue(strings[(int)(Util.getMeasuringTimeNano() % (long)strings.length)]);
} catch (Throwable var2) {
cir.setReturnValue("Witty comment unavailable :(");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import com.lambda.brigadier.executeWithResult
import com.lambda.brigadier.required
import com.lambda.command.LambdaCommand
import com.lambda.module.modules.player.Replay
import com.lambda.util.FileUtils.listRecursive
import com.lambda.util.FolderRegister
import com.lambda.util.FolderRegister.listRecursive
import com.lambda.util.extension.CommandBuilder
import kotlin.io.path.exists

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.lambda.network.NetworkManager.updateToken
import com.lambda.network.api.v1.endpoints.login
import com.lambda.util.StringUtils.hash
import com.lambda.util.extension.isOffline
import net.minecraft.SharedConstants
import net.minecraft.client.network.AllowedAddressResolver
import net.minecraft.client.network.ClientLoginNetworkHandler
import net.minecraft.client.network.ServerAddress
Expand All @@ -51,6 +52,9 @@ object Network : Module(
val authServer by setting("Auth Server", "auth.lambda-client.org")
val apiUrl by setting("API Server", "https://api.lambda-client.org")
val apiVersion by setting("API Version", ApiVersion.V1)
val mappings by setting("Mappings", "https://mappings.lambda-client.org")

val gameVersion = SharedConstants.getGameVersion().name

private var hash: String? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ import com.lambda.util.Communication
import com.lambda.util.Communication.info
import com.lambda.util.DynamicReflectionSerializer.dynamicString
import com.lambda.util.FolderRegister
import com.lambda.util.FolderRegister.relativeMCPath
import com.lambda.util.Formatting.getTime
import com.lambda.util.text.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import net.minecraft.network.packet.Packet
import java.awt.Color
import java.io.File
import java.nio.file.Path
import java.time.format.DateTimeFormatter
import kotlin.io.path.pathString

Expand Down Expand Up @@ -83,7 +83,6 @@ object PacketLogger : Module(
extraBufferCapacity = 1000,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
private val File.relativePath: Path get() = mc.runDirectory.toPath().relativize(toPath())

init {
runIO {
Expand All @@ -105,15 +104,14 @@ object PacketLogger : Module(
createNewFile()
}
val info = buildText {
clickEvent(ClickEvents.openFile(relativePath.pathString)) {
clickEvent(ClickEvents.openFile(relativeMCPath.pathString)) {
literal("Packet logger started: ")
color(Color.YELLOW) { literal(fileName) }
literal(" (click to open)")
}
}
this@PacketLogger.info(info)
}.apply {
// ToDo: Add more rich and accurate data to the header
StringBuilder().apply {
appendLine(Communication.ascii)
appendLine("${Lambda.SYMBOL} - Lambda ${Lambda.VERSION} - Packet Log")
Expand Down Expand Up @@ -144,8 +142,8 @@ object PacketLogger : Module(
file?.let {
val info = buildText {
literal("Stopped logging packets to ")
clickEvent(ClickEvents.openFile(it.relativePath.pathString)) {
color(Color.YELLOW) { literal(it.relativePath.pathString) }
clickEvent(ClickEvents.openFile(it.relativeMCPath.pathString)) {
color(Color.YELLOW) { literal(it.relativeMCPath.pathString) }
literal(" (click to open)")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import com.lambda.event.events.TickEvent
import com.lambda.event.listener.SafeListener.Companion.listen
import com.lambda.module.Module
import com.lambda.module.tag.ModuleTag
import com.lambda.util.FileUtils.locationBoundDirectory
import com.lambda.util.FolderRegister
import com.lambda.util.FolderRegister.locationBoundDirectory
import com.lambda.util.StringUtils.hashString
import com.lambda.util.player.SlotUtils.combined
import com.lambda.util.world.entitySearch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ import com.lambda.sound.SoundManager.playSound
import com.lambda.util.Communication.info
import com.lambda.util.Communication.logError
import com.lambda.util.Communication.warn
import com.lambda.util.FileUtils.locationBoundDirectory
import com.lambda.util.FolderRegister
import com.lambda.util.FolderRegister.locationBoundDirectory
import com.lambda.util.Formatting.asString
import com.lambda.util.Formatting.getTime
import com.lambda.util.KeyCode
Expand Down
17 changes: 13 additions & 4 deletions common/src/main/kotlin/com/lambda/network/LambdaHttp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,20 @@ val LambdaHttp = HttpClient {
}
}

suspend inline fun HttpClient.download(url: String, file: File, block: HttpRequestBuilder.() -> Unit = {}) =
file.writeBytes(get(url, block).readRawBytes())
suspend inline fun HttpClient.download(url: String, file: File, block: HttpRequestBuilder.() -> Unit = {}) {
val response = get(url, block)
check(response.status.isSuccess()) { "Download for $url failed with non 2xx status code" }

suspend inline fun HttpClient.download(url: String, output: OutputStream, block: HttpRequestBuilder.() -> Unit = {}) =
output.write(get(url, block).readRawBytes())
file.writeBytes(response.readRawBytes())
}

suspend inline fun HttpClient.download(url: String, output: OutputStream, block: HttpRequestBuilder.() -> Unit = {}) {
val response = get(url, block)
check(response.status.isSuccess()) { "Download for $url failed with non 2xx status code" }

output.write(response.readRawBytes())
}

suspend inline fun HttpClient.download(url: String, block: HttpRequestBuilder.() -> Unit) =
get(url, block).readRawBytes()

Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import java.util.UUID
* Example:
* - id: ab24f5d6-dcf1-45e4-897e-b50a7c5e7422
*
* response: [Cape] or error
* @return results of cape
*/
suspend fun getCape(uuid: UUID) = runCatching {
LambdaHttp.get("$apiUrl/api/${apiVersion.value}/cape?id=$uuid").body<Cape>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import io.ktor.http.*
* Example:
* - token: OTk1MTU1NzcyMzYxMTQ2NDM4
*
* response: [Authentication] or error
* @return result of [Authentication]
*/
suspend fun linkDiscord(discordToken: String) = runCatching {
LambdaHttp.post("${apiUrl}/api/${apiVersion.value}/link/discord") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import io.ktor.http.*
* - username: Notch
* - hash: 069a79f444e94726a5befca90e38aaf5
*
* response: [Authentication] or error
* @return result of [Authentication]
*/
suspend fun login(username: String, hash: String) = runCatching {
LambdaHttp.post("${apiUrl}/api/${apiVersion.value}/login") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import io.ktor.http.*
* Example:
* - id: galaxy
*
* response: [Unit] or error
* @return nothing
*/
suspend fun setCape(id: String) = runCatching {
val resp = LambdaHttp.put("$apiUrl/api/${apiVersion.value}/cape?id=$id") {
Expand Down
56 changes: 56 additions & 0 deletions common/src/main/kotlin/com/lambda/util/DynamicException.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2025 Lambda
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.lambda.util

import com.lambda.util.DynamicReflectionSerializer.remappedName
import java.io.PrintStream
import java.io.PrintWriter

class DynamicException(original: Throwable) : Throwable(original) {
private val remappedStackTrace = original.stackTrace.remapClassNames()

init {
stackTrace = remappedStackTrace
}

private fun Array<StackTraceElement>.remapClassNames() =
map { element ->
StackTraceElement(
element.className.remappedName,
element.methodName.remappedName,
element.fileName,
element.lineNumber
)
}.toTypedArray()

override fun printStackTrace(s: PrintStream) {
s.println(this)
remappedStackTrace.forEach { element ->
s.println("\tat $element")
}
}

override fun printStackTrace(s: PrintWriter) {
s.println(this)
remappedStackTrace.forEach { element ->
s.println("\tat $element")
}
}

override fun toString(): String = localizedMessage
}
Loading