aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-12-17 23:07:06 +0100
committerLinnea Gräf <nea@nea.moe>2024-12-17 23:07:06 +0100
commita046b198645811fe1b7db129942505c379aabb03 (patch)
treed97e68b1cf3a272aec5d8dd863b82fb0c9b0dc82
parenta2f73de90fb9c9d0ea7a5e7e9e6b9e445a8094ee (diff)
downloadLocalTransactionLedger-a046b198645811fe1b7db129942505c379aabb03.tar.gz
LocalTransactionLedger-a046b198645811fe1b7db129942505c379aabb03.tar.bz2
LocalTransactionLedger-a046b198645811fe1b7db129942505c379aabb03.zip
feat: Add name prefetchingHEADmaster
-rw-r--r--src/main/java/moe/nea/ledger/mixin/OnInitializationCompletePatch.java18
-rw-r--r--src/main/kotlin/moe/nea/ledger/DebugDataCommand.kt34
-rw-r--r--src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt14
-rw-r--r--src/main/kotlin/moe/nea/ledger/Ledger.kt8
-rw-r--r--src/main/kotlin/moe/nea/ledger/events/InitializationComplete.kt6
-rw-r--r--src/main/kotlin/moe/nea/ledger/events/SupplyDebugInfo.kt10
-rw-r--r--src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt43
-rw-r--r--src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt5
-rw-r--r--src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt10
-rw-r--r--src/main/kotlin/moe/nea/ledger/utils/di/DI.kt7
-rw-r--r--src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt1
-rw-r--r--src/main/kotlin/moe/nea/ledger/utils/network/Request.kt40
-rw-r--r--src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt63
-rw-r--r--src/main/kotlin/moe/nea/ledger/utils/network/Response.kt19
-rw-r--r--src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt5
-rw-r--r--src/main/resources/ledgerkeystore.jksbin0 -> 104393 bytes
-rw-r--r--visualizer5
17 files changed, 282 insertions, 6 deletions
diff --git a/src/main/java/moe/nea/ledger/mixin/OnInitializationCompletePatch.java b/src/main/java/moe/nea/ledger/mixin/OnInitializationCompletePatch.java
new file mode 100644
index 0000000..fc9afb7
--- /dev/null
+++ b/src/main/java/moe/nea/ledger/mixin/OnInitializationCompletePatch.java
@@ -0,0 +1,18 @@
+package moe.nea.ledger.mixin;
+
+import moe.nea.ledger.events.InitializationComplete;
+import net.minecraft.client.Minecraft;
+import net.minecraftforge.common.MinecraftForge;
+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(Minecraft.class)
+public class OnInitializationCompletePatch {
+
+ @Inject(method = "startGame", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/client/FMLClientHandler;onInitializationComplete()V"))
+ private void onInitComplete(CallbackInfo ci) {
+ MinecraftForge.EVENT_BUS.post(new InitializationComplete());
+ }
+}
diff --git a/src/main/kotlin/moe/nea/ledger/DebugDataCommand.kt b/src/main/kotlin/moe/nea/ledger/DebugDataCommand.kt
new file mode 100644
index 0000000..bab0a78
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/DebugDataCommand.kt
@@ -0,0 +1,34 @@
+package moe.nea.ledger
+
+import moe.nea.ledger.events.SupplyDebugInfo
+import moe.nea.ledger.utils.di.Inject
+import net.minecraft.command.CommandBase
+import net.minecraft.command.ICommandSender
+import net.minecraftforge.common.MinecraftForge
+
+class DebugDataCommand : CommandBase() {
+
+ override fun canCommandSenderUseCommand(sender: ICommandSender?): Boolean {
+ return true
+ }
+
+ override fun getCommandName(): String {
+ return "ledgerdebug"
+ }
+
+ override fun getCommandUsage(sender: ICommandSender?): String {
+ return ""
+ }
+
+ @Inject
+ lateinit var logger: LedgerLogger
+
+ override fun processCommand(sender: ICommandSender?, args: Array<out String>?) {
+ val debugInfo = SupplyDebugInfo()
+ MinecraftForge.EVENT_BUS.post(debugInfo)
+ logger.printOut("Collected debug info:")
+ debugInfo.data.forEach {
+ logger.printOut("${it.first}: ${it.second}")
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
index 121586e..a84aac8 100644
--- a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
+++ b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt
@@ -3,6 +3,8 @@ package moe.nea.ledger
import moe.nea.ledger.events.BeforeGuiAction
import moe.nea.ledger.events.ExtraSupplyIdEvent
import moe.nea.ledger.events.RegistrationFinishedEvent
+import moe.nea.ledger.events.SupplyDebugInfo
+import moe.nea.ledger.modules.ExternalDataProvider
import net.minecraft.client.Minecraft
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
@@ -28,6 +30,13 @@ class ItemIdProvider {
private val knownNames = mutableMapOf<String, ItemId>()
@SubscribeEvent
+ fun onDataLoaded(event: ExternalDataProvider.DataLoaded) {
+ event.provider.itemNames.forEach { (itemId, itemName) ->
+ knownNames[itemName.unformattedString().trim()] = ItemId(itemId)
+ }
+ }
+
+ @SubscribeEvent
fun onRegistrationFinished(event: RegistrationFinishedEvent) {
MinecraftForge.EVENT_BUS.post(ExtraSupplyIdEvent(knownNames::put))
}
@@ -40,6 +49,11 @@ class ItemIdProvider {
inventory.armorInventory?.forEach { saveFromSlot(it) }
}
+ @SubscribeEvent
+ fun onDebugData(event: SupplyDebugInfo) {
+ event.record("knownItemNames", knownNames.size)
+ }
+
fun saveFromSlot(stack: ItemStack?, preprocessName: (String) -> String = { it }) {
if (stack == null) return
val nbt = stack.tagCompound ?: NBTTagCompound()
diff --git a/src/main/kotlin/moe/nea/ledger/Ledger.kt b/src/main/kotlin/moe/nea/ledger/Ledger.kt
index b8a4073..58a8b3d 100644
--- a/src/main/kotlin/moe/nea/ledger/Ledger.kt
+++ b/src/main/kotlin/moe/nea/ledger/Ledger.kt
@@ -15,13 +15,15 @@ import moe.nea.ledger.modules.BazaarOrderDetection
import moe.nea.ledger.modules.BitsDetection
import moe.nea.ledger.modules.BitsShopDetection
import moe.nea.ledger.modules.DungeonChestDetection
+import moe.nea.ledger.modules.ExternalDataProvider
import moe.nea.ledger.modules.KatDetection
import moe.nea.ledger.modules.KuudraChestDetection
import moe.nea.ledger.modules.MinionDetection
import moe.nea.ledger.modules.NpcDetection
import moe.nea.ledger.modules.VisitorDetection
-import moe.nea.ledger.utils.di.DI
import moe.nea.ledger.utils.ErrorUtil
+import moe.nea.ledger.utils.di.DI
+import moe.nea.ledger.utils.network.RequestUtil
import net.minecraft.client.Minecraft
import net.minecraft.command.ICommand
import net.minecraftforge.client.ClientCommandHandler
@@ -99,12 +101,14 @@ class Ledger {
BankDetection::class.java,
BazaarDetection::class.java,
BazaarOrderDetection::class.java,
+ DebugDataCommand::class.java,
BitsDetection::class.java,
BitsShopDetection::class.java,
ConfigCommand::class.java,
Database::class.java,
DungeonChestDetection::class.java,
ErrorUtil::class.java,
+ ExternalDataProvider::class.java,
ItemIdProvider::class.java,
KatDetection::class.java,
KuudraChestDetection::class.java,
@@ -113,6 +117,7 @@ class Ledger {
MinionDetection::class.java,
NpcDetection::class.java,
QueryCommand::class.java,
+ RequestUtil::class.java,
VisitorDetection::class.java,
)
val errorUtil = di.provide<ErrorUtil>()
@@ -125,7 +130,6 @@ class Ledger {
errorUtil.catch {
di.provide<Database>().loadAndUpgrade()
- error("Lol")
}
MinecraftForge.EVENT_BUS.post(RegistrationFinishedEvent())
diff --git a/src/main/kotlin/moe/nea/ledger/events/InitializationComplete.kt b/src/main/kotlin/moe/nea/ledger/events/InitializationComplete.kt
new file mode 100644
index 0000000..d917039
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/events/InitializationComplete.kt
@@ -0,0 +1,6 @@
+package moe.nea.ledger.events
+
+import net.minecraftforge.fml.common.eventhandler.Event
+
+class InitializationComplete : Event() {
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/events/SupplyDebugInfo.kt b/src/main/kotlin/moe/nea/ledger/events/SupplyDebugInfo.kt
new file mode 100644
index 0000000..cab0a20
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/events/SupplyDebugInfo.kt
@@ -0,0 +1,10 @@
+package moe.nea.ledger.events
+
+import net.minecraftforge.fml.common.eventhandler.Event
+
+class SupplyDebugInfo : Event() { // TODO: collect this in the event recorder
+ val data = mutableListOf<Pair<String, Any>>()
+ fun record(key: String, value: Any) {
+ data.add(key to value)
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt b/src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt
new file mode 100644
index 0000000..93bb453
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt
@@ -0,0 +1,43 @@
+package moe.nea.ledger.modules
+
+import moe.nea.ledger.events.InitializationComplete
+import moe.nea.ledger.events.SupplyDebugInfo
+import moe.nea.ledger.utils.GsonUtil
+import moe.nea.ledger.utils.di.Inject
+import moe.nea.ledger.utils.network.Request
+import moe.nea.ledger.utils.network.RequestUtil
+import net.minecraftforge.common.MinecraftForge
+import net.minecraftforge.fml.common.eventhandler.Event
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
+import java.util.concurrent.CompletableFuture
+
+class ExternalDataProvider @Inject constructor(
+ val requestUtil: RequestUtil
+) {
+
+ fun createAuxillaryDataRequest(path: String): Request {
+ return requestUtil.createRequest("https://github.com/nea89o/ledger-auxiliary-data/raw/refs/heads/master/$path")
+ }
+
+ private val itemNameFuture: CompletableFuture<Map<String, String>> = CompletableFuture.supplyAsync {
+ val request = createAuxillaryDataRequest("data/item_names.json")
+ val response = request.execute(requestUtil)
+ val nameMap = response.json(GsonUtil.typeToken<Map<String, String>>())
+ return@supplyAsync nameMap
+ }
+
+ lateinit var itemNames: Map<String, String>
+
+ class DataLoaded(val provider: ExternalDataProvider) : Event()
+
+ @SubscribeEvent
+ fun onDebugData(debugInfo: SupplyDebugInfo) {
+ debugInfo.record("externalItemsLoaded", itemNameFuture.isDone && !itemNameFuture.isCompletedExceptionally)
+ }
+
+ @SubscribeEvent
+ fun onInitComplete(event: InitializationComplete) {
+ itemNames = itemNameFuture.join()
+ MinecraftForge.EVENT_BUS.post(DataLoaded(this))
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt b/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt
index 4ba313e..344c8bc 100644
--- a/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt
+++ b/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt
@@ -10,6 +10,11 @@ class ErrorUtil {
@Inject
lateinit var reporter: EventRecorder
+ fun reportAdHoc(message: String) {
+ report(Exception(message), message)
+
+ }
+
fun report(exception: Throwable, message: String?) {
Span.current().recordException(reporter, exception, message)
}
diff --git a/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt b/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt
new file mode 100644
index 0000000..d3c1f6e
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt
@@ -0,0 +1,10 @@
+package moe.nea.ledger.utils
+
+import com.google.gson.reflect.TypeToken
+
+object GsonUtil {
+ inline fun <reified T> typeToken(): TypeToken<T> {
+ return object : TypeToken<T>() {}
+ }
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt b/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt
index 6940f72..133637a 100644
--- a/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt
+++ b/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt
@@ -6,6 +6,9 @@ import java.util.Stack
@Suppress("UNCHECKED_CAST")
class DI {
+ private fun formatInjectionStack() =
+ injectionStack.joinToString(" -> ")
+
private fun <T : Any, C> internalProvide(type: Class<T>, element: AnnotatedElement? = null): T {
val provider = providers[type] as BaseDIProvider<T, C>
val context = if (element == null) provider.createEmptyContext() else provider.createContext(element)
@@ -13,13 +16,13 @@ class DI {
val existingValue = values[key]
if (existingValue != null) return existingValue as T
if (type in injectionStack) {
- error("Found injection cycle: ${injectionStack.joinToString(" -> ")} -> $type")
+ error("Found injection cycle: ${formatInjectionStack()} -> $type")
}
injectionStack.push(type)
val value = try {
provider.provideWithContext(this, context)
} catch (ex: Exception) {
- throw RuntimeException("Could not create instance for type $type", ex)
+ throw RuntimeException("Could not create instance for type $type (in stack ${formatInjectionStack()})", ex)
}
val cycleCheckCookie = injectionStack.pop()
require(cycleCheckCookie == type) { "Unbalanced stack cookie: $cycleCheckCookie != $type" }
diff --git a/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt b/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt
index b5ce550..bd5b9ef 100644
--- a/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt
+++ b/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt
@@ -24,6 +24,7 @@ fun interface DIProvider<T : Any> : BaseDIProvider<T, Unit> {
?: clazz.constructors.find { it.parameterCount == 0 }
?: error("Could not find DI injection entrypoint for class $clazz"))
as Constructor<out T>
+ // TODO: consider using unsafe init to inject the parameters *before* calling the constructor
return DIProvider { di ->
val typArgs = cons.parameters.map {
di.provide(it.type, it)
diff --git a/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt b/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt
new file mode 100644
index 0000000..ddf2fcc
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt
@@ -0,0 +1,40 @@
+package moe.nea.ledger.utils.network
+
+import com.google.gson.JsonElement
+import java.net.URL
+
+data class Request(
+ val url: URL,
+ val method: Method,
+ val body: String?,
+ val headers: Map<String, String>,
+) {
+ enum class Method {
+ GET, POST
+ }
+
+ enum class MediaType(val text: String) {
+ JSON("application/json"),
+ TEXT("text/plain"),
+ HTML("text/html"),
+ ANY("*/*"),
+ }
+
+ fun withHeaders(map: Map<String, String>): Request {
+ // TODO: enforce caselessness?
+ return this.copy(headers = headers + map)
+ }
+
+ fun post() = copy(method = Method.POST)
+ fun get() = copy(method = Method.GET)
+
+ fun json(element: JsonElement) = copy(
+ headers = headers + mapOf("content-type" to "application/json"),
+ body = element.toString())
+
+ fun accept(request: MediaType) = withHeaders(mapOf("accept" to request.text))
+
+ fun acceptJson() = accept(MediaType.JSON)
+
+ fun execute(requestUtil: RequestUtil) = requestUtil.executeRequest(this)
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt b/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt
new file mode 100644
index 0000000..a49c65a
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt
@@ -0,0 +1,63 @@
+package moe.nea.ledger.utils.network
+
+import moe.nea.ledger.utils.ErrorUtil
+import moe.nea.ledger.utils.di.Inject
+import java.net.URL
+import java.net.URLConnection
+import java.security.KeyStore
+import java.util.zip.GZIPInputStream
+import javax.net.ssl.HttpsURLConnection
+import javax.net.ssl.KeyManagerFactory
+import javax.net.ssl.SSLContext
+import javax.net.ssl.TrustManagerFactory
+
+class RequestUtil @Inject constructor(val errorUtil: ErrorUtil) {
+
+ private fun createSSLContext(): SSLContext? = errorUtil.catch {
+ val keyStorePath = RequestUtil::class.java.getResourceAsStream("/ledgerkeystore.jks")
+ ?: error("Could not locate keystore")
+ val keyStore = KeyStore.getInstance("JKS")
+ keyStore.load(keyStorePath, "neuneu".toCharArray())
+ val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
+ val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
+ kmf.init(keyStore, null)
+ tmf.init(keyStore)
+ val ctx = SSLContext.getInstance("TLS")
+ ctx.init(kmf.keyManagers, tmf.trustManagers, null)
+ return@catch ctx
+ }
+
+ val sslContext = createSSLContext()
+
+ fun enhanceConnection(connection: URLConnection) {
+ if (connection is HttpsURLConnection && sslContext != null) {
+ connection.sslSocketFactory = sslContext.socketFactory
+ }
+ }
+
+ fun createRequest(url: String) = createRequest(URL(url))
+ fun createRequest(url: URL) = Request(url, Request.Method.GET, null, mapOf())
+
+ fun executeRequest(request: Request): Response {
+ val connection = request.url.openConnection()
+ enhanceConnection(connection)
+ connection.setRequestProperty("accept-encoding", "gzip")
+ request.headers.forEach { (k, v) ->
+ connection.setRequestProperty(k, v)
+ }
+ if (request.body != null) {
+ connection.getOutputStream().write(request.body.encodeToByteArray())
+ connection.getOutputStream().close()
+ }
+ var stream = connection.getInputStream()
+ if (connection.contentEncoding == "gzip") {
+ stream = GZIPInputStream(stream)
+ }
+ val text = stream.bufferedReader().readText()
+ stream.close()
+ // Do NOT call connection.disconnect() to allow for connection reuse
+ return Response(request, text, connection.headerFields)
+ }
+
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt b/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt
new file mode 100644
index 0000000..daae7f7
--- /dev/null
+++ b/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt
@@ -0,0 +1,19 @@
+package moe.nea.ledger.utils.network
+
+import com.google.gson.reflect.TypeToken
+import moe.nea.ledger.Ledger
+
+data class Response(
+ val source: Request,
+ // TODO: allow other body processors, to avoid loading everything as strings
+ val response: String,
+ val headers: Map<String, List<String>>,
+) {
+ fun <T : Any> json(typ: TypeToken<T>): T {
+ return Ledger.gson.fromJson(response, typ.type)
+ }
+
+ fun <T : Any> json(clazz: Class<T>): T {
+ return Ledger.gson.fromJson(response, clazz)
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt b/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt
index df588a8..96b70ec 100644
--- a/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt
+++ b/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt
@@ -22,8 +22,9 @@ class ExceptionContextValue(val exception: Throwable) : ContextValue {
obj.addProperty("message", exception.message)
// TODO: allow exceptions to implement an "extra info" interface
if (searchDepth > 0) {
- if (exception.cause != null) {
- obj.add("cause", walkExceptions(exception, searchDepth - 1))
+ val cause = exception.cause
+ if (cause != null && cause !== exception) {
+ obj.add("cause", walkExceptions(cause, searchDepth - 1))
}
val suppressions = JsonArray()
for (suppressedException in exception.suppressedExceptions) {
diff --git a/src/main/resources/ledgerkeystore.jks b/src/main/resources/ledgerkeystore.jks
new file mode 100644
index 0000000..b71185a
--- /dev/null
+++ b/src/main/resources/ledgerkeystore.jks
Binary files differ
diff --git a/visualizer b/visualizer
new file mode 100644
index 0000000..2a8d865
--- /dev/null
+++ b/visualizer
@@ -0,0 +1,5 @@
+graph = bars | delta
+selector = {
+ [itemId = SKYBLOCK_COIN, direction = GAINED] = GREEN
+ [itemId = SKYBLOCK_COIN, direction = LOST] = RED
+}