diff --git a/build.gradle.kts b/build.gradle.kts
index 8dda80673..e2df1d8af 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -18,52 +18,37 @@
* along with this program. If not, see .
*/
-import org.cadixdev.gradle.licenser.header.HeaderStyle
-import org.cadixdev.gradle.licenser.tasks.LicenseUpdate
import org.gradle.internal.jvm.Jvm
import org.jetbrains.changelog.Changelog
import org.jetbrains.gradle.ext.settings
import org.jetbrains.gradle.ext.taskTriggers
import org.jetbrains.intellij.tasks.PrepareSandboxTask
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-import org.jlleitschuh.gradle.ktlint.tasks.BaseKtLintCheckTask
-import org.jlleitschuh.gradle.ktlint.tasks.KtLintFormatTask
plugins {
- kotlin("jvm") version "1.9.20"
java
- mcdev
groovy
idea
- id("org.jetbrains.intellij") version "1.17.2"
- id("org.cadixdev.licenser")
- id("org.jlleitschuh.gradle.ktlint") version "10.3.0"
- id("org.jetbrains.changelog") version "2.2.0"
+ id(libs.plugins.kotlin.get().pluginId)
+ id(libs.plugins.intellij.get().pluginId)
+ id(libs.plugins.licenser.get().pluginId)
+ id(libs.plugins.ktlint.get().pluginId)
+ id(libs.plugins.changelog.get().pluginId)
+ `mcdev-core`
+ `mcdev-parsing`
+ `mcdev-publishing`
}
val ideaVersionName: String by project
val coreVersion: String by project
-val pluginTomlVersion: String by project
val gradleToolingExtension: Configuration by configurations.creating
val testLibs: Configuration by configurations.creating {
isTransitive = false
}
-group = "com.demonwav.minecraft-dev"
+group = "com.demonwav.mcdev"
version = "$ideaVersionName-$coreVersion"
-java {
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(17))
- }
-}
-kotlin {
- jvmToolchain {
- languageVersion.set(java.toolchain.languageVersion.get())
- }
-}
-
val gradleToolingExtensionSourceSet: SourceSet = sourceSets.create("gradle-tooling-extension") {
configurations.named(compileOnlyConfigurationName) {
extendsFrom(gradleToolingExtension)
@@ -100,45 +85,20 @@ val externalAnnotationsJar = tasks.register("externalAnnotationsJar") {
archiveFileName.set("externalAnnotations.jar")
}
-repositories {
- maven("https://repo.denwav.dev/repository/maven-public/")
- maven("https://maven.fabricmc.net/") {
- content {
- includeModule("net.fabricmc", "mapping-io")
- includeModule("net.fabricmc", "fabric-loader")
- }
- }
- mavenCentral()
- maven("https://repo.spongepowered.org/maven/")
-}
-
dependencies {
// Add tools.jar for the JDI API
implementation(files(Jvm.current().toolsJar))
+ implementation(files(gradleToolingExtensionJar))
+
implementation(libs.mixinExtras.expressions)
testLibs(libs.mixinExtras.common)
- // Kotlin
- implementation(kotlin("stdlib-jdk8"))
- implementation(kotlin("reflect"))
- implementation(libs.bundles.coroutines)
-
- implementation(files(gradleToolingExtensionJar))
-
implementation(libs.mappingIo)
implementation(libs.bundles.asm)
implementation(libs.bundles.fuel)
- jflex(libs.jflex.lib)
- jflexSkeleton(libs.jflex.skeleton) {
- artifact {
- extension = "skeleton"
- }
- }
- grammarKit(libs.grammarKit)
-
testLibs(libs.test.mockJdk)
testLibs(libs.test.mixin)
testLibs(libs.test.spongeapi) {
@@ -181,7 +141,7 @@ dependencies {
to.attribute(filtered, true).attribute(artifactType, "jar")
parameters {
- ideaVersion.set(providers.gradleProperty("ideaVersion"))
+ ideaVersion.set(libs.versions.intellij.ide)
ideaVersionName.set(providers.gradleProperty("ideaVersionName"))
depsFile.set(layout.projectDirectory.file(".gradle/intellij-deps.json"))
}
@@ -199,8 +159,6 @@ changelog {
}
intellij {
- // IntelliJ IDEA dependency
- version.set(providers.gradleProperty("ideaVersion"))
// Bundled plugin dependencies
plugins.addAll(
"java",
@@ -208,20 +166,15 @@ intellij {
"gradle",
"Groovy",
"Kotlin",
- "org.toml.lang:$pluginTomlVersion",
"ByteCodeViewer",
"org.intellij.intelliLang",
"properties",
// needed dependencies for unit tests
"junit"
)
+ plugins.addProvider(libs.versions.pluginToml.map { "org.toml.lang:$it" })
pluginName.set("Minecraft Development")
- updateSinceUntilBuild.set(true)
-
- downloadSources.set(providers.gradleProperty("downloadIdeaSources").map { it.toBoolean() })
-
- sandboxDir.set(layout.projectDirectory.dir(".sandbox").toString())
}
tasks.patchPluginXml {
@@ -229,36 +182,10 @@ tasks.patchPluginXml {
changeNotes = changelog.render(Changelog.OutputType.HTML)
}
-tasks.publishPlugin {
- // Build numbers are used for
- properties["buildNumber"]?.let { buildNumber ->
- project.version = "${project.version}-$buildNumber"
- }
- properties["mcdev.deploy.token"]?.let { deployToken ->
- token.set(deployToken.toString())
- }
- channels.add(properties["mcdev.deploy.channel"]?.toString() ?: "Stable")
-}
-
tasks.runPluginVerifier {
ideVersions.addAll("IC-$ideaVersionName")
}
-tasks.withType().configureEach {
- options.encoding = "UTF-8"
- options.compilerArgs = listOf("-proc:none")
- options.release.set(17)
-}
-
-tasks.withType().configureEach {
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_17.toString()
- // K2 causes the following error: https://youtrack.jetbrains.com/issue/KT-52786
- freeCompilerArgs = listOf(/*"-Xuse-k2", */"-Xjvm-default=all", "-Xjdk-release=17")
- kotlinDaemonJvmArguments.add("-Xmx2G")
- }
-}
-
// Compile classes to be loaded into the Gradle VM to Java 5 to match Groovy
// This is for maximum compatibility, these classes will be loaded into every Gradle import on all
// projects (not just Minecraft), so we don't want to break that with an incompatible class version.
@@ -294,10 +221,11 @@ tasks.processResources {
tasks.test {
dependsOn(tasks.jar, testLibs)
- useJUnitPlatform()
- testLibs.resolvedConfiguration.resolvedArtifacts.forEach {
- systemProperty("testLibs.${it.name}", it.file.absolutePath)
+ doFirst {
+ testLibs.resolvedConfiguration.resolvedArtifacts.forEach {
+ systemProperty("testLibs.${it.name}", it.file.absolutePath)
+ }
}
systemProperty("NO_FS_ROOTS_ACCESS_CHECK", "true")
systemProperty("java.awt.headless", "true")
@@ -310,23 +238,10 @@ tasks.test {
idea {
project.settings.taskTriggers.afterSync("generate")
- module {
- generatedSourceDirs.add(file("build/gen"))
- excludeDirs.add(file(intellij.sandboxDir.get()))
- isDownloadJavadoc = true
- isDownloadSources = true
- }
}
license {
- header.set(resources.text.fromFile(file("copyright.txt")))
- style["flex"] = HeaderStyle.BLOCK_COMMENT.format
- style["bnf"] = HeaderStyle.BLOCK_COMMENT.format
-
- val endings = listOf("java", "kt", "kts", "groovy", "gradle.kts", "xml", "properties", "html", "flex", "bnf")
exclude("META-INF/plugin.xml") // https://youtrack.jetbrains.com/issue/IDEA-345026
- include(endings.map { "**/*.$it" })
-
exclude("com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/*.java")
this.tasks {
@@ -355,19 +270,6 @@ license {
}
}
-ktlint {
- disabledRules.add("filename")
-}
-tasks.withType().configureEach {
- workerMaxHeapSize.set("512m")
-}
-
-tasks.register("format") {
- group = "minecraft"
- description = "Formats source code according to project style"
- dependsOn(tasks.withType(), tasks.withType())
-}
-
val generateAtLexer by lexer("AtLexer", "com/demonwav/mcdev/platform/mcp/at/gen")
val generateAtParser by parser("AtParser", "com/demonwav/mcdev/platform/mcp/at/gen")
@@ -412,12 +314,6 @@ sourceSets.main { java.srcDir(generate) }
// Remove gen directory on clean
tasks.clean { delete(generate) }
-tasks.register("cleanSandbox", Delete::class) {
- group = "intellij"
- description = "Deletes the sandbox directory."
- delete(layout.projectDirectory.dir(".sandbox"))
-}
-
tasks.withType {
pluginJar.set(tasks.jar.get().archiveFile)
from(externalAnnotationsJar) {
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index b608a94d9..4e24b640f 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -1,3 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
/*
* Minecraft Development for IntelliJ
*
@@ -22,12 +44,25 @@ plugins {
`kotlin-dsl`
}
+tasks.withType().configureEach {
+ options.release.set(8)
+}
+tasks.withType().configureEach {
+ kotlinOptions.jvmTarget = "1.8"
+}
+
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
- implementation("com.google.code.gson:gson:2.9.1")
- implementation("org.cadixdev.licenser:org.cadixdev.licenser.gradle.plugin:0.6.1")
+ // hack for version catalogs
+ implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
+ implementation(libs.gson)
+ implementation(libs.kotlin.plugin)
+ implementation(libs.intellij.plugin)
+ implementation(libs.licenser.plugin)
+ implementation(libs.ktlint.plugin)
+ implementation(libs.changelog.plugin)
}
diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts
index ab35e99af..4bb019498 100644
--- a/buildSrc/settings.gradle.kts
+++ b/buildSrc/settings.gradle.kts
@@ -19,3 +19,11 @@
*/
rootProject.name = "buildSrc"
+
+dependencyResolutionManagement {
+ versionCatalogs {
+ create("libs") {
+ from(files("../gradle/libs.versions.toml"))
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/ParserExec.kt b/buildSrc/src/main/kotlin/ParserExec.kt
index ad00c9014..870efce0d 100644
--- a/buildSrc/src/main/kotlin/ParserExec.kt
+++ b/buildSrc/src/main/kotlin/ParserExec.kt
@@ -62,6 +62,7 @@ abstract class ParserExec : JavaExec() {
init {
mainClass.set("org.intellij.grammar.Main")
+ @Suppress("LeakingThis")
jvmArgs(
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED",
diff --git a/buildSrc/src/main/kotlin/mcdev.gradle.kts b/buildSrc/src/main/kotlin/mcdev-core.gradle.kts
similarity index 56%
rename from buildSrc/src/main/kotlin/mcdev.gradle.kts
rename to buildSrc/src/main/kotlin/mcdev-core.gradle.kts
index 99a58e2eb..ef044ae83 100644
--- a/buildSrc/src/main/kotlin/mcdev.gradle.kts
+++ b/buildSrc/src/main/kotlin/mcdev-core.gradle.kts
@@ -20,17 +20,129 @@
import com.google.gson.Gson
import com.google.gson.GsonBuilder
-import com.google.gson.reflect.TypeToken
import java.net.HttpURLConnection
import java.net.URI
-import java.net.URL
import java.util.Properties
import java.util.zip.ZipFile
+import org.cadixdev.gradle.licenser.header.HeaderStyle
+import org.gradle.kotlin.dsl.maven
+import org.gradle.kotlin.dsl.repositories
+import org.gradle.kotlin.dsl.withType
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import org.gradle.accessors.dm.LibrariesForLibs
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
+import org.jlleitschuh.gradle.ktlint.tasks.BaseKtLintCheckTask
-val jflex: Configuration by configurations.creating
-val jflexSkeleton: Configuration by configurations.creating
-val grammarKit: Configuration by configurations.creating
-val compileOnly by configurations
+plugins {
+ java
+ idea
+ id("org.jetbrains.kotlin.jvm")
+ id("org.jetbrains.intellij")
+ id("org.cadixdev.licenser")
+ id("org.jlleitschuh.gradle.ktlint")
+}
+
+java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
+
+tasks.withType().configureEach {
+ options.encoding = "UTF-8"
+ options.compilerArgs = listOf("-proc:none")
+ options.release.set(17)
+}
+
+kotlin {
+ jvmToolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
+
+tasks.withType().configureEach {
+ compilerOptions {
+ jvmTarget = JvmTarget.JVM_17
+ languageVersion = KotlinVersion.KOTLIN_2_0
+ freeCompilerArgs = listOf("-Xjvm-default=all", "-Xjdk-release=17")
+ optIn.add("kotlin.contracts.ExperimentalContracts")
+ }
+ kotlinDaemonJvmArguments.add("-Xmx2G")
+}
+
+repositories {
+ maven("https://repo.denwav.dev/repository/maven-public/")
+ maven("https://maven.fabricmc.net/") {
+ content {
+ includeModule("net.fabricmc", "mapping-io")
+ includeModule("net.fabricmc", "fabric-loader")
+ }
+ }
+ mavenCentral()
+ maven("https://repo.spongepowered.org/maven/")
+}
+
+val libs = the()
+dependencies {
+ implementation(libs.kotlin.stdlib)
+ implementation(libs.kotlin.reflect)
+ implementation(libs.bundles.coroutines)
+
+ testImplementation(libs.junit.api)
+ testRuntimeOnly(libs.junit.entine)
+ testRuntimeOnly(libs.junit.platform.launcher)
+}
+
+intellij {
+ // IntelliJ IDEA dependency
+ version.set(libs.versions.intellij.ide)
+
+ updateSinceUntilBuild.set(true)
+ downloadSources.set(providers.gradleProperty("downloadIdeaSources").map { it.toBoolean() })
+
+ sandboxDir.set(layout.projectDirectory.dir(".sandbox").toString())
+}
+
+license {
+ header.set(resources.text.fromFile(rootProject.layout.projectDirectory.file("copyright.txt")))
+ style["flex"] = HeaderStyle.BLOCK_COMMENT.format
+ style["bnf"] = HeaderStyle.BLOCK_COMMENT.format
+
+ val endings = listOf("java", "kt", "kts", "groovy", "gradle.kts", "xml", "properties", "html", "flex", "bnf")
+ include(endings.map { "**/*.$it" })
+}
+
+idea {
+ module {
+ excludeDirs.add(file(intellij.sandboxDir.get()))
+ }
+}
+
+tasks.withType().configureEach {
+ workerMaxHeapSize = "512m"
+}
+
+tasks.runIde {
+ maxHeapSize = "2G"
+ jvmArgs("--add-exports=java.base/jdk.internal.vm=ALL-UNNAMED")
+}
+
+tasks.register("cleanSandbox", Delete::class) {
+ group = "intellij"
+ description = "Deletes the sandbox directory."
+ delete(layout.projectDirectory.dir(".sandbox"))
+}
+
+tasks.test {
+ useJUnitPlatform()
+}
+
+tasks.register("format") {
+ group = "minecraft"
+ description = "Formats source code according to project style"
+ dependsOn(tasks.licenseFormat, tasks.ktlintFormat)
+}
// Analyze dependencies
val fileName = ".gradle/intellij-deps.json"
@@ -107,6 +219,7 @@ tasks.register("resolveIntellijLibSources") {
}.toList()
val depList = DepList(ideaVersion, ideaVersionName, deps.sortedWith(compareBy { it.groupId }.thenBy { it.artifactId }))
+ jsonFile.parentFile.mkdirs()
jsonFile.bufferedWriter().use { writer ->
GsonBuilder().setPrettyPrinting().create().toJson(depList, writer)
}
diff --git a/buildSrc/src/main/kotlin/mcdev-parsing.gradle.kts b/buildSrc/src/main/kotlin/mcdev-parsing.gradle.kts
new file mode 100644
index 000000000..bdd2234e7
--- /dev/null
+++ b/buildSrc/src/main/kotlin/mcdev-parsing.gradle.kts
@@ -0,0 +1,46 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+import org.gradle.accessors.dm.LibrariesForLibs
+
+plugins {
+ idea
+}
+
+val jflex: Configuration by configurations.creating
+val jflexSkeleton: Configuration by configurations.creating
+val grammarKit: Configuration by configurations.creating
+
+val libs = the()
+dependencies {
+ jflex(libs.jflex.lib)
+ jflexSkeleton(libs.jflex.skeleton) {
+ artifact {
+ extension = "skeleton"
+ }
+ }
+ grammarKit(libs.grammarKit)
+}
+
+idea {
+ module {
+ generatedSourceDirs.add(file("build/gen"))
+ }
+}
diff --git a/buildSrc/src/main/kotlin/mcdev-publishing.gradle.kts b/buildSrc/src/main/kotlin/mcdev-publishing.gradle.kts
new file mode 100644
index 000000000..33c636ede
--- /dev/null
+++ b/buildSrc/src/main/kotlin/mcdev-publishing.gradle.kts
@@ -0,0 +1,34 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+plugins {
+ id("org.jetbrains.intellij")
+}
+
+tasks.publishPlugin {
+ // Build numbers are used for nightlies
+ properties["buildNumber"]?.let { buildNumber ->
+ project.version = "${project.version}-$buildNumber"
+ }
+ properties["mcdev.deploy.token"]?.let { deployToken ->
+ token.set(deployToken.toString())
+ }
+ channels.add(properties["mcdev.deploy.channel"]?.toString() ?: "Stable")
+}
diff --git a/buildSrc/src/main/kotlin/util.kt b/buildSrc/src/main/kotlin/util.kt
index d36de248f..9236d4a19 100644
--- a/buildSrc/src/main/kotlin/util.kt
+++ b/buildSrc/src/main/kotlin/util.kt
@@ -22,19 +22,23 @@ import java.io.ByteArrayOutputStream
import org.cadixdev.gradle.licenser.LicenseExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Project
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.provider.Provider
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.TaskContainer
-import org.gradle.api.tasks.TaskProvider
+import org.gradle.api.tasks.util.PatternFilterable
import org.gradle.kotlin.dsl.RegisteringDomainObjectDelegateProviderWithTypeAndAction
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.registering
import org.gradle.kotlin.dsl.configure
+fun ListProperty.addProvider(provider: Provider) = add(provider)
+
typealias TaskDelegate = RegisteringDomainObjectDelegateProviderWithTypeAndAction
fun Project.lexer(flex: String, pack: String): TaskDelegate {
- configure {
+ extensions.configure("license") {
exclude(pack.removeSuffix("/") + "/**")
}
@@ -53,7 +57,7 @@ fun Project.lexer(flex: String, pack: String): TaskDelegate {
}
fun Project.parser(bnf: String, pack: String): TaskDelegate {
- configure {
+ extensions.configure("license") {
exclude(pack.removeSuffix("/") + "/**")
}
diff --git a/gradle.properties b/gradle.properties
index 5d3d680ee..5a09d2f57 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -19,15 +19,10 @@
#
# suppress inspection "UnusedProperty" for whole file
-kotlin.code.style=official
-
-ideaVersion = 2023.2.2
ideaVersionName = 2023.2.2
coreVersion = 1.8.0
downloadIdeaSources = true
-pluginTomlVersion = 232.8660.88
-
# Silences a build-time warning because we are bundling our own kotlin library
kotlin.stdlib.default.dependency = false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9c1c92308..a2627910d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,11 +1,35 @@
[versions]
-coroutines = "1.6.4"
-junit = "5.9.0"
-junit-platform = "1.9.0"
-asm = "9.3"
+kotlin = "2.0.0"
+coroutines = "1.8.0-RC2"
+junit = "5.10.2"
+junit-platform = "1.10.2"
+asm = "9.6"
fuel = "2.3.1"
+licenser = "0.6.1"
+ktlint = "10.3.0"
+changelog = "2.2.0"
+intellij-plugin = "1.17.2"
+intellij-ide = "2023.2.2"
+pluginToml = "232.8660.88"
+psiPlugin = "232.2-SNAPSHOT"
+
+[plugins]
+kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
+intellij = { id = "org.jetbrains.intellij", version.ref = "intellij-plugin" }
+licenser = { id = "org.cadixdev.licenser", version.ref = "licenser" }
+ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
+changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
[libraries]
+kotlin-plugin = { module = "org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin", version.ref = "kotlin" }
+kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
+kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
+
+intellij-plugin = { module = "org.jetbrains.intellij:org.jetbrains.intellij.gradle.plugin", version.ref = "intellij-plugin" }
+licenser-plugin = { module = "org.cadixdev.licenser:org.cadixdev.licenser.gradle.plugin", version.ref = "licenser" }
+ktlint-plugin = { module = "org.jlleitschuh.gradle.ktlint:org.jlleitschuh.gradle.ktlint.gradle.plugin", version.ref = "ktlint" }
+changelog-plugin = { module = "org.jetbrains.changelog:org.jetbrains.changelog.gradle.plugin", version.ref = "changelog" }
+
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines-jdk8 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", version.ref = "coroutines" }
coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" }
@@ -20,7 +44,7 @@ grammarKit = "org.jetbrains.idea:grammar-kit:1.5.1"
# Gradle Tooling
gradleToolingExtension = "com.jetbrains.intellij.gradle:gradle-tooling-extension:232-EAP-SNAPSHOT"
-annotations = "org.jetbrains:annotations:23.0.0"
+annotations = "org.jetbrains:annotations:24.0.0"
groovy = "org.codehaus.groovy:groovy-all:2.5.18"
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
@@ -28,6 +52,8 @@ asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" }
asm-analysis = { module = "org.ow2.asm:asm-analysis", version.ref = "asm" }
asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" }
+gson = "com.google.code.gson:gson:2.10.1"
+
fuel = { module = "com.github.kittinunf.fuel:fuel", version.ref = "fuel" }
fuel-coroutines = { module = "com.github.kittinunf.fuel:fuel-coroutines", version.ref = "fuel" }
diff --git a/obfuscation-explorer/.gitignore b/obfuscation-explorer/.gitignore
new file mode 100644
index 000000000..d094c39b9
--- /dev/null
+++ b/obfuscation-explorer/.gitignore
@@ -0,0 +1,3 @@
+.sandbox/
+build/
+gen/
diff --git a/obfuscation-explorer/build.gradle.kts b/obfuscation-explorer/build.gradle.kts
new file mode 100644
index 000000000..1c47bfaf8
--- /dev/null
+++ b/obfuscation-explorer/build.gradle.kts
@@ -0,0 +1,128 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+plugins {
+ java
+ groovy
+ idea
+ id(libs.plugins.kotlin.get().pluginId)
+ id(libs.plugins.intellij.get().pluginId)
+ id(libs.plugins.licenser.get().pluginId)
+ id(libs.plugins.ktlint.get().pluginId)
+ `mcdev-core`
+ `mcdev-parsing`
+ `mcdev-publishing`
+}
+
+val ideaVersion: String by project
+val ideaVersionName: String by project
+val coreVersion: String by project
+val downloadIdeaSources: String by project
+
+val jflex by configurations
+val jflexSkeleton by configurations
+val grammarKit by configurations
+
+group = "io.mcdev.obfex"
+version = "$ideaVersionName-$coreVersion"
+
+intellij {
+ plugins.addProvider(libs.versions.psiPlugin.map { "PsiViewer:$it" })
+
+ pluginName.set("Obfuscation Explorer")
+}
+
+val generateSrgLexer by lexer("SrgLexer", "io/mcdev/obfex/formats/srg/gen")
+val generateSrgParser by parser("SrgParser", "io/mcdev/obfex/formats/srg/gen")
+
+val generateCSrgLexer by lexer("CSrgLexer", "io/mcdev/obfex/formats/csrg/gen")
+val generateCSrgParser by parser("CSrgParser", "io/mcdev/obfex/formats/csrg/gen")
+
+val generateTSrgLexer by lexer("TSrgLexer", "io/mcdev/obfex/formats/tsrg/gen")
+val generateTSrgParser by parser("TSrgParser", "io/mcdev/obfex/formats/tsrg/gen")
+
+val generateTSrg2Lexer by lexer("TSrg2Lexer", "io/mcdev/obfex/formats/tsrg2/gen")
+val generateTSrg2Parser by parser("TSrg2Parser", "io/mcdev/obfex/formats/tsrg2/gen")
+
+val generateJamLexer by lexer("JamLexer", "io/mcdev/obfex/formats/jam/gen")
+val generateJamParser by parser("JamParser", "io/mcdev/obfex/formats/jam/gen")
+
+val generateEnigmaLexer by lexer("EnigmaLexer", "io/mcdev/obfex/formats/enigma/gen")
+val generateEnigmaParser by parser("EnigmaParser", "io/mcdev/obfex/formats/enigma/gen")
+
+val generateTinyV1Lexer by lexer("TinyV1Lexer", "io/mcdev/obfex/formats/tinyv1/gen")
+val generateTinyV1Parser by parser("TinyV1Parser", "io/mcdev/obfex/formats/tinyv1/gen")
+
+val generateTinyV2Lexer by lexer("TinyV2Lexer", "io/mcdev/obfex/formats/tinyv2/gen")
+val generateTinyV2Parser by parser("TinyV2Parser", "io/mcdev/obfex/formats/tinyv2/gen")
+
+val generateProGuardLexer by lexer("ProGuardLexer", "io/mcdev/obfex/formats/proguard/gen")
+val generateProGuardParser by parser("ProGuardParser", "io/mcdev/obfex/formats/proguard/gen")
+
+val generate by tasks.registering {
+ group = "minecraft"
+ description = "Generates sources needed to compile the plugin."
+ outputs.dir(layout.buildDirectory.dir("gen"))
+ dependsOn(
+ generateSrgLexer,
+ generateSrgParser,
+ generateCSrgLexer,
+ generateCSrgParser,
+ generateTSrgLexer,
+ generateTSrgParser,
+ generateTSrg2Lexer,
+ generateTSrg2Parser,
+ generateJamLexer,
+ generateJamParser,
+ generateEnigmaLexer,
+ generateEnigmaParser,
+ generateTinyV1Lexer,
+ generateTinyV1Parser,
+ generateTinyV2Lexer,
+ generateTinyV2Parser,
+ generateProGuardLexer,
+ generateProGuardParser,
+ )
+}
+
+sourceSets.main { java.srcDir(generate) }
+
+// Remove gen directory on clean
+tasks.clean { delete(generate) }
+
+tasks.buildSearchableOptions {
+ enabled = false
+}
+
+license {
+ tasks {
+ register("gradle") {
+ files.from(
+ fileTree(project.projectDir) {
+ include("*.gradle.kts", "gradle.properties")
+ exclude("**/buildSrc/**", "**/build/**")
+ }
+ )
+ }
+ register("grammars") {
+ files.from(project.fileTree("src/main/grammars"))
+ }
+ }
+}
diff --git a/obfuscation-explorer/gradle.properties b/obfuscation-explorer/gradle.properties
new file mode 100644
index 000000000..ce780324a
--- /dev/null
+++ b/obfuscation-explorer/gradle.properties
@@ -0,0 +1,22 @@
+#
+# Minecraft Development for IntelliJ
+#
+# https://mcdev.io/
+#
+# Copyright (C) 2024 minecraft-dev
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, version 3.0 only.
+#
+# 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 Lesser General Public License
+# along with this program. If not, see .
+#
+
+# suppress inspection "UnusedProperty" for whole file
+coreVersion = 0.0.1
diff --git a/obfuscation-explorer/icons/csrg.svg b/obfuscation-explorer/icons/csrg.svg
new file mode 100644
index 000000000..ff6729b5a
--- /dev/null
+++ b/obfuscation-explorer/icons/csrg.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/obfuscation-explorer/icons/enigma.svg b/obfuscation-explorer/icons/enigma.svg
new file mode 100644
index 000000000..690bd23ce
--- /dev/null
+++ b/obfuscation-explorer/icons/enigma.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/obfuscation-explorer/icons/jam.svg b/obfuscation-explorer/icons/jam.svg
new file mode 100644
index 000000000..7c3e6e21c
--- /dev/null
+++ b/obfuscation-explorer/icons/jam.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/obfuscation-explorer/icons/pluginIcon.svg b/obfuscation-explorer/icons/pluginIcon.svg
new file mode 100644
index 000000000..ac32bf44e
--- /dev/null
+++ b/obfuscation-explorer/icons/pluginIcon.svg
@@ -0,0 +1,138 @@
+
+
diff --git a/obfuscation-explorer/icons/proguard.svg b/obfuscation-explorer/icons/proguard.svg
new file mode 100644
index 000000000..6e0d40573
--- /dev/null
+++ b/obfuscation-explorer/icons/proguard.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/obfuscation-explorer/icons/srg.svg b/obfuscation-explorer/icons/srg.svg
new file mode 100644
index 000000000..8e0238a87
--- /dev/null
+++ b/obfuscation-explorer/icons/srg.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/obfuscation-explorer/icons/tinyv1.svg b/obfuscation-explorer/icons/tinyv1.svg
new file mode 100644
index 000000000..814d242ee
--- /dev/null
+++ b/obfuscation-explorer/icons/tinyv1.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/obfuscation-explorer/icons/tinyv2.svg b/obfuscation-explorer/icons/tinyv2.svg
new file mode 100644
index 000000000..80af9323c
--- /dev/null
+++ b/obfuscation-explorer/icons/tinyv2.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/obfuscation-explorer/icons/tsrg.svg b/obfuscation-explorer/icons/tsrg.svg
new file mode 100644
index 000000000..1c921d25e
--- /dev/null
+++ b/obfuscation-explorer/icons/tsrg.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/obfuscation-explorer/icons/tsrg2.svg b/obfuscation-explorer/icons/tsrg2.svg
new file mode 100644
index 000000000..9dce962f3
--- /dev/null
+++ b/obfuscation-explorer/icons/tsrg2.svg
@@ -0,0 +1,81 @@
+
+
diff --git a/obfuscation-explorer/icons/xsrg.svg b/obfuscation-explorer/icons/xsrg.svg
new file mode 100644
index 000000000..d53176a98
--- /dev/null
+++ b/obfuscation-explorer/icons/xsrg.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/obfuscation-explorer/src/main/grammars/CSrgLexer.flex b/obfuscation-explorer/src/main/grammars/CSrgLexer.flex
new file mode 100644
index 000000000..0ec03ac8d
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/CSrgLexer.flex
@@ -0,0 +1,80 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.gen;
+
+import com.intellij.lexer.*;
+import com.intellij.psi.tree.IElementType;
+import static io.mcdev.obfex.formats.csrg.gen.psi.CSrgTypes.*;
+import static com.intellij.psi.TokenType.*;
+
+%%
+
+%{
+ public CSrgLexer() {
+ this((java.io.Reader)null);
+ }
+%}
+
+%public
+%class CSrgLexer
+%implements FlexLexer
+%function advance
+%type IElementType
+
+%s SIGNATURE
+
+%unicode
+
+// Name element is used for all parts of an identifier name
+// 1. The parts of the package
+// 2. The class name
+// 3. The member name
+NAME_ELEMENT=[\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*
+
+PRIMITIVE=[ZBCSIFDJV]
+CLASS_TYPE=(\[+[ZBCSIFDJ]|\[*L[^;\s]+;)
+
+COMMENT=\s*#.*
+WHITE_SPACE=\s
+CRLF=\r\n|\n|\r
+
+%%
+
+ {
+ "(" { yybegin(SIGNATURE); return OPEN_PAREN; }
+ "/" { return SLASH; }
+ {PRIMITIVE} {WHITE_SPACE}? { zzMarkedPos = zzStartRead + 1; return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {NAME_ELEMENT} { return NAME_ELEMENT; }
+}
+
+ {
+ ")" { return CLOSE_PAREN; }
+ {PRIMITIVE} { return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {CRLF} { yybegin(YYINITIAL); return CRLF; }
+ {WHITE_SPACE} { yybegin(YYINITIAL); return WHITE_SPACE; }
+}
+
+{COMMENT} { return COMMENT; }
+{CRLF} { yybegin(YYINITIAL); return CRLF; }
+{WHITE_SPACE} { return WHITE_SPACE; }
+[^] { return BAD_CHARACTER; }
diff --git a/obfuscation-explorer/src/main/grammars/CSrgParser.bnf b/obfuscation-explorer/src/main/grammars/CSrgParser.bnf
new file mode 100644
index 000000000..56a6f7acd
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/CSrgParser.bnf
@@ -0,0 +1,79 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+{
+ parserClass="io.mcdev.obfex.formats.csrg.gen.CSrgParser"
+
+ extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
+
+ psiClassPrefix="CSrg"
+ psiImplClassSuffix="Impl"
+ psiPackage="io.mcdev.obfex.formats.csrg.gen.psi"
+ psiImplPackage="io.mcdev.obfex.formats.csrg.gen.psi.impl"
+
+ elementTypeHolderClass="io.mcdev.obfex.formats.csrg.gen.psi.CSrgTypes"
+ elementTypeClass="io.mcdev.obfex.formats.csrg.lang.psi.CSrgElementType"
+ tokenTypeClass="io.mcdev.obfex.formats.csrg.lang.psi.CSrgTokenType"
+
+ tokens=[
+ COMMENT="regexp:\s*#.*"
+ ]
+
+ // do not record error reporting information in recover rules
+ consumeTokenMethod(".*_recover")="consumeTokenFast"
+}
+
+csrg_file ::= line*
+
+private line ::= !<> mapping? line_end {pin=2 recoverWhile=line_recover}
+private line_end ::= <> | CRLF
+private line_recover ::= !(mapping | line_end)
+
+mapping ::= method_mapping | field_mapping | class_mapping
+
+method_mapping ::= mapping_part mapping_part method_signature mapping_part {
+ methods=[
+ obfClassName="/mapping_part[0]"
+ obfMethodName="/mapping_part[1]"
+ obfSignature="/method_signature"
+ deobfMethodName="/mapping_part[2]"
+ ]
+}
+field_mapping ::= mapping_part mapping_part mapping_part {
+ methods=[
+ obfClassName="/mapping_part[0]"
+ obfFieldName="/mapping_part[1]"
+ deobfName="/mapping_part[2]"
+ ]
+}
+class_mapping ::= mapping_part mapping_part {
+ methods=[
+ obfName="/mapping_part[0]"
+ deobfName="/mapping_part[1]"
+ ]
+}
+
+mapping_part ::= PRIMITIVE | identifier
+type_desc ::= PRIMITIVE | CLASS_TYPE
+identifier ::= NAME_ELEMENT (SLASH NAME_ELEMENT)* {pin(".*")=1}
+
+method_signature ::= OPEN_PAREN param_types CLOSE_PAREN return_type
+param_types ::= type_desc*
+return_type ::= type_desc
diff --git a/obfuscation-explorer/src/main/grammars/EnigmaLexer.flex b/obfuscation-explorer/src/main/grammars/EnigmaLexer.flex
new file mode 100644
index 000000000..81f4bfc5a
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/EnigmaLexer.flex
@@ -0,0 +1,97 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.gen;
+
+import com.intellij.lexer.*;
+import com.intellij.psi.tree.IElementType;
+import static io.mcdev.obfex.formats.enigma.gen.psi.EnigmaTypes.*;
+import static com.intellij.psi.TokenType.*;
+
+%%
+
+%{
+ public EnigmaLexer() {
+ this((java.io.Reader)null);
+ }
+%}
+
+%public
+%class EnigmaLexer
+%implements FlexLexer
+%function advance
+%type IElementType
+
+%s DOC_LINE
+%s SIGNATURE
+
+%unicode
+
+// Name element is used for all parts of an identifier name
+// 1. The parts of the package
+// 2. The class name
+// 3. The member name
+NAME_ELEMENT=[\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*
+
+PRIMITIVE=[ZBCSIFDJV]
+CLASS_TYPE=(\[+[ZBCSIFDJ]|\[*L[^;\s]+;)
+INDEX=\d+
+
+COMMENT=\s*#.*
+DOC_TEXT=[^\r\n]+
+
+TAB=\t
+WHITE_SPACE=\s
+CRLF=\r\n|\n|\r
+
+%%
+
+ {
+ "CLASS" { return CLASS_KEY; }
+ "FIELD" { return FIELD_KEY; }
+ "METHOD" { return METHOD_KEY; }
+ "ARG" { return ARG_KEY; }
+ "COMMENT" { yybegin(DOC_LINE); return COMMENT_KEY; }
+ "(" { yybegin(SIGNATURE); return OPEN_PAREN; }
+ "/" { return SLASH; }
+ "" { return CONSTRUCTOR; }
+ {INDEX} { return INDEX; }
+ {PRIMITIVE} {WHITE_SPACE}? { zzMarkedPos = zzStartRead + 1; return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {NAME_ELEMENT} { return NAME_ELEMENT; }
+ {TAB} { return TAB; }
+}
+
+ {
+ {DOC_TEXT} { return DOC_TEXT; }
+}
+
+ {
+ ")" { return CLOSE_PAREN; }
+ {PRIMITIVE} { return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {CRLF} { yybegin(YYINITIAL); return CRLF; }
+ {WHITE_SPACE} { yybegin(YYINITIAL); return WHITE_SPACE; }
+}
+
+{COMMENT} { return COMMENT; }
+{CRLF} { yybegin(YYINITIAL); return CRLF; }
+{WHITE_SPACE} { return WHITE_SPACE; }
+[^] { return BAD_CHARACTER; }
diff --git a/obfuscation-explorer/src/main/grammars/EnigmaParser.bnf b/obfuscation-explorer/src/main/grammars/EnigmaParser.bnf
new file mode 100644
index 000000000..5491fe668
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/EnigmaParser.bnf
@@ -0,0 +1,78 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+{
+ parserClass="io.mcdev.obfex.formats.enigma.gen.EnigmaParser"
+
+ extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
+ parserUtilClass='io.mcdev.obfex.formats.enigma.lang.psi.EnigmaParserUtil'
+
+ psiClassPrefix="Enigma"
+ psiImplClassSuffix="Impl"
+ psiPackage="io.mcdev.obfex.formats.enigma.gen.psi"
+ psiImplPackage="io.mcdev.obfex.formats.enigma.gen.psi.impl"
+
+ elementTypeHolderClass="io.mcdev.obfex.formats.enigma.gen.psi.EnigmaTypes"
+ elementTypeClass="io.mcdev.obfex.formats.enigma.lang.psi.EnigmaElementType"
+ tokenTypeClass="io.mcdev.obfex.formats.enigma.lang.psi.EnigmaTokenType"
+
+ tokens=[
+ COMMENT="regexp:\s*#.*"
+ TAB="regexp:\t"
+ ]
+
+ consumeTokenMethod="consumeTokenFast"
+}
+
+enigma_file ::= entry*
+
+private entry ::= !<> class_mapping*
+private line_end ::= <> | CRLF+
+
+external indent ::= indent0 VIRTUAL_OPEN VIRTUAL_CLOSE <>
+
+class_mapping ::= CLASS_KEY class_mapping_part+ line_end <>? {pin=1}
+class_mapping_part ::= mapping_part | INDEX
+
+private class_children ::= doc_line* member_mapping*
+
+member_mapping ::= field_mapping | method_mapping | class_mapping {recoverWhile=member_recover}
+private member_recover ::= !(FIELD_KEY | METHOD_KEY | (VIRTUAL_CLOSE* CLASS_KEY))
+
+doc_line ::= COMMENT_KEY DOC_TEXT? line_end {pin=1}
+
+field_mapping ::= FIELD_KEY (<> mapping_part)+ type_desc line_end nested_docs? {pin=1}
+
+method_mapping ::= METHOD_KEY mapping_part+ method_signature line_end <>? {pin=1}
+
+private method_children ::= doc_line* arg_mapping*
+
+arg_mapping ::= ARG_KEY arg_index mapping_part line_end nested_docs? {pin=1}
+arg_index ::= INDEX
+
+private nested_docs ::= <>
+
+mapping_part ::= PRIMITIVE | CONSTRUCTOR | identifier
+type_desc ::= PRIMITIVE | CLASS_TYPE
+identifier ::= NAME_ELEMENT (SLASH NAME_ELEMENT)* {pin(".*")=1}
+
+method_signature ::= OPEN_PAREN param_types CLOSE_PAREN return_type
+param_types ::= type_desc*
+return_type ::= type_desc
diff --git a/obfuscation-explorer/src/main/grammars/JamLexer.flex b/obfuscation-explorer/src/main/grammars/JamLexer.flex
new file mode 100644
index 000000000..9877d2273
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/JamLexer.flex
@@ -0,0 +1,90 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.gen;
+
+import com.intellij.lexer.*;
+import com.intellij.psi.tree.IElementType;
+import static io.mcdev.obfex.formats.jam.gen.psi.JamTypes.*;
+import static com.intellij.psi.TokenType.*;
+
+%%
+
+%{
+ public JamLexer() {
+ this((java.io.Reader)null);
+ }
+%}
+
+%public
+%class JamLexer
+%implements FlexLexer
+%function advance
+%type IElementType
+
+%s ENTRY
+%s SIGNATURE
+
+%unicode
+
+// Name element is used for all parts of an identifier name
+// 1. The parts of the package
+// 2. The class name
+// 3. The member name
+NAME_ELEMENT=[\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*
+
+PRIMITIVE=[ZBCSIFDJV]
+CLASS_TYPE=(\[+[ZBCSIFDJ]|\[*L[^;\s]+;)
+INDEX=\d
+
+COMMENT=\s*#.*
+WHITE_SPACE=\s+
+CRLF=\r\n|\n|\r
+
+%%
+
+ {
+ "CL" { yybegin(ENTRY); return CLASS_KEY; }
+ "FD" { yybegin(ENTRY); return FIELD_KEY; }
+ "MD" { yybegin(ENTRY); return METHOD_KEY; }
+ "MP" { yybegin(ENTRY); return PARAM_KEY; }
+}
+
+ {
+ "(" { yybegin(SIGNATURE); return OPEN_PAREN; }
+ "/" { return SLASH; }
+ {INDEX} {WHITE_SPACE}? { zzMarkedPos = zzStartRead + 1; return INDEX; }
+ {PRIMITIVE} {WHITE_SPACE}? { zzMarkedPos = zzStartRead + 1; return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {NAME_ELEMENT} { return NAME_ELEMENT; }
+}
+
+ {
+ ")" { return CLOSE_PAREN; }
+ {PRIMITIVE} { return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {CRLF} { yybegin(YYINITIAL); return CRLF; }
+ {WHITE_SPACE} { yybegin(ENTRY); return WHITE_SPACE; }
+}
+
+{COMMENT} { return COMMENT; }
+{CRLF} { yybegin(YYINITIAL); return CRLF; }
+{WHITE_SPACE} { return WHITE_SPACE; }
+[^] { return BAD_CHARACTER; }
diff --git a/obfuscation-explorer/src/main/grammars/JamParser.bnf b/obfuscation-explorer/src/main/grammars/JamParser.bnf
new file mode 100644
index 000000000..7d5d330fd
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/JamParser.bnf
@@ -0,0 +1,93 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+{
+ parserClass="io.mcdev.obfex.formats.jam.gen.JamParser"
+
+ extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
+
+ psiClassPrefix="Jam"
+ psiImplClassSuffix="Impl"
+ psiPackage="io.mcdev.obfex.formats.jam.gen.psi"
+ psiImplPackage="io.mcdev.obfex.formats.jam.gen.psi.impl"
+
+ elementTypeHolderClass="io.mcdev.obfex.formats.jam.gen.psi.JamTypes"
+ elementTypeClass="io.mcdev.obfex.formats.jam.lang.psi.JamElementType"
+ tokenTypeClass="io.mcdev.obfex.formats.jam.lang.psi.JamTokenType"
+
+ tokens=[
+ COMMENT="regexp:\s*#.*"
+ ]
+
+ // do not record error reporting information in recover rules
+ consumeTokenMethod(".*_recover")="consumeTokenFast"
+}
+
+srg_file ::= line*
+
+private line ::= !<> mapping? line_end {pin=2 recoverWhile=line_recover}
+private line_end ::= <> | CRLF
+private line_recover ::= !(mapping | line_end)
+
+mapping ::= class_mapping | field_mapping | method_mapping | param_mapping
+
+class_mapping ::= CLASS_KEY mapping_part mapping_part {
+ pin=1
+ methods=[
+ obfName="/mapping_part[0]"
+ deobfName="/mapping_part[1]"
+ ]
+}
+field_mapping ::= FIELD_KEY mapping_part mapping_part type_desc mapping_part {
+ pin=1
+ methods=[
+ obfClassName="/mapping_part[0]"
+ obfFieldName="/mapping_part[1]"
+ obfType="/type_desc"
+ deobfName="/mapping_part[2]"
+ ]
+}
+method_mapping ::= METHOD_KEY mapping_part mapping_part method_signature mapping_part {
+ pin=1
+ methods=[
+ obfClassName="/mapping_part[0]"
+ obfMethodName="/mapping_part[1]"
+ obfSignature="/method_signature"
+ deobfName="/mapping_part[2]"
+ ]
+}
+param_mapping ::= PARAM_KEY mapping_part mapping_part method_signature mapping_part mapping_part {
+ pin=1
+ methods=[
+ obfClassName="/mapping_part[0]"
+ obfMethodName="/mapping_part[1]"
+ obfSignature="/method_signature"
+ parameterIndex="/mapping_part[2]"
+ deobfName="/mapping_part[3]"
+ ]
+}
+
+mapping_part ::= PRIMITIVE | identifier | INDEX
+type_desc ::= PRIMITIVE | CLASS_TYPE
+identifier ::= NAME_ELEMENT (SLASH NAME_ELEMENT)* {pin(".*")=1}
+
+method_signature ::= OPEN_PAREN param_types CLOSE_PAREN return_type {pin=1}
+param_types ::= type_desc*
+return_type ::= type_desc
diff --git a/obfuscation-explorer/src/main/grammars/ProGuardLexer.flex b/obfuscation-explorer/src/main/grammars/ProGuardLexer.flex
new file mode 100644
index 000000000..4881947ac
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/ProGuardLexer.flex
@@ -0,0 +1,78 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.gen;
+
+import com.intellij.lexer.*;
+import com.intellij.psi.tree.IElementType;
+import static io.mcdev.obfex.formats.proguard.gen.psi.ProGuardTypes.*;
+import static com.intellij.psi.TokenType.*;
+
+%%
+
+%{
+ public ProGuardLexer() {
+ this((java.io.Reader)null);
+ }
+%}
+
+%public
+%class ProGuardLexer
+%implements FlexLexer
+%function advance
+%type IElementType
+
+%s CORE
+
+%unicode
+
+NAME_ELEMENT=[\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*
+
+PRIMITIVE=void|boolean|char|byte|short|int|long|float|double
+
+NUMBER=\d
+POINTER=\s*->\s*
+
+COMMENT=\s*#.*
+
+WHITE_SPACE=\s
+CRLF=\r\n|\n|\r
+
+%%
+
+ {
+ ":" { return COLON; }
+ "." { return DOT; }
+ "," { return COMMA; }
+ "(" { return OPEN_PAREN; }
+ ")" { return CLOSE_PAREN; }
+ "" { return INIT; }
+ "" { return INIT; }
+ "[]" { return ARRAY_BRACKETS; }
+ "package-info" { return PACKAGE_INFO; }
+ {NUMBER}+ { return NUMBER; }
+ {POINTER} { return POINTER; }
+ {PRIMITIVE} { return PRIMITIVE; }
+ {NAME_ELEMENT} { return NAME_ELEMENT; }
+}
+
+{COMMENT} { return COMMENT; }
+{WHITE_SPACE} { return WHITE_SPACE; }
+[^] { return BAD_CHARACTER; }
diff --git a/obfuscation-explorer/src/main/grammars/ProGuardParser.bnf b/obfuscation-explorer/src/main/grammars/ProGuardParser.bnf
new file mode 100644
index 000000000..503c5cd0a
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/ProGuardParser.bnf
@@ -0,0 +1,69 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+{
+ parserClass="io.mcdev.obfex.formats.proguard.gen.ProGuardParser"
+
+ extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
+
+ psiClassPrefix="ProGuard"
+ psiImplClassSuffix="Impl"
+ psiPackage="io.mcdev.obfex.formats.proguard.gen.psi"
+ psiImplPackage="io.mcdev.obfex.formats.proguard.gen.psi.impl"
+
+ elementTypeHolderClass="io.mcdev.obfex.formats.proguard.gen.psi.ProGuardTypes"
+ elementTypeClass="io.mcdev.obfex.formats.proguard.lang.psi.ProGuardElementType"
+ tokenTypeClass="io.mcdev.obfex.formats.proguard.lang.psi.ProGuardTokenType"
+
+ tokens=[
+ COMMENT="regexp:\s*#.*"
+ ]
+}
+
+proguard_file ::= entry*
+
+private entry ::= !<> class_mapping?
+
+class_mapping ::= identifier POINTER identifier COLON member_mapping* {
+ methods=[
+ fromName="/class_name[0]"
+ toName="/class_name[1]"
+ ]
+}
+
+member_mapping ::= method_mapping | field_mapping
+
+field_mapping ::= type_desc field_name POINTER field_name {
+ methods=[
+ fromName="/field_name[0]"
+ toName="/field_name[1]"
+ ]
+}
+field_name ::= identifier | NUMBER
+
+method_mapping ::= (NUMBER COLON NUMBER COLON)? return_type method_name method_signature POINTER method_name
+return_type ::= type_desc
+method_name ::= identifier | INIT | CLINIT
+
+method_signature ::= OPEN_PAREN (param_type (COMMA param_type)*)? CLOSE_PAREN {pin=1}
+param_type ::= type_desc
+
+identifier ::= NAME_ELEMENT (DOT (NAME_ELEMENT | PACKAGE_INFO))* {pin(".*")=1}
+type_desc ::= (identifier | PRIMITIVE) ARRAY_BRACKETS*
diff --git a/obfuscation-explorer/src/main/grammars/SrgLexer.flex b/obfuscation-explorer/src/main/grammars/SrgLexer.flex
new file mode 100644
index 000000000..8c4018e0b
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/SrgLexer.flex
@@ -0,0 +1,96 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.gen;
+
+import com.intellij.lexer.*;
+import com.intellij.psi.tree.IElementType;
+import static io.mcdev.obfex.formats.srg.gen.psi.SrgTypes.*;
+import static com.intellij.psi.TokenType.*;
+
+%%
+
+%{
+ public SrgLexer() {
+ this((java.io.Reader)null);
+ }
+%}
+
+%public
+%class SrgLexer
+%implements FlexLexer
+%function advance
+%type IElementType
+
+%s PACKAGE_ENTRY
+%s ENTRY
+%s SIGNATURE
+
+%unicode
+
+// Package name entries just match until whitespace is hit
+PACKAGE_NAME=[^\s\r\n]+
+
+// Name element is used for all parts of an identifier name
+// 1. The parts of the package
+// 2. The class name
+// 3. The member name
+NAME_ELEMENT=[\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*
+
+PRIMITIVE=[ZBCSIFDJV]
+CLASS_TYPE=(\[+[ZBCSIFDJ]|\[*L[^;\s]+;)
+
+COMMENT=\s*#.*
+WHITE_SPACE=\s+
+CRLF=\r\n|\n|\r
+
+%%
+
+ {
+ "PK:" { yybegin(PACKAGE_ENTRY); return PACKAGE_KEY; }
+ "CL:" { yybegin(ENTRY); return CLASS_KEY; }
+ "FD:" { yybegin(ENTRY); return FIELD_KEY; }
+ "MD:" { yybegin(ENTRY); return METHOD_KEY; }
+}
+
+ {
+ {PACKAGE_NAME} { return PACKAGE_NAME; }
+}
+
+ {
+ "(" { yybegin(SIGNATURE); return OPEN_PAREN; }
+ "/" { return SLASH; }
+ {PRIMITIVE} {WHITE_SPACE}? { zzMarkedPos = zzStartRead + 1; return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {NAME_ELEMENT} { return NAME_ELEMENT; }
+}
+
+ {
+ ")" { return CLOSE_PAREN; }
+ {PRIMITIVE} { return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {CRLF} { yybegin(YYINITIAL); return CRLF; }
+ {WHITE_SPACE} { yybegin(ENTRY); return WHITE_SPACE; }
+}
+
+{COMMENT} { return COMMENT; }
+{CRLF} { yybegin(YYINITIAL); return CRLF; }
+{WHITE_SPACE} { return WHITE_SPACE; }
+[^] { return BAD_CHARACTER; }
diff --git a/obfuscation-explorer/src/main/grammars/SrgParser.bnf b/obfuscation-explorer/src/main/grammars/SrgParser.bnf
new file mode 100644
index 000000000..cffbc519d
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/SrgParser.bnf
@@ -0,0 +1,111 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+{
+ parserClass="io.mcdev.obfex.formats.srg.gen.SrgParser"
+
+ extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
+
+ psiClassPrefix="Srg"
+ psiImplClassSuffix="Impl"
+ psiPackage="io.mcdev.obfex.formats.srg.gen.psi"
+ psiImplPackage="io.mcdev.obfex.formats.srg.gen.psi.impl"
+
+ elementTypeHolderClass="io.mcdev.obfex.formats.srg.gen.psi.SrgTypes"
+ elementTypeClass="io.mcdev.obfex.formats.srg.lang.psi.SrgElementType"
+ tokenTypeClass="io.mcdev.obfex.formats.srg.lang.psi.SrgTokenType"
+
+ tokens=[
+ COMMENT="regexp:\s*#.*"
+ ]
+
+ // do not record error reporting information in recover rules
+ consumeTokenMethod(".*_recover")="consumeTokenFast"
+}
+
+srg_file ::= line*
+
+private line ::= !<> mapping? line_end {pin=2 recoverWhile=line_recover}
+private line_end ::= <> | CRLF
+private line_recover ::= !(mapping | line_end)
+
+mapping ::= package_mapping | class_mapping | field_mapping | method_mapping
+
+package_mapping ::= PACKAGE_KEY package_mapping_part package_mapping_part {
+ pin=1
+ methods=[
+ obfName="/package_mapping_part[0]"
+ deobfName="/package_mapping_part[1]"
+ ]
+}
+class_mapping ::= CLASS_KEY mapping_part mapping_part {
+ pin=1
+ methods=[
+ obfName="/mapping_part[0]"
+ deobfName="/mapping_part[1]"
+ ]
+}
+field_mapping ::= FIELD_KEY (extended_field_mapping | standard_field_mapping) {
+ pin=1
+ methods=[
+ obfName
+ obfSignature
+ deobfName
+ deobfSignature
+ ]
+ mixin="io.mcdev.obfex.formats.srg.lang.psi.mixins.impl.SrgFieldMappingImplMixin"
+ implements="io.mcdev.obfex.formats.srg.lang.psi.mixins.SrgFieldMappingMixin"
+}
+method_mapping ::= METHOD_KEY mapping_part method_signature mapping_part method_signature {
+ pin=1
+ methods=[
+ obfName="/mapping_part[0]"
+ obfSignature="/method_signature[0]"
+ deobfName="/mapping_part[1]"
+ deobfSignature="/method_signature[1]"
+ ]
+}
+
+package_mapping_part ::= PACKAGE_NAME
+
+standard_field_mapping ::= mapping_part mapping_part {
+ methods=[
+ obfName="/mapping_part[0]"
+ deobfName="/mapping_part[1]"
+ ]
+}
+extended_field_mapping ::= mapping_part type_desc mapping_part type_desc {
+ pin=3
+ methods=[
+ obfName="/mapping_part[0]"
+ obfType="/type_desc[0]"
+ deobfName="/mapping_part[1]"
+ deobfType="/type_desc[1]"
+ ]
+}
+
+mapping_part ::= PRIMITIVE | identifier
+type_desc ::= PRIMITIVE | CLASS_TYPE
+identifier ::= name_part (SLASH name_part)* {pin(".*")=1}
+name_part ::= NAME_ELEMENT
+
+method_signature ::= OPEN_PAREN param_types CLOSE_PAREN return_type {pin=1}
+param_types ::= type_desc*
+return_type ::= type_desc
diff --git a/obfuscation-explorer/src/main/grammars/TSrg2Lexer.flex b/obfuscation-explorer/src/main/grammars/TSrg2Lexer.flex
new file mode 100644
index 000000000..59a44a855
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/TSrg2Lexer.flex
@@ -0,0 +1,101 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.gen;
+
+import com.intellij.lexer.*;
+import com.intellij.psi.tree.IElementType;
+import static io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2Types.*;
+import static com.intellij.psi.TokenType.*;
+
+%%
+
+%{
+ public TSrg2Lexer() {
+ this((java.io.Reader)null);
+ }
+%}
+
+%public
+%class TSrg2Lexer
+%implements FlexLexer
+%function advance
+%type IElementType
+
+%s HEADER
+%s CORE
+%s SIGNATURE
+
+%unicode
+
+// Name element is used for all parts of an identifier name
+// 1. The parts of the package
+// 2. The class name
+// 3. The member name
+NAME_ELEMENT=[\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*
+
+PRIMITIVE=[ZBCSIFDJV]
+CLASS_TYPE=(\[+[ZBCSIFDJ]|\[*L[^;\s]+;)
+
+COMMENT=\s*#.*
+
+TAB=\t
+WHITE_SPACE=\s
+CRLF=\r\n|\n|\r
+NAMESPACE=\w
+DIGIT=\d
+
+%%
+
+ {
+ "tsrg2" { yybegin(HEADER); return TSRG2_KEY; }
+}
+
+ {
+ {NAMESPACE}+ { return NAMESPACE_KEY; }
+ {CRLF} { yybegin(CORE); return CRLF; }
+}
+
+ {
+ "(" { yybegin(SIGNATURE); return OPEN_PAREN; }
+ "/" { return SLASH; }
+ {PRIMITIVE} {WHITE_SPACE}? { zzMarkedPos = zzStartRead + 1; return PRIMITIVE; }
+ "" { return INIT; }
+ "" { return CLINIT; }
+ "static" { return STATIC; }
+ "package-info" { return NAME_ELEMENT; }
+ {DIGIT}+ { return DIGIT; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {NAME_ELEMENT} { return NAME_ELEMENT; }
+ {TAB} { return TAB; }
+}
+
+ {
+ ")" { return CLOSE_PAREN; }
+ {PRIMITIVE} { return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {CRLF} { yybegin(CORE); return CRLF; }
+ {WHITE_SPACE} { yybegin(CORE); return WHITE_SPACE; }
+}
+
+{COMMENT} { return COMMENT; }
+{CRLF} { yybegin(CORE); return CRLF; }
+{WHITE_SPACE} { return WHITE_SPACE; }
+[^] { return BAD_CHARACTER; }
diff --git a/obfuscation-explorer/src/main/grammars/TSrg2Parser.bnf b/obfuscation-explorer/src/main/grammars/TSrg2Parser.bnf
new file mode 100644
index 000000000..0653b6d20
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/TSrg2Parser.bnf
@@ -0,0 +1,81 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+{
+ parserClass="io.mcdev.obfex.formats.tsrg2.gen.TSrg2Parser"
+
+ extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
+ parserUtilClass='io.mcdev.obfex.formats.util.sigws.SignificantWhitespaceParserUtil'
+
+ psiClassPrefix="TSrg2"
+ psiImplClassSuffix="Impl"
+ psiPackage="io.mcdev.obfex.formats.tsrg2.gen.psi"
+ psiImplPackage="io.mcdev.obfex.formats.tsrg2.gen.psi.impl"
+
+ elementTypeHolderClass="io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2Types"
+ elementTypeClass="io.mcdev.obfex.formats.tsrg2.lang.psi.TSrg2ElementType"
+ tokenTypeClass="io.mcdev.obfex.formats.tsrg2.lang.psi.TSrg2TokenType"
+
+ tokens=[
+ COMMENT="regexp:\s*#.*"
+ TAB="regexp:\t"
+ ]
+
+ consumeTokenMethod="consumeTokenFast"
+}
+
+tsrg_file ::= file_element?
+file_element ::= header entry*
+
+private line_end ::= <> | CRLF+
+
+header ::= TSRG2_KEY namespace* line_end {pin=1}
+
+namespace ::= NAMESPACE_KEY
+
+private entry ::= !<> class_mapping?
+
+external indent ::= indent0 VIRTUAL_OPEN VIRTUAL_CLOSE <>
+
+class_mapping ::= mapping_part+ line_end <>? {pin=1 recoverWhile=mapping_recover}
+private mapping_recover ::= !(mapping_part | VIRTUAL_CLOSE)
+
+private class_children ::= member_mapping*
+member_mapping ::= method_mapping | field_mapping {recoverWhile=mapping_recover}
+
+method_mapping ::= mapping_part method_signature mapping_part+ line_end <>? {pin=2}
+private method_members ::= static_decl? param_mapping*
+
+field_mapping ::= mapping_part+ line_end <>? {pin=1}
+
+private static_decl ::= STATIC line_end {pin=1 recoverWhile=static_recover}
+private static_recover ::= !(VIRTUAL_CLOSE | DIGIT)
+
+mapping_part ::= PRIMITIVE | INIT | CLINIT | DIGIT | identifier
+type_desc ::= PRIMITIVE | CLASS_TYPE
+identifier ::= NAME_ELEMENT (SLASH NAME_ELEMENT)* {pin(".*")=1}
+
+method_signature ::= OPEN_PAREN param_types CLOSE_PAREN return_type {pin=1}
+param_types ::= type_desc*
+return_type ::= type_desc
+
+param_mapping ::= param_index mapping_part* line_end {pin=1 recoverWhile=param_recover}
+param_index ::= DIGIT
+private param_recover ::= !(VIRTUAL_CLOSE | DIGIT)
diff --git a/obfuscation-explorer/src/main/grammars/TSrgLexer.flex b/obfuscation-explorer/src/main/grammars/TSrgLexer.flex
new file mode 100644
index 000000000..778ea33a7
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/TSrgLexer.flex
@@ -0,0 +1,83 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.gen;
+
+import com.intellij.lexer.*;
+import com.intellij.psi.tree.IElementType;
+import static io.mcdev.obfex.formats.tsrg.gen.psi.TSrgTypes.*;
+import static com.intellij.psi.TokenType.*;
+
+%%
+
+%{
+ public TSrgLexer() {
+ this((java.io.Reader)null);
+ }
+%}
+
+%public
+%class TSrgLexer
+%implements FlexLexer
+%function advance
+%type IElementType
+
+%s SIGNATURE
+
+%unicode
+
+// Name element is used for all parts of an identifier name
+// 1. The parts of the package
+// 2. The class name
+// 3. The member name
+NAME_ELEMENT=[\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*
+
+PRIMITIVE=[ZBCSIFDJV]
+CLASS_TYPE=(\[+[ZBCSIFDJ]|\[*L[^;\s]+;)
+
+COMMENT=\s*#.*
+
+TAB=\t
+WHITE_SPACE=\s
+CRLF=\r\n|\n|\r
+
+%%
+
+ {
+ "(" { yybegin(SIGNATURE); return OPEN_PAREN; }
+ "/" { return SLASH; }
+ {TAB} { return TAB; }
+ {PRIMITIVE} {WHITE_SPACE}? { zzMarkedPos = zzStartRead + 1; return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {NAME_ELEMENT} { return NAME_ELEMENT; }
+}
+
+ {
+ ")" { return CLOSE_PAREN; }
+ {PRIMITIVE} { return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {CRLF} { yybegin(YYINITIAL); return CRLF; }
+ {WHITE_SPACE} { yybegin(YYINITIAL); return WHITE_SPACE; }
+}
+
+{COMMENT} { return COMMENT; }
+{CRLF} { yybegin(YYINITIAL); return CRLF; }
+{WHITE_SPACE} { return WHITE_SPACE; }
+[^] { return BAD_CHARACTER; }
diff --git a/obfuscation-explorer/src/main/grammars/TSrgParser.bnf b/obfuscation-explorer/src/main/grammars/TSrgParser.bnf
new file mode 100644
index 000000000..ff4eab6c7
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/TSrgParser.bnf
@@ -0,0 +1,70 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+{
+ parserClass="io.mcdev.obfex.formats.tsrg.gen.TSrgParser"
+
+ extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
+
+ psiClassPrefix="TSrg"
+ psiImplClassSuffix="Impl"
+ psiPackage="io.mcdev.obfex.formats.tsrg.gen.psi"
+ psiImplPackage="io.mcdev.obfex.formats.tsrg.gen.psi.impl"
+
+ elementTypeHolderClass="io.mcdev.obfex.formats.tsrg.gen.psi.TSrgTypes"
+ elementTypeClass="io.mcdev.obfex.formats.tsrg.lang.psi.TSrgElementType"
+ tokenTypeClass="io.mcdev.obfex.formats.tsrg.lang.psi.TSrgTokenType"
+
+ tokens=[
+ COMMENT="regexp:\s*#.*"
+ ]
+
+ // do not record error reporting information in recover rules
+ consumeTokenMethod(".*_recover")="consumeTokenFast"
+}
+
+tsrg_file ::= entry*
+
+private entry ::= !<> mapping? line_end {recoverWhile=line_recover}
+private line_end ::= <> | CRLF
+private line_recover ::= !(mapping | line_end)
+
+mapping ::= class_mapping (line_end member_mapping)*
+
+class_mapping ::= obf_class_mapping_part deobf_class_mapping_part
+obf_class_mapping_part ::= mapping_part
+deobf_class_mapping_part ::= mapping_part
+
+member_mapping ::= TAB (method_mapping | field_mapping)
+method_mapping ::= obf_method_mapping_part method_signature deobf_method_mapping_part
+obf_method_mapping_part ::= mapping_part
+deobf_method_mapping_part ::= mapping_part
+
+field_mapping ::= obf_field_mapping_part deobf_field_mapping_part
+obf_field_mapping_part ::= mapping_part
+deobf_field_mapping_part ::= mapping_part
+
+mapping_part ::= PRIMITIVE | identifier
+type_desc ::= PRIMITIVE | CLASS_TYPE
+identifier ::= NAME_ELEMENT (SLASH NAME_ELEMENT)* {pin(".*")=1}
+
+method_signature ::= OPEN_PAREN param_types CLOSE_PAREN return_type
+param_types ::= type_desc*
+return_type ::= type_desc
diff --git a/obfuscation-explorer/src/main/grammars/TinyV1Lexer.flex b/obfuscation-explorer/src/main/grammars/TinyV1Lexer.flex
new file mode 100644
index 000000000..0f839f463
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/TinyV1Lexer.flex
@@ -0,0 +1,93 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.gen;
+
+import com.intellij.lexer.*;
+import com.intellij.psi.tree.IElementType;
+import static io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1Types.*;
+import static com.intellij.psi.TokenType.*;
+
+%%
+
+%{
+ public TinyV1Lexer() {
+ this((java.io.Reader)null);
+ }
+%}
+
+%public
+%class TinyV1Lexer
+%implements FlexLexer
+%function advance
+%type IElementType
+
+%s NAMESPACES
+%s CORE
+%s ENTRY
+%s SIGNATURE
+
+%unicode
+
+NAMESPACE=[^\s]
+
+NAME_ELEMENT=[\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*
+PRIMITIVE=[ZBCSIFDJV]
+CLASS_TYPE=(\[+[ZBCSIFDJ]|\[*L[^;\s]+;)
+
+COMMENT=\s*#.*
+WHITE_SPACE=\s+
+CRLF=\r\n|\n|\r
+
+%%
+
+ {
+ "v1" { yybegin(NAMESPACES); return V1_KEY; }
+}
+
+ {
+ {NAMESPACE}+ { return NAMESPACE_KEY; }
+}
+
+ {
+ "CLASS" { yybegin(ENTRY); return CLASS_KEY; }
+ "FIELD" { yybegin(ENTRY); return FIELD_KEY; }
+ "METHOD" { yybegin(ENTRY); return METHOD_KEY; }
+}
+
+ {
+ "(" { yybegin(SIGNATURE); return OPEN_PAREN; }
+ "/" { return SLASH; }
+ {PRIMITIVE} {WHITE_SPACE}? { zzMarkedPos = zzStartRead + 1; return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {NAME_ELEMENT} { return NAME_ELEMENT; }
+}
+
+ {
+ ")" { return CLOSE_PAREN; }
+ {PRIMITIVE} { return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {WHITE_SPACE} { yybegin(ENTRY); return WHITE_SPACE; }
+}
+
+{COMMENT} { return COMMENT; }
+{CRLF} { yybegin(CORE); return CRLF; }
+{WHITE_SPACE} { return WHITE_SPACE; }
+[^] { return BAD_CHARACTER; }
diff --git a/obfuscation-explorer/src/main/grammars/TinyV1Parser.bnf b/obfuscation-explorer/src/main/grammars/TinyV1Parser.bnf
new file mode 100644
index 000000000..ae61cc24a
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/TinyV1Parser.bnf
@@ -0,0 +1,67 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+{
+ parserClass="io.mcdev.obfex.formats.tinyv1.gen.TinyV1Parser"
+
+ extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
+
+ psiClassPrefix="TinyV1"
+ psiImplClassSuffix="Impl"
+ psiPackage="io.mcdev.obfex.formats.tinyv1.gen.psi"
+ psiImplPackage="io.mcdev.obfex.formats.tinyv1.gen.psi.impl"
+
+ elementTypeHolderClass="io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1Types"
+ elementTypeClass="io.mcdev.obfex.formats.tinyv1.lang.psi.TinyV1ElementType"
+ tokenTypeClass="io.mcdev.obfex.formats.tinyv1.lang.psi.TinyV1TokenType"
+
+ tokens=[
+ COMMENT="regexp:\s*#.*"
+ ]
+
+ // do not record error reporting information in recover rules
+ consumeTokenMethod(".*_recover")="consumeTokenFast"
+}
+
+tinyv1_file ::= file_element?
+file_element ::= header line_end line*
+
+header ::= V1_KEY namespace* {pin=1}
+namespace ::= NAMESPACE_KEY
+
+private line_end ::= <> | CRLF
+private line_recover ::= !(mapping | line_end)
+
+private line ::= !<> mapping? line_end {pin=2 recoverWhile=line_recover}
+
+mapping ::= class_mapping | field_mapping | method_mapping
+
+class_mapping ::= CLASS_KEY mapping_part* {pin=1}
+field_mapping ::= FIELD_KEY mapping_part type_desc mapping_part* {pin=1}
+method_mapping ::= METHOD_KEY mapping_part method_signature mapping_part* {pin=1}
+
+mapping_part ::= PRIMITIVE | identifier
+type_desc ::= PRIMITIVE | CLASS_TYPE
+identifier ::= name_part (SLASH name_part)* {pin(".*")=1}
+name_part ::= NAME_ELEMENT
+
+method_signature ::= OPEN_PAREN param_types CLOSE_PAREN return_type {pin=1}
+param_types ::= type_desc*
+return_type ::= type_desc
diff --git a/obfuscation-explorer/src/main/grammars/TinyV2Lexer.flex b/obfuscation-explorer/src/main/grammars/TinyV2Lexer.flex
new file mode 100644
index 000000000..08fe089be
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/TinyV2Lexer.flex
@@ -0,0 +1,154 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.gen;
+
+import com.intellij.lexer.*;
+import com.intellij.psi.tree.IElementType;
+import static io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2Types.*;
+import static com.intellij.psi.TokenType.*;
+
+%%
+
+%{
+ private int versionNumCounter = 0;
+
+ public TinyV2Lexer() {
+ this((java.io.Reader)null);
+ }
+%}
+
+%public
+%class TinyV2Lexer
+%implements FlexLexer
+%function advance
+%type IElementType
+
+%s HEADER
+%s MAYBE_PROPERTIES
+%s PROPERTIES
+%s PROP_VALUE
+%s DOC_LINE
+%s CORE
+%s CORE2
+%s MAPPING
+%s PARAM_MAPPING
+%s LOCAL_VAR_MAPPING
+%s SIGNATURE
+
+%unicode
+
+SAFE_STRING=[^\\\n\r\t\0\s]
+PROP_VALUE=[^\s]
+
+DOC_TEXT=[^\r\n]+
+
+NAME_ELEMENT=[\p{L}_\p{Sc}][\p{L}\p{N}_\p{Sc}]*
+
+PRIMITIVE=[ZBCSIFDJV]
+CLASS_TYPE=(\[+[ZBCSIFDJ]|\[*L[^;\s]+;)
+
+DIGIT=\d
+
+COMMENT=\s*#.*
+
+WHITE_SPACE=\s
+CRLF=\r\n|\n|\r
+
+%%
+
+ {
+ "tiny" { yybegin(HEADER); return TINY_KEY; }
+}
+
+ {
+ {DIGIT}+ { if (versionNumCounter++ <= 2) return VERSION_NUM; else return NAMESPACE_KEY; }
+ {SAFE_STRING}+ { return NAMESPACE_KEY; }
+ {CRLF} { yybegin(MAYBE_PROPERTIES); return CRLF; }
+}
+
+ {
+ "c" { yybegin(MAPPING); return CLASS_KEY; }
+ {CRLF} { yybegin(CORE); return CRLF; }
+ {WHITE_SPACE} { yybegin(PROPERTIES); return WHITE_SPACE; }
+}
+
+ {
+ {SAFE_STRING}+ { return PROPERTY_KEY; }
+ {CRLF} { yybegin(MAYBE_PROPERTIES); return CRLF; }
+ {WHITE_SPACE} { yybegin(PROP_VALUE); return WHITE_SPACE; }
+}
+
+ {
+ {CRLF} { yybegin(MAYBE_PROPERTIES); return CRLF; }
+ {PROP_VALUE}+ { return PROPERTY_VALUE; }
+}
+
+ {
+ "c" { yybegin(MAPPING); return CLASS_KEY; }
+ {CRLF} { yybegin(CORE); return CRLF; }
+ {WHITE_SPACE} { yybegin(CORE2); return WHITE_SPACE; }
+}
+
+ {
+ "m" { yybegin(MAPPING); return METHOD_KEY; }
+ "f" { yybegin(MAPPING); return FIELD_KEY; }
+ "p" { yybegin(PARAM_MAPPING); return PARAM_KEY; }
+ "v" { yybegin(LOCAL_VAR_MAPPING); return VAR_KEY; }
+ "c" { yybegin(DOC_LINE); return COMMENT_KEY; }
+}
+
+ {
+ {DOC_TEXT} { return DOC_TEXT; }
+ {CRLF} { yybegin(CORE); return CRLF; }
+}
+
+ {
+ "(" { yybegin(SIGNATURE); return OPEN_PAREN; }
+ "/" { return SLASH; }
+ {PRIMITIVE} {WHITE_SPACE}? { zzMarkedPos = zzStartRead + 1; return PRIMITIVE; }
+ "" { return INIT; }
+ "" { return CLINIT; }
+ "package-info" { return NAME_ELEMENT; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {NAME_ELEMENT} { return NAME_ELEMENT; }
+}
+
+ {
+ {DIGIT}+ { yybegin(MAPPING); return DIGIT; }
+}
+
+ {
+ {DIGIT}+ { return DIGIT; }
+ {NAME_ELEMENT} { return NAME_ELEMENT; }
+}
+
+ {
+ ")" { return CLOSE_PAREN; }
+ {PRIMITIVE} { return PRIMITIVE; }
+ {CLASS_TYPE} { return CLASS_TYPE; }
+ {CRLF} { yybegin(CORE); return CRLF; }
+ {WHITE_SPACE} { yybegin(MAPPING); return WHITE_SPACE; }
+}
+
+{COMMENT} { return COMMENT; }
+{CRLF} { yybegin(CORE); return CRLF; }
+{WHITE_SPACE} { return WHITE_SPACE; }
+[^] { return BAD_CHARACTER; }
diff --git a/obfuscation-explorer/src/main/grammars/TinyV2Parser.bnf b/obfuscation-explorer/src/main/grammars/TinyV2Parser.bnf
new file mode 100644
index 000000000..5f9f7ef0f
--- /dev/null
+++ b/obfuscation-explorer/src/main/grammars/TinyV2Parser.bnf
@@ -0,0 +1,94 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+{
+ parserClass="io.mcdev.obfex.formats.tinyv2.gen.TinyV2Parser"
+
+ extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
+
+ psiClassPrefix="TinyV2"
+ psiImplClassSuffix="Impl"
+ psiPackage="io.mcdev.obfex.formats.tinyv2.gen.psi"
+ psiImplPackage="io.mcdev.obfex.formats.tinyv2.gen.psi.impl"
+
+ elementTypeHolderClass="io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2Types"
+ elementTypeClass="io.mcdev.obfex.formats.tinyv2.lang.psi.TinyV2ElementType"
+ tokenTypeClass="io.mcdev.obfex.formats.tinyv2.lang.psi.TinyV2TokenType"
+
+ tokens=[
+ COMMENT="regexp:\s*#.*"
+ ]
+
+// consumeTokenMethod="consumeTokenFast"
+}
+
+tinyv2_file ::= file_element?
+file_element ::= header (line_end property*)? class_mapping*
+
+header ::= TINY_KEY VERSION_NUM VERSION_NUM namespace* {pin=1 recoverWhile=header_recover} {
+ methods=[
+ majorVersionNum="/VERSION_NUM[0]"
+ minorVersionNum="/VERSION_NUM[1]"
+ ]
+}
+private header_recover ::= !line_end
+
+namespace ::= NAMESPACE_KEY
+
+property ::= PROPERTY_KEY PROPERTY_VALUE? line_end {
+ methods=[
+ propertyKey="/PROPERTY_KEY"
+ propergyValue="/PROPERTY_VALUE"
+ ]
+}
+
+private line_end ::= <> | CRLF+
+
+class_mapping ::= CLASS_KEY mapping_part* line_end class_member* {pin=1 recoverWhile=class_recover}
+private class_recover ::= !CLASS_KEY
+
+doc_line ::= COMMENT_KEY DOC_TEXT? line_end {pin=1}
+
+class_member ::= doc_line | method_mapping | field_mapping {recoverWhile=member_recover}
+private member_recover ::= !(METHOD_KEY | FIELD_KEY | CLASS_KEY)
+method_mapping ::= METHOD_KEY method_signature mapping_part* line_end method_member* {pin=1}
+
+method_member ::= doc_line | param_mapping | local_var_mapping
+
+param_mapping ::= PARAM_KEY param_index mapping_part* line_end doc_line* {pin=1 recoverWhile=param_recover}
+private param_recover ::= !(PARAM_KEY | VAR_KEY | METHOD_KEY | FIELD_KEY | CLASS_KEY)
+param_index ::= DIGIT
+
+local_var_mapping ::= VAR_KEY local_var_index local_var_start lvt_index? mapping_part* line_end doc_line* {pin=1 recoverWhile=local_var_recover}
+private local_var_recover ::= !(VAR_KEY | PARAM_KEY | METHOD_KEY | FIELD_KEY | CLASS_KEY)
+local_var_index ::= DIGIT
+local_var_start ::= DIGIT
+lvt_index ::= DIGIT
+
+field_mapping ::= FIELD_KEY type_desc mapping_part* line_end doc_line* {pin=1}
+
+mapping_part ::= PRIMITIVE | INIT | CLINIT | identifier
+
+type_desc ::= PRIMITIVE | CLASS_TYPE
+identifier ::= NAME_ELEMENT (SLASH NAME_ELEMENT)* {pin(".*")=1}
+
+method_signature ::= OPEN_PAREN param_types CLOSE_PAREN return_type {pin=1}
+param_types ::= type_desc*
+return_type ::= type_desc
diff --git a/obfuscation-explorer/src/main/kotlin/ObfIcons.kt b/obfuscation-explorer/src/main/kotlin/ObfIcons.kt
new file mode 100644
index 000000000..e115d251b
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/ObfIcons.kt
@@ -0,0 +1,44 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex
+
+import com.intellij.ui.IconManager
+import javax.swing.Icon
+
+object ObfIcons {
+
+ val OBF_EX_ICON = load("/fileTypes/obfex.svg")
+
+ val SRG_ICON = load("/fileTypes/srg.svg")
+ val TSRG_ICON = load("/fileTypes/tsrg.svg")
+ val TSRG2_ICON = load("/fileTypes/tsrg2.svg")
+ val CSRG_ICON = load("/fileTypes/csrg.svg")
+ val XSRG_ICON = load("/fileTypes/xsrg.svg")
+ val JAM_ICON = load("/fileTypes/jam.svg")
+ val ENIGMA_ICON = load("/fileTypes/enigma.svg")
+ val TINY_V1_ICON = load("/fileTypes/tinyv1.svg")
+ val TINY_V2_ICON = load("/fileTypes/tinyv2.svg")
+ val PROGUARD_ICONS = load("/fileTypes/proguard.svg")
+
+ private fun load(path: String): Icon {
+ return IconManager.getInstance().getIcon(path, ObfIcons::class.java.classLoader)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/facet/ObfExFacet.kt b/obfuscation-explorer/src/main/kotlin/facet/ObfExFacet.kt
new file mode 100644
index 000000000..68b8e6981
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/facet/ObfExFacet.kt
@@ -0,0 +1,44 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.facet
+
+import com.intellij.facet.Facet
+import com.intellij.facet.FacetTypeId
+import com.intellij.facet.FacetTypeRegistry
+import com.intellij.openapi.module.Module
+import io.mcdev.obfex.facet.ObfExFacetType.Companion.TYPE_ID
+
+class ObfExFacet(
+ module: Module,
+ name: String,
+ config: ObfExFacetConfiguration,
+ underlyingFacet: Facet<*>?
+) : Facet(facetType, module, name, config, underlyingFacet) {
+
+ companion object {
+ val ID = FacetTypeId(TYPE_ID)
+
+ val facetType: ObfExFacetType
+ get() = facetTypeOrNull as ObfExFacetType
+ val facetTypeOrNull: ObfExFacetType?
+ get() = FacetTypeRegistry.getInstance().findFacetType(TYPE_ID) as? ObfExFacetType
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/facet/ObfExFacetConfiguration.kt b/obfuscation-explorer/src/main/kotlin/facet/ObfExFacetConfiguration.kt
new file mode 100644
index 000000000..eacda08d9
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/facet/ObfExFacetConfiguration.kt
@@ -0,0 +1,66 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.facet
+
+import com.intellij.facet.FacetConfiguration
+import com.intellij.facet.ui.FacetEditorContext
+import com.intellij.facet.ui.FacetEditorTab
+import com.intellij.facet.ui.FacetValidatorsManager
+import com.intellij.openapi.components.PersistentStateComponent
+import com.intellij.openapi.components.State
+import com.intellij.util.xmlb.annotations.Attribute
+import com.intellij.util.xmlb.annotations.Tag
+import com.intellij.util.xmlb.annotations.XCollection
+import io.mcdev.obfex.facet.config.ObfExFacetEditorTab
+import io.mcdev.obfex.formats.MappingsFormatType
+
+@State(name = "ObfExFacet")
+class ObfExFacetConfiguration : FacetConfiguration, PersistentStateComponent {
+
+ private var state = ObfExFacetConfigurationData()
+
+ override fun createEditorTabs(
+ editorContext: FacetEditorContext?,
+ validatorsManager: FacetValidatorsManager?
+ ): Array {
+ return arrayOf(ObfExFacetEditorTab(this))
+ }
+
+ override fun getState() = state
+ override fun loadState(state: ObfExFacetConfigurationData) {
+ this.state = state
+ }
+}
+
+data class ObfExFacetConfigurationData(
+ @Tag("mappingTargets")
+ @XCollection(elementName = "mappingTarget", style = XCollection.Style.v2)
+ var mappingTargets: List = listOf(),
+)
+
+data class MappingTargetConfig(
+
+ @Attribute("type", converter = MappingsFormatType.Converter::class)
+ var type: MappingsFormatType? = null,
+
+ @Attribute("uri")
+ var uri: String? = null,
+)
diff --git a/obfuscation-explorer/src/main/kotlin/facet/ObfExFacetType.kt b/obfuscation-explorer/src/main/kotlin/facet/ObfExFacetType.kt
new file mode 100644
index 000000000..02d2dda8b
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/facet/ObfExFacetType.kt
@@ -0,0 +1,47 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.facet
+
+import com.intellij.facet.Facet
+import com.intellij.facet.FacetType
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.module.ModuleType
+import io.mcdev.obfex.ObfIcons
+
+class ObfExFacetType :
+ FacetType(ObfExFacet.ID, TYPE_ID, "Obfuscation Explorer") {
+
+ override fun createFacet(
+ module: Module,
+ name: String,
+ configuration: ObfExFacetConfiguration,
+ underlyingFacet: Facet<*>?
+ ) = ObfExFacet(module, name, configuration, underlyingFacet)
+
+ override fun createDefaultConfiguration() = ObfExFacetConfiguration()
+ override fun isSuitableModuleType(moduleType: ModuleType<*>?) = true
+
+ override fun getIcon() = ObfIcons.OBF_EX_ICON
+
+ companion object {
+ const val TYPE_ID = "obfex"
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/facet/config/MappingsTable.kt b/obfuscation-explorer/src/main/kotlin/facet/config/MappingsTable.kt
new file mode 100644
index 000000000..0bcee35d7
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/facet/config/MappingsTable.kt
@@ -0,0 +1,313 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.facet.config
+
+import com.intellij.openapi.actionSystem.AnAction
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.diagnostic.logger
+import com.intellij.openapi.fileChooser.FileChooser
+import com.intellij.openapi.fileChooser.FileChooserDescriptor
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.wm.IdeFocusManager
+import com.intellij.ui.ToolbarDecorator
+import com.intellij.ui.components.JBTextField
+import com.intellij.ui.components.textFieldWithBrowseButton
+import com.intellij.ui.table.JBTable
+import com.intellij.util.IconUtil
+import com.intellij.util.ui.AbstractTableCellEditor
+import io.mcdev.obfex.facet.MappingTargetConfig
+import io.mcdev.obfex.facet.ObfExFacetConfiguration
+import io.mcdev.obfex.formats.MappingsFormatType
+import io.mcdev.obfex.mappings.MappingsFile
+import io.mcdev.obfex.mappings.MappingsFormatParser
+import java.awt.Component
+import java.net.URI
+import java.net.URISyntaxException
+import java.nio.file.Paths
+import javax.swing.JComponent
+import javax.swing.JTable
+import javax.swing.ListSelectionModel
+import javax.swing.table.AbstractTableModel
+import javax.swing.table.DefaultTableCellRenderer
+
+class MappingsTable(private val config: ObfExFacetConfiguration) {
+
+ private val state: MutableList = configToState(config.state.mappingTargets).toMutableList()
+
+ private val model: MappingsFileTableModel = MappingsFileTableModel(state)
+ private val table: JBTable = createTable(model)
+ val panel: JComponent = createPanel(table, model)
+
+ fun stateToConfig(): List {
+ if (!table.isEditing) {
+ return state.map { MappingTargetConfig(it.type, it.uri?.toString() ?: "") }
+ }
+
+ val value = table.cellEditor.cellEditorValue as? URI
+ val currentRow = table.editingRow
+
+ return state.mapIndexed { i, row ->
+ if (i == currentRow) {
+ // TODO file type
+ MappingTargetConfig(null, value?.toString() ?: "")
+ } else {
+ MappingTargetConfig(row.type, row.uri?.toString() ?: "")
+ }
+ }
+ }
+
+ private fun configToState(list: List): List {
+ return list.map { value ->
+ val (type, uri) = value
+ if (type == null && uri.isNullOrBlank()) {
+ MappingsTableRow.NULL
+ } else {
+ MappingsTableRow(type, if (uri.isNullOrBlank()) null else URI.create(uri))
+ }
+ }
+ }
+
+ fun reset() {
+ state.clear()
+ state.addAll(configToState(config.state.mappingTargets))
+
+ table.clearSelection()
+ model.fireTableDataChanged()
+ }
+
+ private class MappingsFileTableModel(val state: MutableList) : AbstractTableModel() {
+ override fun getRowCount(): Int = state.size
+ override fun getColumnCount(): Int = 1
+ override fun getColumnClass(columnIndex: Int): Class<*> = URI::class.java
+ override fun getColumnName(column: Int): String = "Mappings Files"
+
+ override fun isCellEditable(rowIndex: Int, columnIndex: Int): Boolean = true
+
+ override fun getValueAt(rowIndex: Int, columnIndex: Int): Any {
+ return state[rowIndex]
+ }
+ override fun setValueAt(aValue: Any?, rowIndex: Int, columnIndex: Int) {
+ val uri = when (aValue) {
+ is URI -> aValue
+ is String -> aValue.toUriFromUserInput()
+ else -> return
+ }
+ val type: MappingsFormatType? = uri?.let {
+ val path = uri.path
+ val matchedParsers = MappingsFormatParser.EP_NAME.extensions.filter { parser ->
+ parser.expectedFileExtensions.any { ext -> path.endsWith(ext) }
+ }
+ if (matchedParsers.isEmpty()) {
+ null
+ } else if (matchedParsers.size == 1) {
+ // TODO Provide a separate optional API method for getting file type, for parsers which don't
+ // need to do a full parse to determine type
+ // TODO Make a util for this since it's repeated here
+ (matchedParsers.single().parse(toVirtualFile(uri))?.source as? MappingsFile)?.type
+ } else {
+ val file = toVirtualFile(uri)
+ val allMatching = matchedParsers.filterNot { parser ->
+ parser.isSupportedFile(file)
+ }
+ if (allMatching.isEmpty()) {
+ null
+ } else if (allMatching.size == 1) {
+ (allMatching.single().parse(toVirtualFile(uri))?.source as? MappingsFile)?.type
+ } else {
+ logger().warn(
+ "Multiple support mappings parsers found for URI: " +
+ "$uri; choosing first registered"
+ )
+ (allMatching.first().parse(toVirtualFile(uri))?.source as? MappingsFile)?.type
+ }
+ }
+ }
+ state[rowIndex] = MappingsTableRow(type, uri)
+ }
+
+ private fun toVirtualFile(uri: URI): VirtualFile {
+ // This needs to handle http uris as well - probably by downloading to the .idea directory or something maybe
+ // need to have an external file manager for handling that, keep track of etags, redownload on change, etc
+ TODO()
+ }
+ }
+
+ private class Editor(
+ private val model: MappingsFileTableModel,
+ private val state: MutableList = model.state,
+ ) : AbstractTableCellEditor() {
+ private val textField = textFieldWithBrowseButton(null, "Mappings Files", fileDesc, null)
+ private var row: Int = -1
+
+ override fun getCellEditorValue(): Any? {
+ return textField.text.toUriFromUserInput()
+ }
+
+ override fun getTableCellEditorComponent(
+ table: JTable?,
+ value: Any?,
+ isSelected: Boolean,
+ row: Int,
+ column: Int
+ ): Component {
+ this.row = row
+ textField.text = when (value) {
+ is URI -> value.toPresentableText()
+ else -> ""
+ }
+ return textField
+ }
+
+ override fun stopCellEditing(): Boolean {
+ if (row != -1 && state.size > row) {
+ val uri = textField.text.toUriFromUserInput()
+ state[row] = MappingsTableRow(null, uri)
+ model.fireTableCellUpdated(row, 0)
+ }
+ textField.text = ""
+ return super.stopCellEditing()
+ }
+ }
+
+ private object Renderer : DefaultTableCellRenderer() {
+ private fun readResolve(): Any = Renderer
+
+ override fun getTableCellRendererComponent(
+ table: JTable?,
+ value: Any?,
+ isSelected: Boolean,
+ hasFocus: Boolean,
+ row: Int,
+ column: Int
+ ): Component {
+ val field = JBTextField()
+ field.text = when (value) {
+ is URI -> value.toPresentableText()
+ is String -> value
+ else -> ""
+ }
+ field.isEditable = false
+
+ return field
+ }
+ }
+
+ companion object {
+ private fun createTable(model: MappingsFileTableModel): JBTable {
+ val table = JBTable(model)
+
+ table.setShowGrid(true)
+ table.setEnableAntialiasing(true)
+
+ table.emptyText.text = "No mappings files"
+
+ table.visibleRowCount = 8
+
+ table.setDefaultRenderer(URI::class.java, Renderer)
+ table.setDefaultEditor(URI::class.java, Editor(model))
+
+ table.selectionModel.selectionMode = ListSelectionModel.SINGLE_SELECTION
+
+ return table
+ }
+
+ private fun createPanel(table: JBTable, model: MappingsFileTableModel): JComponent {
+ val state = model.state
+
+ return ToolbarDecorator.createDecorator(table)
+ .disableUpDownActions()
+ .setAddAction {
+ val initial = state.size
+
+ val chosenFiles = FileChooser.chooseFiles(fileDesc, null, null)
+ for (chosenFile in chosenFiles) {
+ state.add(MappingsTableRow(null, chosenFile.toNioPath().toUri()))
+ }
+ model.fireTableRowsInserted(initial, state.lastIndex)
+ }
+ .setAddIcon(IconUtil.addPackageIcon)
+ .setAddActionName("Add File")
+ .addExtraAction(object : AnAction(
+ "Add Text",
+ "Create blank entry for manual input",
+ IconUtil.addBlankLineIcon
+ ) {
+ override fun actionPerformed(e: AnActionEvent) {
+ state.add(MappingsTableRow.NULL)
+ if (table.isEditing) {
+ table.cellEditor.stopCellEditing()
+ }
+ model.fireTableRowsInserted(state.lastIndex - 1, state.lastIndex)
+
+ IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown {
+ IdeFocusManager.getGlobalInstance().requestFocus(table, true)
+
+ table.changeSelection(state.lastIndex, 0, false, false)
+ table.editCellAt(state.lastIndex, 0)
+ }
+ }
+ })
+ .setRemoveAction {
+ val selected = table.selectedRow
+ if (selected == -1) {
+ return@setRemoveAction
+ }
+ if (table.isEditing) {
+ table.cellEditor?.stopCellEditing()
+ }
+
+ state.removeAt(selected)
+ model.fireTableRowsDeleted(selected, selected)
+
+ IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown {
+ IdeFocusManager.getGlobalInstance().requestFocus(table, true)
+ }
+ }
+ .setButtonComparator("Add File", "Add Text", "Remove")
+ .createPanel()
+ }
+
+ private fun URI.toPresentableText(): String {
+ if (scheme == "file") {
+ return Paths.get(this).toAbsolutePath().toString()
+ }
+ return toString()
+ }
+
+ private fun String.toUriFromUserInput(): URI? {
+ if (isBlank()) {
+ return null
+ }
+ val uri = try {
+ URI(this)
+ } catch (_: URISyntaxException) {
+ return null
+ }
+ if (uri.scheme == null) {
+ // plain file path
+ return Paths.get(this).toUri()
+ }
+ return uri
+ }
+
+ private val fileDesc = FileChooserDescriptor(true, false, false, false, false, true)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/facet/config/MappingsTableCell.kt b/obfuscation-explorer/src/main/kotlin/facet/config/MappingsTableCell.kt
new file mode 100644
index 000000000..3ea2c4133
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/facet/config/MappingsTableCell.kt
@@ -0,0 +1,39 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.facet.config
+
+import com.intellij.ui.dsl.builder.panel
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsFile
+import java.awt.Component
+
+object MappingsTableCell {
+
+ fun createComponent(def: MappingsDefinition): Component = panel {
+ row {
+ when (val s = def.source) {
+ is MappingsFile -> {
+ icon(s.type.icon)
+ }
+ }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/facet/config/MappingsTableRow.kt b/obfuscation-explorer/src/main/kotlin/facet/config/MappingsTableRow.kt
new file mode 100644
index 000000000..abfe6a661
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/facet/config/MappingsTableRow.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.facet.config
+
+import io.mcdev.obfex.formats.MappingsFormatType
+import java.net.URI
+
+data class MappingsTableRow(
+ val type: MappingsFormatType? = null,
+ val uri: URI? = null,
+) {
+ companion object {
+ val NULL = MappingsTableRow()
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/facet/config/ObfExFacetEditorTab.kt b/obfuscation-explorer/src/main/kotlin/facet/config/ObfExFacetEditorTab.kt
new file mode 100644
index 000000000..4c54f1d83
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/facet/config/ObfExFacetEditorTab.kt
@@ -0,0 +1,55 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.facet.config
+
+import com.intellij.facet.ui.FacetEditorTab
+import com.intellij.ui.dsl.builder.AlignX
+import com.intellij.ui.dsl.builder.panel
+import io.mcdev.obfex.facet.ObfExFacetConfiguration
+import javax.swing.JComponent
+
+class ObfExFacetEditorTab(private val config: ObfExFacetConfiguration) : FacetEditorTab() {
+
+ private val table = MappingsTable(config)
+
+ override fun createComponent(): JComponent {
+ return panel {
+ row {
+ cell(table.panel)
+ .align(AlignX.FILL)
+ }
+ }
+ }
+
+ override fun isModified(): Boolean {
+ return config.state.mappingTargets != table.stateToConfig()
+ }
+
+ override fun getDisplayName(): String = "Obfuscation Explorer Module Settings"
+
+ override fun reset() {
+ table.reset()
+ }
+
+ override fun apply() {
+ config.state.mappingTargets = table.stateToConfig()
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/MappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/MappingsFormatType.kt
new file mode 100644
index 000000000..7281318d7
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/MappingsFormatType.kt
@@ -0,0 +1,41 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats
+
+import com.intellij.util.xmlb.Converter as IConverter
+import javax.swing.Icon
+import org.jetbrains.annotations.NonNls
+
+abstract class MappingsFormatType(@param:NonNls val id: String) {
+
+ abstract val icon: Icon
+ abstract val name: String
+
+ class Converter : IConverter() {
+ override fun toString(value: MappingsFormatType): String? {
+ return value.id
+ }
+
+ override fun fromString(value: String): MappingsFormatType? {
+ return MappingsFormatTypeManager.get().registeredTypes.firstOrNull { it.id == value }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/MappingsFormatTypeManager.kt b/obfuscation-explorer/src/main/kotlin/formats/MappingsFormatTypeManager.kt
new file mode 100644
index 000000000..a757981ea
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/MappingsFormatTypeManager.kt
@@ -0,0 +1,43 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats
+
+import java.util.Collections
+
+class MappingsFormatTypeManager {
+
+ private val types: HashMap = hashMapOf()
+
+ fun registerType(type: MappingsFormatType) {
+ if (types.containsKey(type.id)) {
+ throw IllegalArgumentException("Cannot register mappings format type: ${type.id}; Already registered")
+ }
+ types[type.id] = type
+ }
+
+ val registeredTypes: Collection
+ get() = Collections.unmodifiableCollection(types.values)
+
+ companion object {
+ private val instance = MappingsFormatTypeManager()
+ fun get() = instance
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/CSrgMappingsFormatParser.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/CSrgMappingsFormatParser.kt
new file mode 100644
index 000000000..9612623c9
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/CSrgMappingsFormatParser.kt
@@ -0,0 +1,109 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg
+
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.MappingPart
+import io.mcdev.obfex.forLines
+import io.mcdev.obfex.mappings.FileCoords
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsDefinitionBuilder
+import io.mcdev.obfex.mappings.UnnamedMappingsFormatParser
+import io.mcdev.obfex.mappings.clazz
+import io.mcdev.obfex.mappings.field
+import io.mcdev.obfex.mappings.method
+import io.mcdev.obfex.ref.MethodDescriptor
+import io.mcdev.obfex.ref.asClass
+import io.mcdev.obfex.ref.asField
+import io.mcdev.obfex.ref.asMethod
+import io.mcdev.obfex.ref.highPriority
+import io.mcdev.obfex.ref.lowPriority
+
+class CSrgMappingsFormatParser : UnnamedMappingsFormatParser {
+
+ override val expectedFileExtensions: Array = arrayOf("csrg")
+
+ override fun parse(file: VirtualFile): MappingsDefinition {
+ val builder = MappingsDefinitionBuilder(CSrgMappingsFormatType, file)
+
+ file.forLines { lineNum, parts, line ->
+ when (parts.size) {
+ 2 -> parseClassLine(builder, lineNum, parts[0], parts[1])
+ 3 -> parseFieldLine(builder, lineNum, parts[0], parts[1], parts[2])
+ 4 -> parseMethodLine(builder, lineNum, parts[0], parts[1], parts[2], parts[3])
+ else -> {
+ builder.error("Unrecognized line: $line", FileCoords(lineNum))
+ }
+ }
+
+ true
+ }
+
+ return builder.build()
+ }
+
+ private fun parseClassLine(
+ builder: MappingsDefinitionBuilder,
+ lineNum: Int,
+ leftClass: MappingPart,
+ rightClass: MappingPart,
+ ) {
+ builder.clazz(lineNum.highPriority) {
+ with(leftClass.asClass().from)
+ with(rightClass.asClass().to)
+ }
+ }
+
+ private fun parseFieldLine(
+ builder: MappingsDefinitionBuilder,
+ lineNum: Int,
+ className: MappingPart,
+ leftField: MappingPart,
+ rightField: MappingPart,
+ ) {
+ builder.clazz(className.asClass().from, lineNum.lowPriority).field(lineNum.highPriority) {
+ with(leftField.asField().from)
+ with(rightField.asField().to)
+ }
+ }
+
+ private fun parseMethodLine(
+ builder: MappingsDefinitionBuilder,
+ lineNum: Int,
+ className: MappingPart,
+ leftMethod: MappingPart,
+ methodDesc: MappingPart,
+ rightMethod: MappingPart,
+ ) {
+ val descParsed = MethodDescriptor.parse(methodDesc)
+ if (descParsed == null) {
+ builder.error("Invalid method descriptor: ${methodDesc.value}", FileCoords(lineNum, methodDesc))
+ return
+ }
+
+ builder.clazz(className.asClass().from, lineNum.lowPriority).method(lineNum.highPriority) {
+ desc = descParsed
+
+ with(leftMethod.asMethod().from)
+ with(rightMethod.asMethod().to)
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/CSrgMappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/CSrgMappingsFormatType.kt
new file mode 100644
index 000000000..60ff641b5
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/CSrgMappingsFormatType.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg
+
+import io.mcdev.obfex.ObfIcons
+import io.mcdev.obfex.formats.MappingsFormatType
+import javax.swing.Icon
+
+object CSrgMappingsFormatType : MappingsFormatType("CSRG") {
+
+ override val icon: Icon = ObfIcons.CSRG_ICON
+ override val name: String = "CSRG"
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgFile.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgFile.kt
new file mode 100644
index 000000000..3210479dd
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgFile.kt
@@ -0,0 +1,30 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.lang
+
+import com.intellij.extapi.psi.PsiFileBase
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.psi.FileViewProvider
+
+class CSrgFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, CSrgLanguage) {
+ override fun getFileType(): FileType = CSrgFileType
+ override fun toString(): String = CSrgFileType.description
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgFileType.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgFileType.kt
new file mode 100644
index 000000000..28547b243
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgFileType.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import io.mcdev.obfex.ObfIcons
+import javax.swing.Icon
+
+object CSrgFileType : LanguageFileType(CSrgLanguage) {
+
+ override fun getName(): String = "CSRG"
+ override fun getDescription(): String = "CSRG obfuscation mapping file"
+ override fun getDefaultExtension(): String = "csrg"
+ override fun getIcon(): Icon = ObfIcons.CSRG_ICON
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgLanguage.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgLanguage.kt
new file mode 100644
index 000000000..20aa671a8
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgLanguage.kt
@@ -0,0 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.lang
+
+import com.intellij.lang.Language
+
+object CSrgLanguage : Language("CSRG")
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgParserDefinition.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgParserDefinition.kt
new file mode 100644
index 000000000..a3f420f6c
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/CSrgParserDefinition.kt
@@ -0,0 +1,50 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.lang
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.Language
+import com.intellij.lang.ParserDefinition
+import com.intellij.lang.PsiParser
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.project.Project
+import com.intellij.psi.FileViewProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.tree.IFileElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.csrg.gen.CSrgParser
+import io.mcdev.obfex.formats.csrg.gen.psi.CSrgTypes
+import io.mcdev.obfex.formats.csrg.lang.psi.CSrgLexerAdapter
+
+class CSrgParserDefinition : ParserDefinition {
+
+ override fun createLexer(project: Project): Lexer = CSrgLexerAdapter()
+ override fun getCommentTokens(): TokenSet = COMMENTS
+ override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
+ override fun createParser(project: Project): PsiParser = CSrgParser()
+ override fun getFileNodeType(): IFileElementType = FILE
+ override fun createFile(viewProvider: FileViewProvider): PsiFile = CSrgFile(viewProvider)
+ override fun createElement(node: ASTNode): PsiElement = CSrgTypes.Factory.createElement(node)
+}
+
+private val COMMENTS = TokenSet.create(CSrgTypes.COMMENT)
+private val FILE = IFileElementType(Language.findInstance(CSrgLanguage::class.java))
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/colors/CSrgAnnotator.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/colors/CSrgAnnotator.kt
new file mode 100644
index 000000000..7bd29d121
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/colors/CSrgAnnotator.kt
@@ -0,0 +1,50 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.lang.colors
+
+import com.intellij.lang.annotation.AnnotationHolder
+import com.intellij.lang.annotation.Annotator
+import com.intellij.psi.PsiElement
+import io.mcdev.obfex.formats.csrg.gen.psi.CSrgClassMapping
+import io.mcdev.obfex.formats.csrg.gen.psi.CSrgFieldMapping
+import io.mcdev.obfex.formats.csrg.gen.psi.CSrgMethodMapping
+import io.mcdev.obfex.formats.util.registerHighlight
+
+class CSrgAnnotator : Annotator {
+ override fun annotate(element: PsiElement, holder: AnnotationHolder) {
+ when (element) {
+ is CSrgClassMapping -> {
+ registerHighlight(element.obfName, CSrgSyntaxHighlighter.CLASS_NAME, holder)
+ registerHighlight(element.deobfName, CSrgSyntaxHighlighter.CLASS_NAME, holder, true)
+ }
+ is CSrgMethodMapping -> {
+ registerHighlight(element.obfClassName, CSrgSyntaxHighlighter.CLASS_NAME, holder)
+ registerHighlight(element.obfMethodName, CSrgSyntaxHighlighter.METHOD, holder)
+ registerHighlight(element.deobfMethodName, CSrgSyntaxHighlighter.METHOD, holder, true)
+ }
+ is CSrgFieldMapping -> {
+ registerHighlight(element.obfClassName, CSrgSyntaxHighlighter.CLASS_NAME, holder)
+ registerHighlight(element.obfFieldName, CSrgSyntaxHighlighter.FIELD, holder)
+ registerHighlight(element.deobfName, CSrgSyntaxHighlighter.FIELD, holder, true)
+ }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/colors/CSrgSyntaxHighlighter.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/colors/CSrgSyntaxHighlighter.kt
new file mode 100644
index 000000000..0b026674f
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/colors/CSrgSyntaxHighlighter.kt
@@ -0,0 +1,71 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.lang.colors
+
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.csrg.gen.psi.CSrgTypes
+import io.mcdev.obfex.formats.csrg.lang.psi.CSrgLexerAdapter
+
+class CSrgSyntaxHighlighter : SyntaxHighlighterBase() {
+ override fun getHighlightingLexer() = CSrgLexerAdapter()
+
+ override fun getTokenHighlights(tokenType: IElementType?): Array =
+ when (tokenType) {
+ CSrgTypes.CLASS_TYPE -> CLASS_TYPE_KEYS
+ CSrgTypes.PRIMITIVE -> PRIMITIVE_KEYS
+ CSrgTypes.COMMENT -> COMMENT_KEYS
+ else -> TextAttributesKey.EMPTY_ARRAY
+ }
+
+ companion object {
+ val CLASS_NAME = TextAttributesKey.createTextAttributesKey(
+ "CSRG_CLASS_NAME",
+ DefaultLanguageHighlighterColors.STRING
+ )
+ val METHOD = TextAttributesKey.createTextAttributesKey(
+ "CSRG_METHOD",
+ DefaultLanguageHighlighterColors.INSTANCE_METHOD
+ )
+ val FIELD = TextAttributesKey.createTextAttributesKey(
+ "CSRG_FIELD",
+ DefaultLanguageHighlighterColors.INSTANCE_FIELD
+ )
+ val PRIMITIVE = TextAttributesKey.createTextAttributesKey(
+ "CSRG_PRIMITIVE",
+ DefaultLanguageHighlighterColors.NUMBER
+ )
+ val CLASS_TYPE = TextAttributesKey.createTextAttributesKey(
+ "CSRG_CLASS_TYPE",
+ DefaultLanguageHighlighterColors.CLASS_REFERENCE
+ )
+ val COMMENT = TextAttributesKey.createTextAttributesKey(
+ "CSRG_COMMENT",
+ DefaultLanguageHighlighterColors.LINE_COMMENT
+ )
+
+ private val PRIMITIVE_KEYS = arrayOf(PRIMITIVE)
+ private val CLASS_TYPE_KEYS = arrayOf(CLASS_TYPE)
+ private val COMMENT_KEYS = arrayOf(COMMENT)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/colors/CSrgSyntaxHighlighterFactory.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/colors/CSrgSyntaxHighlighterFactory.kt
new file mode 100644
index 000000000..3bf0c2ecd
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/colors/CSrgSyntaxHighlighterFactory.kt
@@ -0,0 +1,29 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.lang.colors
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+
+class CSrgSyntaxHighlighterFactory : SyntaxHighlighterFactory() {
+ override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?) = CSrgSyntaxHighlighter()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/psi/CSrgElementType.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/psi/CSrgElementType.kt
new file mode 100644
index 000000000..3692d908d
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/psi/CSrgElementType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.csrg.lang.CSrgLanguage
+import org.jetbrains.annotations.NonNls
+
+class CSrgElementType(@NonNls debugName: String) : IElementType(debugName, CSrgLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/psi/CSrgLexerAdapter.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/psi/CSrgLexerAdapter.kt
new file mode 100644
index 000000000..360bddc6c
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/psi/CSrgLexerAdapter.kt
@@ -0,0 +1,26 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.lang.psi
+
+import com.intellij.lexer.FlexAdapter
+import io.mcdev.obfex.formats.csrg.gen.CSrgLexer
+
+class CSrgLexerAdapter : FlexAdapter(CSrgLexer())
diff --git a/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/psi/CSrgTokenType.kt b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/psi/CSrgTokenType.kt
new file mode 100644
index 000000000..d5edfbe72
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/csrg/lang/psi/CSrgTokenType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.csrg.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.csrg.lang.CSrgLanguage
+import org.jetbrains.annotations.NonNls
+
+class CSrgTokenType(@NonNls debugName: String) : IElementType(debugName, CSrgLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/EnigmaLanguageCodeStyleSettingsProvider.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/EnigmaLanguageCodeStyleSettingsProvider.kt
new file mode 100644
index 000000000..57dfaf832
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/EnigmaLanguageCodeStyleSettingsProvider.kt
@@ -0,0 +1,46 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma
+
+import io.mcdev.obfex.formats.enigma.lang.EnigmaLanguage
+import io.mcdev.obfex.formats.util.TabCodeStyleSettingsProvider
+import org.intellij.lang.annotations.Language
+
+class EnigmaLanguageCodeStyleSettingsProvider : TabCodeStyleSettingsProvider() {
+ override fun getLanguage() = EnigmaLanguage
+ override fun getCodeSample(settingsType: SettingsType) = SAMPLE
+}
+
+@Language("Enigma")
+private const val SAMPLE = """
+CLASS net/minecraft/class_6489 net/minecraft/GameVersion
+ COMMENT The game version interface used by Minecraft, replacing the javabridge
+ COMMENT one's occurrences in Minecraft code.
+ METHOD method_37912 getSaveVersion ()Lnet/minecraft/class_6595;
+ COMMENT {@return the save version information for this game version}
+ METHOD method_48017 getResourceVersion (Lnet/minecraft/class_3264;)I
+ ARG 1 type
+ METHOD method_48018 getId ()Ljava/lang/String;
+ METHOD method_48019 getName ()Ljava/lang/String;
+ METHOD method_48020 getProtocolVersion ()I
+ METHOD method_48021 getBuildTime ()Ljava/util/Date;
+ METHOD method_48022 isStable ()Z
+"""
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/EnigmaMappingsFormatParser.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/EnigmaMappingsFormatParser.kt
new file mode 100644
index 000000000..9f0aec3a0
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/EnigmaMappingsFormatParser.kt
@@ -0,0 +1,218 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma
+
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.MappingPart
+import io.mcdev.obfex.forLinesIndent
+import io.mcdev.obfex.formats.util.indicesFrom
+import io.mcdev.obfex.mappings.ClassMappingBuilder
+import io.mcdev.obfex.mappings.FileCoords
+import io.mcdev.obfex.mappings.MappingIssuesRegistry
+import io.mcdev.obfex.mappings.MappingSetBuilderCore
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsDefinitionBuilder
+import io.mcdev.obfex.mappings.MethodMappingBuilder
+import io.mcdev.obfex.mappings.UnnamedMappingsFormatParser
+import io.mcdev.obfex.mappings.clazz
+import io.mcdev.obfex.mappings.field
+import io.mcdev.obfex.mappings.method
+import io.mcdev.obfex.mappings.ns
+import io.mcdev.obfex.mappings.param
+import io.mcdev.obfex.ref.asClass
+import io.mcdev.obfex.ref.asField
+import io.mcdev.obfex.ref.asMethod
+import io.mcdev.obfex.ref.asMethodDesc
+import io.mcdev.obfex.ref.asParam
+import io.mcdev.obfex.ref.asParamIndex
+import io.mcdev.obfex.ref.asTypeDef
+import io.mcdev.obfex.ref.highPriority
+
+class EnigmaMappingsFormatParser : UnnamedMappingsFormatParser {
+
+ override val expectedFileExtensions: Array = arrayOf("mapping", "mappings")
+
+ private data class State(
+ val builder: MappingsDefinitionBuilder,
+ var baseIndent: Int = 0,
+ var classNamesStack: ArrayDeque = ArrayDeque(),
+ var classMapping: ClassMappingBuilder? = null,
+ var methodMapping: MethodMappingBuilder? = null,
+ ) : MappingSetBuilderCore by builder, MappingIssuesRegistry by builder
+
+ override fun parse(file: VirtualFile): MappingsDefinition {
+ val builder = MappingsDefinitionBuilder(EnigmaMappingsFormatType, file)
+
+ val state = State(builder)
+ file.forLinesIndent { indent, lineNum, parts, _ ->
+ when {
+ indent == state.baseIndent -> parseClass(state, lineNum, parts)
+ indent == state.baseIndent + 1 -> parseMember(state, lineNum, parts)
+ indent == state.baseIndent + 2 -> parseSubMember(state, lineNum, parts)
+ indent < state.baseIndent -> {
+ // new class starting
+ state.baseIndent = indent
+ while (state.classNamesStack.size > state.baseIndent) {
+ state.classNamesStack.removeLastOrNull()
+ }
+ parseClass(state, lineNum, parts)
+ }
+ }
+
+ return@forLinesIndent true
+ }
+
+ return builder.build()
+ }
+
+ private fun parseClass(state: State, lineNum: Int, parts: Array) {
+ state.classMapping = null
+ state.methodMapping = null
+
+ if (parts.isEmpty()) {
+ return
+ }
+
+ when (parts[0].value) {
+ "CLASS" -> {
+ if (parts.size != 3) {
+ state.error("Unexpected number of entries in class mapping line", FileCoords(lineNum))
+ return
+ }
+
+ val mapping = state.clazz(lineNum.highPriority, parts[0].col) {
+ for (i in parts.indicesFrom(1)) {
+ val className = fullClassName(state, i - 1, parts[i].value)
+ if (className == null) {
+ state.error(
+ "All component names for class mapping could not be built as" +
+ "not all names in the hierarchy are present",
+ FileCoords(lineNum)
+ )
+ } else {
+ with(className.asClass().ns(i - 1))
+ }
+ }
+ }
+
+ state.classMapping = mapping
+ state.classNamesStack.addLast(mapping)
+ }
+ "COMMENT" -> return
+ else -> state.error("Unexpected mapping file entry", FileCoords(lineNum))
+ }
+ }
+
+ private fun fullClassName(state: State, index: Int, name: String): String? {
+ if (state.classNamesStack.isEmpty()) {
+ return name
+ }
+
+ val last = state.classNamesStack.lastOrNull()?.names?.get(index) ?: return null
+ @Suppress("ConvertToStringTemplate")
+ return last + '$' + name
+ }
+
+ private fun parseMember(state: State, lineNum: Int, parts: Array) {
+ state.methodMapping = null
+
+ if (parts.isEmpty()) {
+ return
+ }
+
+ when (parts[0].value) {
+ "FIELD" -> parseField(state, lineNum, parts)
+ "METHOD" -> parseMethod(state, lineNum, parts)
+ "CLASS" -> {
+ state.baseIndent++
+ parseClass(state, lineNum, parts)
+ }
+ "COMMENT" -> return
+ else -> state.error("Unexpected mapping file entry", FileCoords(lineNum))
+ }
+ }
+
+ private fun parseField(state: State, lineNum: Int, parts: Array) {
+ val builder = state.classMapping ?: return
+
+ val fieldType = when (parts.size) {
+ 3 -> null
+ 4 -> parts[3].value.asTypeDef()
+ ?: return builder.error("Invalid field type", FileCoords(lineNum, parts[3]))
+ else -> return state.error("Unexpected number of entries in field mapping line", FileCoords(lineNum))
+ }
+
+ builder.field(lineNum.highPriority, parts[0].col) {
+ type = fieldType
+
+ with(parts[1].asField().from)
+ with(parts[2].asField().to)
+ }
+ }
+
+ private fun parseMethod(state: State, lineNum: Int, parts: Array) {
+ val builder = state.classMapping ?: return
+
+ val (leftName, rightName, methodDesc) = when (parts.size) {
+ 3 -> Triple(parts[1], parts[1], parts[2])
+ 4 -> Triple(parts[1], parts[2], parts[3])
+ else -> return state.error("Unexpected number of entries in method mapping line", FileCoords(lineNum))
+ }
+
+ val parsed = methodDesc.value.asMethodDesc()
+ ?: return state.error("Invalid method descriptor", FileCoords(lineNum, methodDesc))
+
+ state.methodMapping = builder.method(lineNum.highPriority, parts[0].col) {
+ desc = parsed
+
+ with(leftName.asMethod().from)
+ with(rightName.asMethod().to)
+ }
+ }
+
+ private fun parseSubMember(state: State, lineNum: Int, parts: Array) {
+ val builder = state.methodMapping ?: return
+
+ if (parts.isEmpty()) {
+ return
+ }
+
+ if (parts[0].value == "COMMENT") {
+ return
+ }
+ if (parts[0].value != "ARG") {
+ return state.error("Unexpected mapping file entry", FileCoords(lineNum))
+ }
+
+ if (parts.size != 3) {
+ return state.error("Unexpected number of entries in method parameter mapping line", FileCoords(lineNum))
+ }
+
+ val index = parts[1].value.toIntOrNull()
+ ?: return state.error("Method parameter index is not a valid integer", FileCoords(lineNum, parts[1]))
+
+ // TODO enigma actually uses lvt indices, not param. Enigma doesn't include metadata on if the method is static
+ // or not so we can't reliably work it out here
+ builder.param(index.asParamIndex(), lineNum.highPriority) {
+ with(parts[2].asParam().to)
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/EnigmaMappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/EnigmaMappingsFormatType.kt
new file mode 100644
index 000000000..4348fa228
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/EnigmaMappingsFormatType.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma
+
+import io.mcdev.obfex.ObfIcons
+import io.mcdev.obfex.formats.MappingsFormatType
+import javax.swing.Icon
+
+object EnigmaMappingsFormatType : MappingsFormatType("Enigma") {
+
+ override val icon: Icon = ObfIcons.ENIGMA_ICON
+ override val name: String = "Enigma"
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaFile.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaFile.kt
new file mode 100644
index 000000000..eeec3b18e
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaFile.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang
+
+import com.intellij.extapi.psi.PsiFileBase
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.psi.FileViewProvider
+
+class EnigmaFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, EnigmaLanguage) {
+
+ override fun getFileType(): FileType = EnigmaFileType
+ override fun toString(): String = EnigmaFileType.description
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaFileType.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaFileType.kt
new file mode 100644
index 000000000..4747853b6
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaFileType.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import io.mcdev.obfex.ObfIcons
+import javax.swing.Icon
+
+object EnigmaFileType : LanguageFileType(EnigmaLanguage) {
+
+ override fun getName(): String = "Enigma"
+ override fun getDescription(): String = "Enigma obfuscation mapping file"
+ override fun getDefaultExtension(): String = "mapping"
+ override fun getIcon(): Icon = ObfIcons.ENIGMA_ICON
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaLanguage.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaLanguage.kt
new file mode 100644
index 000000000..db7f12b86
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaLanguage.kt
@@ -0,0 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang
+
+import com.intellij.lang.Language
+
+object EnigmaLanguage : Language("Enigma")
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaLayoutLexer.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaLayoutLexer.kt
new file mode 100644
index 000000000..ca192f43d
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaLayoutLexer.kt
@@ -0,0 +1,35 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang
+
+import com.intellij.psi.tree.IElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.enigma.gen.psi.EnigmaTypes
+import io.mcdev.obfex.formats.enigma.lang.psi.EnigmaLexerAdapter
+import io.mcdev.obfex.formats.util.sigws.SignificantWhitespaceLexer
+
+class EnigmaLayoutLexer : SignificantWhitespaceLexer(EnigmaLexerAdapter()) {
+
+ override val newlineTokens: TokenSet = TokenSet.create(EnigmaTypes.CRLF)
+ override val tabTokens: TokenSet = TokenSet.create(EnigmaTypes.TAB)
+ override val virtualOpenToken: IElementType = EnigmaTypes.VIRTUAL_OPEN
+ override val virtualCloseToken: IElementType = EnigmaTypes.VIRTUAL_CLOSE
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaParserDefinition.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaParserDefinition.kt
new file mode 100644
index 000000000..cb0d103e1
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/EnigmaParserDefinition.kt
@@ -0,0 +1,49 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.ParserDefinition
+import com.intellij.lang.PsiParser
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.project.Project
+import com.intellij.psi.FileViewProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.tree.IFileElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.enigma.gen.EnigmaParser
+import io.mcdev.obfex.formats.enigma.gen.psi.EnigmaTypes
+
+class EnigmaParserDefinition : ParserDefinition {
+ override fun createLexer(project: Project): Lexer = EnigmaLayoutLexer()
+ override fun getCommentTokens(): TokenSet = COMMENTS
+ override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
+ override fun createParser(project: Project): PsiParser = EnigmaParser()
+ override fun getFileNodeType(): IFileElementType = FILE
+ override fun createFile(viewProvider: FileViewProvider): PsiFile = EnigmaFile(viewProvider)
+ override fun createElement(node: ASTNode): PsiElement = EnigmaTypes.Factory.createElement(node)
+ override fun getWhitespaceTokens(): TokenSet = WHITESPACE
+}
+
+private val WHITESPACE = TokenSet.orSet(TokenSet.WHITE_SPACE, TokenSet.create(EnigmaTypes.TAB))
+private val COMMENTS = TokenSet.create(EnigmaTypes.COMMENT)
+private val FILE = IFileElementType(EnigmaLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/colors/EnigmaAnnotator.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/colors/EnigmaAnnotator.kt
new file mode 100644
index 000000000..41e8f8dc8
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/colors/EnigmaAnnotator.kt
@@ -0,0 +1,50 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang.colors
+
+import com.intellij.lang.annotation.AnnotationHolder
+import com.intellij.lang.annotation.Annotator
+import com.intellij.psi.PsiElement
+import io.mcdev.obfex.formats.enigma.gen.psi.EnigmaArgIndex
+import io.mcdev.obfex.formats.enigma.gen.psi.EnigmaArgMapping
+import io.mcdev.obfex.formats.enigma.gen.psi.EnigmaClassMappingPart
+import io.mcdev.obfex.formats.enigma.gen.psi.EnigmaFieldMapping
+import io.mcdev.obfex.formats.enigma.gen.psi.EnigmaMappingPart
+import io.mcdev.obfex.formats.enigma.gen.psi.EnigmaMethodMapping
+import io.mcdev.obfex.formats.util.registerHighlight
+
+class EnigmaAnnotator : Annotator {
+ override fun annotate(element: PsiElement, holder: AnnotationHolder) {
+ val key = when (element) {
+ is EnigmaClassMappingPart -> EnigmaSyntaxHighlighter.CLASS_NAME
+ is EnigmaArgIndex -> EnigmaSyntaxHighlighter.PARAM_INDEX
+ is EnigmaMappingPart -> when (element.parent) {
+ is EnigmaMethodMapping -> EnigmaSyntaxHighlighter.METHOD
+ is EnigmaFieldMapping -> EnigmaSyntaxHighlighter.FIELD
+ is EnigmaArgMapping -> EnigmaSyntaxHighlighter.PARAM
+ else -> return
+ }
+ else -> return
+ }
+
+ registerHighlight(element, key, holder)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/colors/EnigmaSyntaxHighlighter.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/colors/EnigmaSyntaxHighlighter.kt
new file mode 100644
index 000000000..b5d4cea4e
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/colors/EnigmaSyntaxHighlighter.kt
@@ -0,0 +1,95 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang.colors
+
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.enigma.gen.psi.EnigmaTypes
+import io.mcdev.obfex.formats.enigma.lang.psi.EnigmaLexerAdapter
+
+class EnigmaSyntaxHighlighter : SyntaxHighlighterBase() {
+
+ override fun getHighlightingLexer() = EnigmaLexerAdapter()
+
+ override fun getTokenHighlights(tokenType: IElementType?): Array =
+ when (tokenType) {
+ EnigmaTypes.CLASS_KEY, EnigmaTypes.FIELD_KEY, EnigmaTypes.METHOD_KEY,
+ EnigmaTypes.ARG_KEY, EnigmaTypes.COMMENT_KEY -> KEYWORD_KEYS
+ EnigmaTypes.CLASS_TYPE -> CLASS_TYPE_KEYS
+ EnigmaTypes.PRIMITIVE -> PRIMITIVE_KEYS
+ EnigmaTypes.COMMENT -> COMMENT_KEYS
+ EnigmaTypes.DOC_TEXT -> DOC_KEYS
+ else -> TextAttributesKey.EMPTY_ARRAY
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ companion object {
+ val KEYWORD = TextAttributesKey.createTextAttributesKey(
+ "ENIGMA_KEYWORD",
+ DefaultLanguageHighlighterColors.KEYWORD
+ )
+ val CLASS_NAME = TextAttributesKey.createTextAttributesKey(
+ "ENIGMA_CLASS_NAME",
+ DefaultLanguageHighlighterColors.STRING
+ )
+ val METHOD = TextAttributesKey.createTextAttributesKey(
+ "ENIGMA_METHOD",
+ DefaultLanguageHighlighterColors.INSTANCE_METHOD
+ )
+ val FIELD = TextAttributesKey.createTextAttributesKey(
+ "ENIGMA_FIELD",
+ DefaultLanguageHighlighterColors.INSTANCE_FIELD
+ )
+ val PARAM = TextAttributesKey.createTextAttributesKey(
+ "ENIGMA_PARAM",
+ DefaultLanguageHighlighterColors.PARAMETER
+ )
+ val PARAM_INDEX = TextAttributesKey.createTextAttributesKey(
+ "ENIGMA_PARAM_INDEX",
+ DefaultLanguageHighlighterColors.INLAY_DEFAULT
+ )
+ val PRIMITIVE = TextAttributesKey.createTextAttributesKey(
+ "ENIGMA_PRIMITIVE",
+ DefaultLanguageHighlighterColors.NUMBER
+ )
+ val CLASS_TYPE = TextAttributesKey.createTextAttributesKey(
+ "ENIGMA_CLASS_TYPE",
+ DefaultLanguageHighlighterColors.CLASS_REFERENCE
+ )
+ val COMMENT = TextAttributesKey.createTextAttributesKey(
+ "ENIGMA_COMMENT",
+ DefaultLanguageHighlighterColors.LINE_COMMENT
+ )
+ val DOC = TextAttributesKey.createTextAttributesKey(
+ "ENIGMA_JAVADOC",
+ DefaultLanguageHighlighterColors.DOC_COMMENT
+ )
+
+ private val KEYWORD_KEYS = arrayOf(KEYWORD)
+ private val PRIMITIVE_KEYS = arrayOf(PRIMITIVE)
+ private val CLASS_TYPE_KEYS = arrayOf(CLASS_TYPE)
+ private val COMMENT_KEYS = arrayOf(COMMENT)
+ private val DOC_KEYS = arrayOf(DOC)
+ private val PARAM_INDEX_KEYS = arrayOf(PARAM_INDEX)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/colors/EnigmaSyntaxHighlighterFactory.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/colors/EnigmaSyntaxHighlighterFactory.kt
new file mode 100644
index 000000000..2ffe35199
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/colors/EnigmaSyntaxHighlighterFactory.kt
@@ -0,0 +1,29 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang.colors
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+
+class EnigmaSyntaxHighlighterFactory : SyntaxHighlighterFactory() {
+ override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?) = EnigmaSyntaxHighlighter()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaElementType.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaElementType.kt
new file mode 100644
index 000000000..3eaa68d93
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaElementType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.enigma.lang.EnigmaLanguage
+import org.jetbrains.annotations.NonNls
+
+class EnigmaElementType(@NonNls debugName: String) : IElementType(debugName, EnigmaLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaLexerAdapter.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaLexerAdapter.kt
new file mode 100644
index 000000000..043b75846
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaLexerAdapter.kt
@@ -0,0 +1,26 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang.psi
+
+import com.intellij.lexer.FlexAdapter
+import io.mcdev.obfex.formats.enigma.gen.EnigmaLexer
+
+class EnigmaLexerAdapter : FlexAdapter(EnigmaLexer())
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaParserUtil.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaParserUtil.kt
new file mode 100644
index 000000000..9297125f7
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaParserUtil.kt
@@ -0,0 +1,42 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang.psi
+
+import com.intellij.lang.PsiBuilder
+import io.mcdev.obfex.formats.enigma.gen.psi.EnigmaTypes
+import io.mcdev.obfex.formats.util.sigws.SignificantWhitespaceParserUtil
+
+@Suppress("UNUSED_PARAMETER")
+object EnigmaParserUtil : SignificantWhitespaceParserUtil() {
+ @JvmStatic
+ fun fieldMappingPart(
+ builder: PsiBuilder,
+ level: Int,
+ ): Boolean {
+ if (builder.tokenType !== EnigmaTypes.PRIMITIVE) {
+ return true
+ }
+ val next = builder.lookAhead(1)
+ return next != null && // eof
+ next !== EnigmaTypes.CRLF && // end of line
+ next !== EnigmaTypes.VIRTUAL_CLOSE // automatically inserted at eof
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaTokenType.kt b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaTokenType.kt
new file mode 100644
index 000000000..2ada8946f
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/enigma/lang/psi/EnigmaTokenType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.enigma.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.enigma.lang.EnigmaLanguage
+import org.jetbrains.annotations.NonNls
+
+class EnigmaTokenType(@NonNls debugName: String) : IElementType(debugName, EnigmaLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/JamMappingsFormatParser.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/JamMappingsFormatParser.kt
new file mode 100644
index 000000000..b2116efad
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/JamMappingsFormatParser.kt
@@ -0,0 +1,146 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam
+
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.MappingPart
+import io.mcdev.obfex.forLines
+import io.mcdev.obfex.mappings.FileCoords
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsDefinitionBuilder
+import io.mcdev.obfex.mappings.UnnamedMappingsFormatParser
+import io.mcdev.obfex.mappings.clazz
+import io.mcdev.obfex.mappings.field
+import io.mcdev.obfex.mappings.method
+import io.mcdev.obfex.mappings.param
+import io.mcdev.obfex.ref.asClass
+import io.mcdev.obfex.ref.asField
+import io.mcdev.obfex.ref.asMethod
+import io.mcdev.obfex.ref.asMethodDesc
+import io.mcdev.obfex.ref.asParam
+import io.mcdev.obfex.ref.asParamIndex
+import io.mcdev.obfex.ref.asRef
+import io.mcdev.obfex.ref.asTypeDef
+import io.mcdev.obfex.ref.highPriority
+import io.mcdev.obfex.ref.lowPriority
+
+@Suppress("DuplicatedCode")
+class JamMappingsFormatParser : UnnamedMappingsFormatParser {
+
+ override val expectedFileExtensions: Array = arrayOf("jam")
+
+ override fun parse(file: VirtualFile): MappingsDefinition {
+ val builder = MappingsDefinitionBuilder(JamMappingsFormatType, file)
+
+ file.forLines { lineNum, parts, _ ->
+ if (parts.isEmpty()) {
+ return@forLines true
+ }
+
+ val key = parts.first()
+
+ when (key.value) {
+ "CL" -> parseClassLine(builder, lineNum, parts)
+ "FD" -> parseFieldLine(builder, lineNum, parts)
+ "MD" -> parseMethodLine(builder, lineNum, parts)
+ "MP" -> parseMethodParamLine(builder, lineNum, parts)
+ else -> builder.error("Unrecognized key: ${key.value.substring(0, 2)}", FileCoords(lineNum))
+ }
+
+ return@forLines true
+ }
+
+ return builder.build()
+ }
+
+ private fun parseClassLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 3) {
+ builder.error("Class line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (_, leftClass, rightClass) = parts
+ builder.clazz(lineNum.highPriority) {
+ with(leftClass.asClass().from)
+ with(rightClass.asClass().to)
+ }
+ }
+
+ private fun parseFieldLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 5) {
+ builder.error("Field line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (_, leftClass, leftName, leftDesc, rightName) = parts
+
+ val leftType = leftDesc.value.asTypeDef()
+ ?: return builder.error("Field descriptor is invalid", FileCoords(lineNum, leftDesc))
+
+ builder.clazz(leftClass.asClass().from, lineNum.lowPriority).field(lineNum.highPriority) {
+ type = leftType
+
+ with(leftName.asField().from)
+ with(rightName.asField().to)
+ }
+ }
+
+ private fun parseMethodLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 5) {
+ builder.error("Method line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (_, leftClass, leftMethod, leftDesc, rightName) = parts
+
+ val leftType = leftDesc.value.asMethodDesc()
+ ?: return builder.error("Method descriptor is invalid", FileCoords(lineNum, leftDesc))
+
+ builder.clazz(leftClass.asClass().from, lineNum.lowPriority).method(lineNum.highPriority) {
+ desc = leftType
+
+ with(leftMethod.asMethod().from)
+ with(rightName.asMethod().to)
+ }
+ }
+
+ private fun parseMethodParamLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 6) {
+ builder.error("Method parameter line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (_, leftClass, leftMethod, leftDesc, index) = parts
+ val rightName = parts[5]
+
+ val leftType = leftDesc.value.asMethodDesc()
+ ?: return builder.error("Method descriptor is invalid", FileCoords(lineNum, leftDesc))
+
+ val indexNum = index.value.toIntOrNull()
+ ?: return builder.error("Method param index is invalid", FileCoords(lineNum, index))
+
+ builder.clazz(leftClass.asClass().from, lineNum.lowPriority)
+ .method(leftMethod.asMethod().asRef(leftType).from, lineNum.lowPriority)
+ .param(indexNum.asParamIndex(), lineNum.highPriority) {
+ with(rightName.asParam().to)
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/JamMappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/JamMappingsFormatType.kt
new file mode 100644
index 000000000..27dee8e19
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/JamMappingsFormatType.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam
+
+import io.mcdev.obfex.ObfIcons
+import io.mcdev.obfex.formats.MappingsFormatType
+import javax.swing.Icon
+
+object JamMappingsFormatType : MappingsFormatType("JAM") {
+
+ override val icon: Icon = ObfIcons.JAM_ICON
+ override val name: String = "JAM"
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamFile.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamFile.kt
new file mode 100644
index 000000000..36d1279ea
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamFile.kt
@@ -0,0 +1,30 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.lang
+
+import com.intellij.extapi.psi.PsiFileBase
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.psi.FileViewProvider
+
+class JamFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, JamLanguage) {
+ override fun getFileType(): FileType = JamFileType
+ override fun toString(): String = JamFileType.description
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamFileType.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamFileType.kt
new file mode 100644
index 000000000..bcb4d1148
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamFileType.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import io.mcdev.obfex.ObfIcons
+import javax.swing.Icon
+
+object JamFileType : LanguageFileType(JamLanguage) {
+
+ override fun getName(): String = "JAM"
+ override fun getDescription(): String = "JAM obfuscation mapping file"
+ override fun getDefaultExtension(): String = "jam"
+ override fun getIcon(): Icon = ObfIcons.JAM_ICON
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamLanguage.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamLanguage.kt
new file mode 100644
index 000000000..97c02c42f
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamLanguage.kt
@@ -0,0 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.lang
+
+import com.intellij.lang.Language
+
+object JamLanguage : Language("JAM")
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamParserDefinition.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamParserDefinition.kt
new file mode 100644
index 000000000..3671c028e
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/JamParserDefinition.kt
@@ -0,0 +1,50 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.lang
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.Language
+import com.intellij.lang.ParserDefinition
+import com.intellij.lang.PsiParser
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.project.Project
+import com.intellij.psi.FileViewProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.tree.IFileElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.jam.gen.JamParser
+import io.mcdev.obfex.formats.jam.gen.psi.JamTypes
+import io.mcdev.obfex.formats.jam.lang.psi.JamLexerAdapter
+
+class JamParserDefinition : ParserDefinition {
+
+ override fun createLexer(project: Project): Lexer = JamLexerAdapter()
+ override fun getCommentTokens(): TokenSet = COMMENTS
+ override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
+ override fun createParser(project: Project): PsiParser = JamParser()
+ override fun getFileNodeType(): IFileElementType = FILE
+ override fun createFile(viewProvider: FileViewProvider): PsiFile = JamFile(viewProvider)
+ override fun createElement(node: ASTNode): PsiElement = JamTypes.Factory.createElement(node)
+}
+
+private val COMMENTS = TokenSet.create(JamTypes.COMMENT)
+private val FILE = IFileElementType(Language.findInstance(JamLanguage::class.java))
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/lang/colors/JamAnnotator.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/colors/JamAnnotator.kt
new file mode 100644
index 000000000..1d8b67ed5
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/colors/JamAnnotator.kt
@@ -0,0 +1,61 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.lang.colors
+
+import com.intellij.lang.annotation.AnnotationHolder
+import com.intellij.lang.annotation.Annotator
+import com.intellij.psi.PsiElement
+import io.mcdev.obfex.formats.jam.gen.psi.JamClassMapping
+import io.mcdev.obfex.formats.jam.gen.psi.JamFieldMapping
+import io.mcdev.obfex.formats.jam.gen.psi.JamMethodMapping
+import io.mcdev.obfex.formats.jam.gen.psi.JamParamMapping
+import io.mcdev.obfex.formats.util.registerHighlight
+
+class JamAnnotator : Annotator {
+
+ override fun annotate(element: PsiElement, holder: AnnotationHolder) {
+ when (element) {
+ is JamClassMapping -> {
+ registerHighlight(element.obfName, JamSyntaxHighlighter.CLASS_NAME, holder)
+ registerHighlight(element.deobfName, JamSyntaxHighlighter.CLASS_NAME, holder, true)
+ }
+
+ is JamMethodMapping -> {
+ registerHighlight(element.obfClassName, JamSyntaxHighlighter.CLASS_NAME, holder)
+ registerHighlight(element.obfMethodName, JamSyntaxHighlighter.METHOD, holder)
+ registerHighlight(element.deobfName, JamSyntaxHighlighter.METHOD, holder, true)
+ }
+
+ is JamFieldMapping -> {
+ registerHighlight(element.obfClassName, JamSyntaxHighlighter.CLASS_NAME, holder)
+ registerHighlight(element.obfFieldName, JamSyntaxHighlighter.FIELD, holder)
+ registerHighlight(element.deobfName, JamSyntaxHighlighter.FIELD, holder, true)
+ }
+
+ is JamParamMapping -> {
+ registerHighlight(element.obfClassName, JamSyntaxHighlighter.CLASS_NAME, holder)
+ registerHighlight(element.obfMethodName, JamSyntaxHighlighter.METHOD, holder)
+ registerHighlight(element.parameterIndex, JamSyntaxHighlighter.PARAM_INDEX, holder)
+ registerHighlight(element.deobfName, JamSyntaxHighlighter.PARAM, holder, true)
+ }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/lang/colors/JamSyntaxHighlighter.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/colors/JamSyntaxHighlighter.kt
new file mode 100644
index 000000000..565a54c44
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/colors/JamSyntaxHighlighter.kt
@@ -0,0 +1,86 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.lang.colors
+
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.jam.gen.psi.JamTypes
+import io.mcdev.obfex.formats.jam.lang.psi.JamLexerAdapter
+import io.mcdev.obfex.formats.srg.lang.colors.SrgSyntaxHighlighter
+
+class JamSyntaxHighlighter : SyntaxHighlighterBase() {
+
+ override fun getHighlightingLexer(): Lexer = JamLexerAdapter()
+
+ override fun getTokenHighlights(tokenType: IElementType?): Array {
+ return when (tokenType) {
+ JamTypes.CLASS_KEY, JamTypes.FIELD_KEY, JamTypes.METHOD_KEY, JamTypes.PARAM_KEY -> KEYWORD_KEYS
+ JamTypes.CLASS_TYPE -> CLASS_TYPE_KEYS
+ JamTypes.PRIMITIVE -> PRIMITIVE_KEYS
+ JamTypes.COMMENT -> COMMENT_KEYS
+ else -> TextAttributesKey.EMPTY_ARRAY
+ }
+ }
+
+ companion object {
+ val KEYWORD = TextAttributesKey.createTextAttributesKey("JAM_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD)
+ val CLASS_NAME = TextAttributesKey.createTextAttributesKey(
+ "JAM_CLASS_NAME",
+ DefaultLanguageHighlighterColors.STRING
+ )
+ val METHOD = TextAttributesKey.createTextAttributesKey(
+ "JAM_METHOD",
+ DefaultLanguageHighlighterColors.INSTANCE_METHOD
+ )
+ val FIELD = TextAttributesKey.createTextAttributesKey(
+ "JAM_FIELD",
+ DefaultLanguageHighlighterColors.INSTANCE_FIELD
+ )
+ val PARAM = TextAttributesKey.createTextAttributesKey(
+ "JAM_PARAM",
+ DefaultLanguageHighlighterColors.PARAMETER
+ )
+ val PARAM_INDEX = TextAttributesKey.createTextAttributesKey(
+ "JAM_PARAM_INDEX",
+ DefaultLanguageHighlighterColors.INLAY_DEFAULT
+ )
+ val PRIMITIVE = TextAttributesKey.createTextAttributesKey(
+ "JAM_PRIMITIVE",
+ DefaultLanguageHighlighterColors.NUMBER
+ )
+ val CLASS_TYPE = TextAttributesKey.createTextAttributesKey(
+ "JAM_CLASS_TYPE",
+ DefaultLanguageHighlighterColors.CLASS_REFERENCE
+ )
+ val COMMENT = TextAttributesKey.createTextAttributesKey(
+ "JAM_COMMENT",
+ DefaultLanguageHighlighterColors.LINE_COMMENT
+ )
+
+ private val KEYWORD_KEYS = arrayOf(SrgSyntaxHighlighter.KEYWORD)
+ private val PRIMITIVE_KEYS = arrayOf(SrgSyntaxHighlighter.PRIMITIVE)
+ private val CLASS_TYPE_KEYS = arrayOf(SrgSyntaxHighlighter.CLASS_TYPE)
+ private val COMMENT_KEYS = arrayOf(SrgSyntaxHighlighter.COMMENT)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/lang/colors/JamSyntaxHighlighterFactory.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/colors/JamSyntaxHighlighterFactory.kt
new file mode 100644
index 000000000..52896b4d7
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/colors/JamSyntaxHighlighterFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.lang.colors
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighter
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+
+class JamSyntaxHighlighterFactory : SyntaxHighlighterFactory() {
+ override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?): SyntaxHighlighter =
+ JamSyntaxHighlighter()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/lang/psi/JamElementType.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/psi/JamElementType.kt
new file mode 100644
index 000000000..385c99650
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/psi/JamElementType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.jam.lang.JamLanguage
+import org.jetbrains.annotations.NonNls
+
+class JamElementType(@NonNls debugName: String) : IElementType(debugName, JamLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/lang/psi/JamLexerAdapter.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/psi/JamLexerAdapter.kt
new file mode 100644
index 000000000..80a8a15e5
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/psi/JamLexerAdapter.kt
@@ -0,0 +1,26 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.lang.psi
+
+import com.intellij.lexer.FlexAdapter
+import io.mcdev.obfex.formats.jam.gen.JamLexer
+
+class JamLexerAdapter : FlexAdapter(JamLexer())
diff --git a/obfuscation-explorer/src/main/kotlin/formats/jam/lang/psi/JamTokenType.kt b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/psi/JamTokenType.kt
new file mode 100644
index 000000000..aa7e73788
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/jam/lang/psi/JamTokenType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.jam.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.jam.lang.JamLanguage
+import org.jetbrains.annotations.NonNls
+
+class JamTokenType(@NonNls debugName: String) : IElementType(debugName, JamLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/ProGuardMappingsFormatParser.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/ProGuardMappingsFormatParser.kt
new file mode 100644
index 000000000..82b8457ea
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/ProGuardMappingsFormatParser.kt
@@ -0,0 +1,217 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard
+
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.MappingPart
+import io.mcdev.obfex.forLinesIndent
+import io.mcdev.obfex.mappings.ClassMappingBuilder
+import io.mcdev.obfex.mappings.FileCoords
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsDefinitionBuilder
+import io.mcdev.obfex.mappings.UnnamedMappingsFormatParser
+import io.mcdev.obfex.mappings.clazz
+import io.mcdev.obfex.mappings.field
+import io.mcdev.obfex.mappings.method
+import io.mcdev.obfex.ref.ArrayTypeDef
+import io.mcdev.obfex.ref.ClassTypeDef
+import io.mcdev.obfex.ref.MethodDescriptor
+import io.mcdev.obfex.ref.PrimitiveTypeDef
+import io.mcdev.obfex.ref.ReturnTypeDef
+import io.mcdev.obfex.ref.TypeDef
+import io.mcdev.obfex.ref.VoidTypeDef
+import io.mcdev.obfex.ref.asClass
+import io.mcdev.obfex.ref.asField
+import io.mcdev.obfex.ref.asMethod
+import io.mcdev.obfex.ref.highPriority
+
+@Suppress("DuplicatedCode")
+class ProGuardMappingsFormatParser : UnnamedMappingsFormatParser {
+
+ override val expectedFileExtensions: Array = arrayOf("map")
+
+ override fun parse(file: VirtualFile): MappingsDefinition {
+ val builder = MappingsDefinitionBuilder(ProGuardMappingsFormatType, file)
+
+ var classMapping: ClassMappingBuilder? = null
+ file.forLinesIndent { indent, lineNum, parts, _ ->
+ if (indent > 0) {
+ val c = classMapping
+ if (c == null) {
+ builder.error("Member mapping line with no associated class", FileCoords(lineNum))
+ } else {
+ parseMember(c, lineNum, parts)
+ }
+ } else {
+ classMapping = parseClass(builder, lineNum, parts)
+ }
+
+ return@forLinesIndent true
+ }
+
+ return builder.build()
+ }
+
+ private fun parseClass(
+ builder: MappingsDefinitionBuilder,
+ lineNum: Int,
+ parts: Array,
+ ): ClassMappingBuilder? {
+ if (parts.size != 3) {
+ builder.error("Invalid class mapping line", FileCoords(lineNum))
+ return null
+ }
+
+ if (parts[1].value != "->") {
+ builder.error("Unrecognized class mapping separator", FileCoords(lineNum))
+ return null
+ }
+
+ val leftClass = parts[0]
+ val rightClass = parts[2].value.trimEnd(':')
+
+ return builder.clazz(lineNum.highPriority) {
+ with(leftClass.asClass().from)
+ with(rightClass.asClass().to)
+ }
+ }
+
+ private val methodLinesRegex = Regex("\\d+:\\d+:")
+
+ private fun parseMember(builder: ClassMappingBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 4) {
+ builder.error("Unrecognized member mapping line", FileCoords(lineNum))
+ return
+ }
+
+ if (parts[2].value != "->") {
+ builder.error("Unrecognized member mapping separator", FileCoords(lineNum))
+ return
+ }
+
+ val memberDesc = parts[0]
+ val leftName = parts[1]
+ val rightName = parts[3]
+
+ val match = methodLinesRegex.matchAt(memberDesc.value, 0)
+ if (match != null) {
+ // method
+ val returnTypeText = memberDesc.value.substring(match.range.last + 1)
+
+ val paramsIndex = leftName.value.indexOf('(')
+ if (paramsIndex == -1) {
+ builder.error("Invalid method descriptor", FileCoords(lineNum, leftName))
+ return
+ }
+
+ val methodName = leftName.value.substring(0, paramsIndex)
+ val returnType = parseType(returnTypeText)
+ ?: return builder.error("Invalid return type", FileCoords(lineNum, memberDesc))
+
+ val methodParams = leftName.value.substring(paramsIndex)
+
+ val params = parseMethodParams(methodParams)
+ ?: return builder.error("Invalid method descriptor", FileCoords(lineNum, leftName))
+
+ val methodDesc = MethodDescriptor(params, returnType)
+
+ builder.method(lineNum.highPriority) {
+ desc = methodDesc
+
+ with(methodName.asMethod().from)
+ with(rightName.asMethod().to)
+ }
+ } else {
+ val fieldType = parseType(memberDesc.value)
+ if (fieldType !is TypeDef) {
+ builder.error("Invalid field type", FileCoords(lineNum, memberDesc))
+ return
+ }
+
+ builder.field(lineNum.highPriority) {
+ type = fieldType
+
+ with(leftName.asField().from)
+ with(rightName.asField().to)
+ }
+ }
+ }
+
+ // Proguard doesn't use standard descriptor syntax
+ private fun parseMethodParams(params: String): List? {
+ val res = mutableListOf()
+
+ // first index is (
+ var index = 1
+ while (index < params.length) {
+ val nextIndex = params.indexOfAny(charArrayOf(',', ')'), index)
+ if (nextIndex == -1) {
+ return null
+ }
+
+ val type = parseType(params.substring(index, nextIndex))
+ if (type !is TypeDef) {
+ return null
+ }
+ res += type
+
+ index = nextIndex + 1
+ }
+
+ return res
+ }
+
+ private fun parseType(text: String): ReturnTypeDef? {
+ return when (text) {
+ "boolean" -> PrimitiveTypeDef.BOOLEAN
+ "char" -> PrimitiveTypeDef.CHAR
+ "byte" -> PrimitiveTypeDef.BYTE
+ "short" -> PrimitiveTypeDef.SHORT
+ "int" -> PrimitiveTypeDef.INT
+ "long" -> PrimitiveTypeDef.LONG
+ "float" -> PrimitiveTypeDef.FLOAT
+ "double" -> PrimitiveTypeDef.DOUBLE
+ "void" -> VoidTypeDef
+ else -> {
+ var arrayCount = 0
+ while (true) {
+ if (text.lastIndexOf("[]", text.lastIndex - (arrayCount * 2)) > 0) {
+ arrayCount++
+ continue
+ } else {
+ break
+ }
+ }
+
+ if (arrayCount > 0) {
+ val baseType = parseType(text.substring(0, text.lastIndex - arrayCount * 2))
+ if (baseType !is TypeDef) {
+ return null
+ }
+
+ ArrayTypeDef(baseType, arrayCount)
+ } else {
+ ClassTypeDef(text.asClass())
+ }
+ }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/ProGuardMappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/ProGuardMappingsFormatType.kt
new file mode 100644
index 000000000..ffd2d9d15
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/ProGuardMappingsFormatType.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard
+
+import io.mcdev.obfex.ObfIcons
+import io.mcdev.obfex.formats.MappingsFormatType
+import javax.swing.Icon
+
+object ProGuardMappingsFormatType : MappingsFormatType("ProGuard") {
+
+ override val icon: Icon = ObfIcons.PROGUARD_ICONS
+ override val name: String = "ProGuard"
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardFile.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardFile.kt
new file mode 100644
index 000000000..c4b642f41
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardFile.kt
@@ -0,0 +1,30 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang
+
+import com.intellij.extapi.psi.PsiFileBase
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.psi.FileViewProvider
+
+class ProGuardFile(viewProGuardFile: FileViewProvider) : PsiFileBase(viewProGuardFile, ProGuardLanguage) {
+ override fun getFileType(): FileType = ProGuardFileType
+ override fun toString(): String = ProGuardFileType.description
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardFileType.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardFileType.kt
new file mode 100644
index 000000000..d7a1c60af
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardFileType.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import io.mcdev.obfex.ObfIcons
+import javax.swing.Icon
+
+object ProGuardFileType : LanguageFileType(ProGuardLanguage) {
+
+ override fun getName(): String = "ProGuard"
+ override fun getDescription(): String = "ProGuard obfuscation mapping file"
+ override fun getDefaultExtension(): String = "map"
+ override fun getIcon(): Icon = ObfIcons.PROGUARD_ICONS
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardFileTypeDetector.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardFileTypeDetector.kt
new file mode 100644
index 000000000..3e98b82d4
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardFileTypeDetector.kt
@@ -0,0 +1,73 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang
+
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.openapi.fileTypes.FileTypeRegistry.FileTypeDetector
+import com.intellij.openapi.util.io.ByteSequence
+import com.intellij.openapi.vfs.VirtualFile
+import org.intellij.lang.annotations.Language
+
+class ProGuardFileTypeDetector : FileTypeDetector {
+
+ override fun detect(file: VirtualFile, firstBytes: ByteSequence, firstCharsIfText: CharSequence?): FileType? {
+ if (firstCharsIfText == null) {
+ return null
+ }
+
+ val lines = firstCharsIfText.lineSequence()
+ .filter { !LINE_COMMENT.matches(it) }
+ .toList()
+
+ if (lines.isEmpty()) {
+ return null
+ }
+
+ // first line must be a class mapping line
+ val firstLine = lines.first()
+ if (firstLine.matches(CLASS_MAPPING_LINE)) {
+ return ProGuardFileType
+ }
+
+ return null
+ }
+
+ private companion object {
+ private val LINE_COMMENT = Regex("^\\s*#.*$")
+ @Language("RegExp")
+ private const val JAVA_IDENTIFIER_REGEX =
+ // Initial name (either class or first package identifier)
+ "\\p{javaJavaIdentifierStart}\\p{javaUnicodeIdentifierPart}*" +
+ // additional name parts (package and class name)
+ "(\\.\\p{javaJavaIdentifierStart}\\p{javaUnicodeIdentifierPart}*)*"
+
+ private val CLASS_MAPPING_LINE = Regex(
+ // starts at beginning of the line
+ "^" +
+ JAVA_IDENTIFIER_REGEX +
+ // Separator
+ " -> " +
+ JAVA_IDENTIFIER_REGEX +
+ // ending can include a trailing comment
+ ":\\s*(#.*)?$"
+ )
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardLanguage.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardLanguage.kt
new file mode 100644
index 000000000..90018c1ea
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardLanguage.kt
@@ -0,0 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang
+
+import com.intellij.lang.Language
+
+object ProGuardLanguage : Language("ProGuard")
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardParserDefinition.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardParserDefinition.kt
new file mode 100644
index 000000000..28da969eb
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/ProGuardParserDefinition.kt
@@ -0,0 +1,48 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.ParserDefinition
+import com.intellij.lang.PsiParser
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.project.Project
+import com.intellij.psi.FileViewProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.tree.IFileElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.proguard.gen.ProGuardParser
+import io.mcdev.obfex.formats.proguard.gen.psi.ProGuardTypes
+import io.mcdev.obfex.formats.proguard.lang.psi.ProGuardLexerAdapter
+
+class ProGuardParserDefinition : ParserDefinition {
+ override fun createLexer(project: Project?): Lexer = ProGuardLexerAdapter()
+ override fun getCommentTokens(): TokenSet = COMMENTS
+ override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
+ override fun createParser(project: Project?): PsiParser = ProGuardParser()
+ override fun getFileNodeType(): IFileElementType = FILE
+ override fun createFile(viewProvider: FileViewProvider): PsiFile = ProGuardFile(viewProvider)
+ override fun createElement(node: ASTNode?): PsiElement = ProGuardTypes.Factory.createElement(node)
+}
+
+private val COMMENTS = TokenSet.create(ProGuardTypes.COMMENT)
+private val FILE = IFileElementType(ProGuardLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/colors/ProGuardAnnotator.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/colors/ProGuardAnnotator.kt
new file mode 100644
index 000000000..01c62d467
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/colors/ProGuardAnnotator.kt
@@ -0,0 +1,54 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang.colors
+
+import com.intellij.lang.annotation.AnnotationHolder
+import com.intellij.lang.annotation.Annotator
+import com.intellij.psi.PsiElement
+import io.mcdev.obfex.formats.proguard.gen.psi.ProGuardClassMapping
+import io.mcdev.obfex.formats.proguard.gen.psi.ProGuardFieldName
+import io.mcdev.obfex.formats.proguard.gen.psi.ProGuardMethodName
+import io.mcdev.obfex.formats.proguard.gen.psi.ProGuardTypeDesc
+import io.mcdev.obfex.formats.util.registerHighlight
+
+class ProGuardAnnotator : Annotator {
+ override fun annotate(element: PsiElement, holder: AnnotationHolder) {
+ if (element is ProGuardClassMapping) {
+ for (identifier in element.identifierList) {
+ registerHighlight(identifier, ProGuardSyntaxHighlighter.CLASS_NAME, holder)
+ }
+ return
+ }
+ if (element is ProGuardTypeDesc) {
+ element.identifier?.let { ident ->
+ registerHighlight(ident, ProGuardSyntaxHighlighter.CLASS_NAME, holder)
+ }
+ }
+
+ val key = when (element) {
+ is ProGuardMethodName -> ProGuardSyntaxHighlighter.METHOD
+ is ProGuardFieldName -> ProGuardSyntaxHighlighter.FIELD
+ else -> return
+ }
+
+ registerHighlight(element, key, holder)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/colors/ProGuardSyntaxHighlighter.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/colors/ProGuardSyntaxHighlighter.kt
new file mode 100644
index 000000000..9a3891f26
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/colors/ProGuardSyntaxHighlighter.kt
@@ -0,0 +1,98 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang.colors
+
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.proguard.gen.psi.ProGuardTypes
+import io.mcdev.obfex.formats.proguard.lang.psi.ProGuardLexerAdapter
+
+class ProGuardSyntaxHighlighter : SyntaxHighlighterBase() {
+
+ override fun getHighlightingLexer(): Lexer = ProGuardLexerAdapter()
+
+ override fun getTokenHighlights(tokenType: IElementType?): Array =
+ when (tokenType) {
+ ProGuardTypes.PRIMITIVE -> PRIMITIVE_KEYS
+ ProGuardTypes.NUMBER -> NUMBER_KEYS
+ ProGuardTypes.DOT -> DOT_KEYS
+ ProGuardTypes.COMMA -> COMMA_KEYS
+ ProGuardTypes.COMMENT -> COMMENT_KEYS
+ ProGuardTypes.COLON -> COLON_KEYS
+ ProGuardTypes.POINTER -> POINTER_KEYS
+ else -> TextAttributesKey.EMPTY_ARRAY
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ companion object {
+ val CLASS_NAME = TextAttributesKey.createTextAttributesKey(
+ "PROGUARD_CLASS_NAME",
+ DefaultLanguageHighlighterColors.STRING
+ )
+ val METHOD = TextAttributesKey.createTextAttributesKey(
+ "PROGUARD_METHOD",
+ DefaultLanguageHighlighterColors.INSTANCE_METHOD
+ )
+ val FIELD = TextAttributesKey.createTextAttributesKey(
+ "PROGUARD_FIELD",
+ DefaultLanguageHighlighterColors.INSTANCE_FIELD
+ )
+ val PRIMITIVE = TextAttributesKey.createTextAttributesKey(
+ "PROGUARD_PRIMITIVE",
+ DefaultLanguageHighlighterColors.KEYWORD
+ )
+ val NUMBER = TextAttributesKey.createTextAttributesKey(
+ "PROGUARD_NUMBER",
+ DefaultLanguageHighlighterColors.NUMBER
+ )
+ val COMMENT = TextAttributesKey.createTextAttributesKey(
+ "PROGUARD_COMMENT",
+ DefaultLanguageHighlighterColors.LINE_COMMENT
+ )
+ val COMMA = TextAttributesKey.createTextAttributesKey(
+ "PROGUARD_COMMA",
+ DefaultLanguageHighlighterColors.DOT
+ )
+ val DOT = TextAttributesKey.createTextAttributesKey(
+ "PROGUARD_DOT",
+ COMMA
+ )
+ val COLON = TextAttributesKey.createTextAttributesKey(
+ "PROGUARD_COLON",
+ DOT
+ )
+ val POINTER = TextAttributesKey.createTextAttributesKey(
+ "PROGUARD_POINTER",
+ DefaultLanguageHighlighterColors.INLAY_TEXT_WITHOUT_BACKGROUND
+ )
+
+ private val PRIMITIVE_KEYS = arrayOf(PRIMITIVE)
+ private val NUMBER_KEYS = arrayOf(NUMBER)
+ private val COMMENT_KEYS = arrayOf(COMMENT)
+ private val COMMA_KEYS = arrayOf(COMMA)
+ private val DOT_KEYS = arrayOf(DOT)
+ private val COLON_KEYS = arrayOf(COLON)
+ private val POINTER_KEYS = arrayOf(POINTER)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/colors/ProGuardSyntaxHighlighterFactory.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/colors/ProGuardSyntaxHighlighterFactory.kt
new file mode 100644
index 000000000..18ab0b539
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/colors/ProGuardSyntaxHighlighterFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang.colors
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighter
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+
+class ProGuardSyntaxHighlighterFactory : SyntaxHighlighterFactory() {
+ override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?): SyntaxHighlighter =
+ ProGuardSyntaxHighlighter()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/psi/ProGuardElementType.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/psi/ProGuardElementType.kt
new file mode 100644
index 000000000..cb64d0ed7
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/psi/ProGuardElementType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.proguard.lang.ProGuardLanguage
+import org.jetbrains.annotations.NonNls
+
+class ProGuardElementType(@NonNls debugName: String) : IElementType(debugName, ProGuardLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/psi/ProGuardLexerAdapter.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/psi/ProGuardLexerAdapter.kt
new file mode 100644
index 000000000..9c99f8348
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/psi/ProGuardLexerAdapter.kt
@@ -0,0 +1,26 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang.psi
+
+import com.intellij.lexer.FlexAdapter
+import io.mcdev.obfex.formats.proguard.gen.ProGuardLexer
+
+class ProGuardLexerAdapter : FlexAdapter(ProGuardLexer())
diff --git a/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/psi/ProGuardTokenType.kt b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/psi/ProGuardTokenType.kt
new file mode 100644
index 000000000..23df1a981
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/proguard/lang/psi/ProGuardTokenType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.proguard.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.proguard.lang.ProGuardLanguage
+import org.jetbrains.annotations.NonNls
+
+class ProGuardTokenType(@NonNls debugName: String) : IElementType(debugName, ProGuardLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/SrgMappingsFormatParser.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/SrgMappingsFormatParser.kt
new file mode 100644
index 000000000..113b170a6
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/SrgMappingsFormatParser.kt
@@ -0,0 +1,180 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg
+
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.MappingPart
+import io.mcdev.obfex.forLines
+import io.mcdev.obfex.mappings.FileCoords
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsDefinitionBuilder
+import io.mcdev.obfex.mappings.UnnamedMappingsFormatParser
+import io.mcdev.obfex.mappings.clazz
+import io.mcdev.obfex.mappings.field
+import io.mcdev.obfex.mappings.method
+import io.mcdev.obfex.mappings.pack
+import io.mcdev.obfex.ref.asClass
+import io.mcdev.obfex.ref.asField
+import io.mcdev.obfex.ref.asMethod
+import io.mcdev.obfex.ref.asMethodDesc
+import io.mcdev.obfex.ref.asPackage
+import io.mcdev.obfex.ref.highPriority
+import io.mcdev.obfex.ref.lowPriority
+import io.mcdev.obfex.splitOnLast
+
+@Suppress("DuplicatedCode")
+class SrgMappingsFormatParser : UnnamedMappingsFormatParser {
+
+ override val expectedFileExtensions: Array = arrayOf("srg")
+
+ override fun parse(file: VirtualFile): MappingsDefinition {
+ val builder = MappingsDefinitionBuilder(SrgMappingsFormatType, file)
+
+ file.forLines { lineNum, parts, _ ->
+ if (parts.isEmpty()) {
+ return@forLines true
+ }
+
+ val key = parts.first()
+
+ when (key.value) {
+ "PK:" -> parsePackageLine(builder, lineNum, parts)
+ "CL:" -> parseClassLine(builder, lineNum, parts)
+ "MD:" -> parseMethodLine(builder, lineNum, parts)
+ "FD:" -> parseFieldLine(builder, lineNum, parts)
+ else -> builder.error("Unrecognized key: ${key.value.substring(0, 3)}", FileCoords(lineNum))
+ }
+
+ return@forLines true
+ }
+
+ return builder.build()
+ }
+
+ private fun parsePackageLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 3) {
+ builder.error("Package line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (_, leftPack, rightPack) = parts
+ builder.pack(lineNum.highPriority) {
+ with(leftPack.asPackage().from)
+ with(rightPack.asPackage().to)
+ }
+ }
+
+ private fun parseClassLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 3) {
+ builder.error("Class line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (_, leftClass, rightClass) = parts
+ builder.clazz(lineNum.highPriority) {
+ with(leftClass.asClass().from)
+ with(rightClass.asClass().to)
+ }
+ }
+
+ private fun parseFieldLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 3) {
+ builder.error("Field line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (leftClass, leftField) = parts[1].splitOnLast('/')
+ if (leftField == null) {
+ builder.error(
+ "Field mapping is invalid, no field is specified after class",
+ FileCoords(lineNum, parts[1])
+ )
+ return
+ }
+
+ val (rightClass, rightField) = parts[2].splitOnLast('/')
+ if (rightField == null) {
+ builder.error(
+ "Field mapping is invalid, no field is specified after class",
+ FileCoords(lineNum, parts[2])
+ )
+ return
+ }
+
+ builder.clazz(leftClass.asClass().from, lineNum.lowPriority) {
+ unlessExists(rightClass.asClass().to)
+
+ field(lineNum.highPriority) {
+ with(leftField.asField().from)
+ with(rightField.asField().to)
+ }
+ }
+ }
+
+ private fun parseMethodLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 5) {
+ builder.error("Method line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (leftClass, leftMethod) = parts[1].splitOnLast('/')
+ if (leftMethod == null) {
+ builder.error(
+ "Method mapping is invalid, no method is specified after class",
+ FileCoords(lineNum, parts[1])
+ )
+ return
+ }
+
+ val (rightClass, rightMethod) = parts[3].splitOnLast('/')
+ if (rightMethod == null) {
+ builder.error(
+ "Method mapping is invalid, no method is specified after class",
+ FileCoords(lineNum, parts[3])
+ )
+ return
+ }
+
+ val leftDesc = parts[2].value.asMethodDesc()
+ if (leftDesc == null) {
+ builder.error("Method descriptor is invalid", FileCoords(lineNum, parts[2]))
+ return
+ }
+
+ // we don't actually store this descriptor, but we verify it for valid syntax
+ val rightDesc = parts[4].value.asMethodDesc()
+ if (rightDesc == null) {
+ builder.error("Method descriptor is invalid", FileCoords(lineNum, parts[4]))
+ return
+ }
+
+ builder.clazz(leftClass.asClass().from, lineNum.lowPriority) {
+ unlessExists(rightClass.asClass().to)
+
+ method(lineNum.highPriority) {
+ desc = leftDesc
+
+ with(leftMethod.asMethod().from)
+ with(rightMethod.asMethod().to)
+ }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/SrgMappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/SrgMappingsFormatType.kt
new file mode 100644
index 000000000..0b2ee94ba
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/SrgMappingsFormatType.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg
+
+import io.mcdev.obfex.ObfIcons
+import io.mcdev.obfex.formats.MappingsFormatType
+import javax.swing.Icon
+
+object SrgMappingsFormatType : MappingsFormatType("SRG") {
+
+ override val icon: Icon = ObfIcons.SRG_ICON
+ override val name: String = "SRG"
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgFile.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgFile.kt
new file mode 100644
index 000000000..dae743ad7
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgFile.kt
@@ -0,0 +1,30 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang
+
+import com.intellij.extapi.psi.PsiFileBase
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.psi.FileViewProvider
+
+class SrgFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, SrgLanguage) {
+ override fun getFileType(): FileType = SrgFileType
+ override fun toString(): String = SrgFileType.description
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgFileType.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgFileType.kt
new file mode 100644
index 000000000..5bf893600
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgFileType.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import io.mcdev.obfex.ObfIcons
+import javax.swing.Icon
+
+object SrgFileType : LanguageFileType(SrgLanguage) {
+
+ override fun getName(): String = "SRG"
+ override fun getDescription(): String = "SRG obfuscation mapping file"
+ override fun getDefaultExtension(): String = "srg"
+ override fun getIcon(): Icon = ObfIcons.SRG_ICON
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgLanguage.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgLanguage.kt
new file mode 100644
index 000000000..8dfe82567
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgLanguage.kt
@@ -0,0 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang
+
+import com.intellij.lang.Language
+
+object SrgLanguage : Language("SRG")
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgParserDefinition.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgParserDefinition.kt
new file mode 100644
index 000000000..01523cea4
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/SrgParserDefinition.kt
@@ -0,0 +1,50 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.Language
+import com.intellij.lang.ParserDefinition
+import com.intellij.lang.PsiParser
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.project.Project
+import com.intellij.psi.FileViewProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.tree.IFileElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.srg.gen.SrgParser
+import io.mcdev.obfex.formats.srg.gen.psi.SrgTypes
+import io.mcdev.obfex.formats.srg.lang.psi.SrgLexerAdapter
+
+class SrgParserDefinition : ParserDefinition {
+
+ override fun createLexer(project: Project): Lexer = SrgLexerAdapter()
+ override fun getCommentTokens(): TokenSet = COMMENTS
+ override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
+ override fun createParser(project: Project): PsiParser = SrgParser()
+ override fun getFileNodeType(): IFileElementType = FILE
+ override fun createFile(viewProvider: FileViewProvider): PsiFile = SrgFile(viewProvider)
+ override fun createElement(node: ASTNode): PsiElement = SrgTypes.Factory.createElement(node)
+}
+
+private val COMMENTS = TokenSet.create(SrgTypes.COMMENT)
+private val FILE = IFileElementType(Language.findInstance(SrgLanguage::class.java))
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/colors/SrgAnnotator.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/colors/SrgAnnotator.kt
new file mode 100644
index 000000000..1dc744e46
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/colors/SrgAnnotator.kt
@@ -0,0 +1,72 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang.colors
+
+import com.intellij.lang.annotation.AnnotationHolder
+import com.intellij.lang.annotation.Annotator
+import com.intellij.lang.annotation.HighlightSeverity
+import com.intellij.psi.PsiElement
+import io.mcdev.obfex.formats.srg.gen.psi.SrgClassMapping
+import io.mcdev.obfex.formats.srg.gen.psi.SrgFieldMapping
+import io.mcdev.obfex.formats.srg.gen.psi.SrgMethodMapping
+import io.mcdev.obfex.formats.srg.lang.SrgLanguage
+import io.mcdev.obfex.formats.util.registerHighlight
+
+class SrgAnnotator : Annotator {
+
+ override fun annotate(element: PsiElement, holder: AnnotationHolder) {
+ when (element) {
+ is SrgClassMapping -> {
+ registerHighlight(element.obfName, SrgSyntaxHighlighter.CLASS_NAME, holder)
+ registerHighlight(element.deobfName, SrgSyntaxHighlighter.CLASS_NAME, holder, true)
+ }
+
+ is SrgMethodMapping -> {
+ registerHighlight(element.obfName, SrgSyntaxHighlighter.CLASS_NAME, holder)
+ element.obfName?.identifier?.namePartList?.last()?.let { name ->
+ registerHighlight(name, SrgSyntaxHighlighter.METHOD, holder)
+ }
+ registerHighlight(element.deobfName, SrgSyntaxHighlighter.CLASS_NAME, holder, true)
+ element.deobfName?.identifier?.namePartList?.last()?.let { name ->
+ registerHighlight(name, SrgSyntaxHighlighter.METHOD, holder, true)
+ }
+ }
+
+ is SrgFieldMapping -> {
+ registerHighlight(element.obfName, SrgSyntaxHighlighter.CLASS_NAME, holder)
+ element.obfName?.identifier?.namePartList?.last()?.let { name ->
+ registerHighlight(name, SrgSyntaxHighlighter.FIELD, holder)
+ }
+ registerHighlight(element.deobfName, SrgSyntaxHighlighter.CLASS_NAME, holder, true)
+ element.deobfName?.identifier?.namePartList?.last()?.let { name ->
+ registerHighlight(name, SrgSyntaxHighlighter.FIELD, holder, true)
+ }
+
+ if (element.getExtendedFieldMapping() != null && element.containingFile.language == SrgLanguage) {
+ val msg = "Extended field mappings are not allowed in standard SRG mapping files"
+ holder.newAnnotation(HighlightSeverity.ERROR, msg)
+ .range(element)
+ .create()
+ }
+ }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/colors/SrgSyntaxHighlighter.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/colors/SrgSyntaxHighlighter.kt
new file mode 100644
index 000000000..ad60e9c3c
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/colors/SrgSyntaxHighlighter.kt
@@ -0,0 +1,78 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang.colors
+
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.srg.gen.psi.SrgTypes
+import io.mcdev.obfex.formats.srg.lang.psi.SrgLexerAdapter
+
+class SrgSyntaxHighlighter : SyntaxHighlighterBase() {
+
+ override fun getHighlightingLexer(): Lexer = SrgLexerAdapter()
+
+ override fun getTokenHighlights(tokenType: IElementType?): Array {
+ return when (tokenType) {
+ SrgTypes.PACKAGE_KEY, SrgTypes.CLASS_KEY, SrgTypes.FIELD_KEY, SrgTypes.METHOD_KEY -> KEYWORD_KEYS
+ SrgTypes.CLASS_TYPE -> CLASS_TYPE_KEYS
+ SrgTypes.PRIMITIVE -> PRIMITIVE_KEYS
+ SrgTypes.COMMENT -> COMMENT_KEYS
+ else -> TextAttributesKey.EMPTY_ARRAY
+ }
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ companion object {
+ val KEYWORD = TextAttributesKey.createTextAttributesKey("SRG_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD)
+ val CLASS_NAME = TextAttributesKey.createTextAttributesKey(
+ "SRG_CLASS_NAME",
+ DefaultLanguageHighlighterColors.STRING
+ )
+ val METHOD = TextAttributesKey.createTextAttributesKey(
+ "SRG_METHOD",
+ DefaultLanguageHighlighterColors.INSTANCE_METHOD
+ )
+ val FIELD = TextAttributesKey.createTextAttributesKey(
+ "SRG_FIELD",
+ DefaultLanguageHighlighterColors.INSTANCE_FIELD
+ )
+ val PRIMITIVE = TextAttributesKey.createTextAttributesKey(
+ "SRG_PRIMITIVE",
+ DefaultLanguageHighlighterColors.NUMBER
+ )
+ val CLASS_TYPE = TextAttributesKey.createTextAttributesKey(
+ "SRG_CLASS_TYPE",
+ DefaultLanguageHighlighterColors.CLASS_REFERENCE
+ )
+ val COMMENT = TextAttributesKey.createTextAttributesKey(
+ "SRG_COMMENT",
+ DefaultLanguageHighlighterColors.LINE_COMMENT
+ )
+
+ private val KEYWORD_KEYS = arrayOf(KEYWORD)
+ private val PRIMITIVE_KEYS = arrayOf(PRIMITIVE)
+ private val CLASS_TYPE_KEYS = arrayOf(CLASS_TYPE)
+ private val COMMENT_KEYS = arrayOf(COMMENT)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/colors/SrgSyntaxHighlighterFactory.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/colors/SrgSyntaxHighlighterFactory.kt
new file mode 100644
index 000000000..a0a5f4074
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/colors/SrgSyntaxHighlighterFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang.colors
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighter
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+
+class SrgSyntaxHighlighterFactory : SyntaxHighlighterFactory() {
+ override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?): SyntaxHighlighter =
+ SrgSyntaxHighlighter()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/SrgElementType.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/SrgElementType.kt
new file mode 100644
index 000000000..e6e20f8f5
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/SrgElementType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.srg.lang.SrgLanguage
+import org.jetbrains.annotations.NonNls
+
+class SrgElementType(@NonNls debugName: String) : IElementType(debugName, SrgLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/SrgLexerAdapter.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/SrgLexerAdapter.kt
new file mode 100644
index 000000000..8e4c08a3e
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/SrgLexerAdapter.kt
@@ -0,0 +1,26 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang.psi
+
+import com.intellij.lexer.FlexAdapter
+import io.mcdev.obfex.formats.srg.gen.SrgLexer
+
+class SrgLexerAdapter : FlexAdapter(SrgLexer())
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/SrgTokenType.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/SrgTokenType.kt
new file mode 100644
index 000000000..e4fa4a3db
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/SrgTokenType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.srg.lang.SrgLanguage
+import org.jetbrains.annotations.NonNls
+
+class SrgTokenType(@NonNls debugName: String) : IElementType(debugName, SrgLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/mixins/SrgFieldMappingMixin.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/mixins/SrgFieldMappingMixin.kt
new file mode 100644
index 000000000..fb6f8c41f
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/mixins/SrgFieldMappingMixin.kt
@@ -0,0 +1,36 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang.psi.mixins
+
+import com.intellij.psi.PsiElement
+import io.mcdev.obfex.formats.srg.gen.psi.SrgExtendedFieldMapping
+import io.mcdev.obfex.formats.srg.gen.psi.SrgMappingPart
+import io.mcdev.obfex.formats.srg.gen.psi.SrgStandardFieldMapping
+
+interface SrgFieldMappingMixin : PsiElement {
+ fun getExtendedFieldMapping(): SrgExtendedFieldMapping?
+
+ fun getStandardFieldMapping(): SrgStandardFieldMapping?
+
+ val mappingPartList: List
+ val obfName: SrgMappingPart?
+ val deobfName: SrgMappingPart?
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/mixins/impl/SrgFieldMappingImplMixin.kt b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/mixins/impl/SrgFieldMappingImplMixin.kt
new file mode 100644
index 000000000..e4917bda5
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/srg/lang/psi/mixins/impl/SrgFieldMappingImplMixin.kt
@@ -0,0 +1,38 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.srg.lang.psi.mixins.impl
+
+import com.intellij.extapi.psi.ASTWrapperPsiElement
+import com.intellij.lang.ASTNode
+import io.mcdev.obfex.formats.srg.gen.psi.SrgMappingPart
+import io.mcdev.obfex.formats.srg.lang.psi.mixins.SrgFieldMappingMixin
+
+abstract class SrgFieldMappingImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), SrgFieldMappingMixin {
+
+ override val mappingPartList: List
+ get() = getStandardFieldMapping()?.mappingPartList ?: getStandardFieldMapping()?.mappingPartList ?: emptyList()
+
+ override val obfName: SrgMappingPart?
+ get() = getStandardFieldMapping()?.obfName ?: getExtendedFieldMapping()?.obfName
+
+ override val deobfName: SrgMappingPart?
+ get() = getStandardFieldMapping()?.deobfName ?: getExtendedFieldMapping()?.deobfName
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/TinyV1MappingsFormatParser.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/TinyV1MappingsFormatParser.kt
new file mode 100644
index 000000000..222dd6255
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/TinyV1MappingsFormatParser.kt
@@ -0,0 +1,193 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1
+
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.MappingPart
+import io.mcdev.obfex.forLines
+import io.mcdev.obfex.formats.util.indicesFrom
+import io.mcdev.obfex.mappings.FileCoords
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsDefinitionBuilder
+import io.mcdev.obfex.mappings.MappingsFormatParser
+import io.mcdev.obfex.mappings.clazz
+import io.mcdev.obfex.mappings.field
+import io.mcdev.obfex.mappings.method
+import io.mcdev.obfex.mappings.ns
+import io.mcdev.obfex.ref.asClass
+import io.mcdev.obfex.ref.asField
+import io.mcdev.obfex.ref.asMethod
+import io.mcdev.obfex.ref.asMethodDesc
+import io.mcdev.obfex.ref.asTypeDef
+import io.mcdev.obfex.ref.highPriority
+import io.mcdev.obfex.ref.lowPriority
+import io.mcdev.obfex.splitMappingLine
+
+class TinyV1MappingsFormatParser : MappingsFormatParser {
+
+ override val expectedFileExtensions: Array = arrayOf("tiny")
+
+ override fun isSupportedFile(file: VirtualFile): Boolean {
+ val firstLine = file.inputStream.bufferedReader(file.charset).use { reader ->
+ reader.readLine()
+ }
+ return getNamespaces(firstLine) != null
+ }
+
+ override fun parse(file: VirtualFile): MappingsDefinition? {
+ val firstLine = file.inputStream.bufferedReader(file.charset).use { reader ->
+ reader.readLine()
+ }
+
+ val namespaces = getNamespaces(firstLine) ?: return null
+
+ val builder = MappingsDefinitionBuilder(TinyV1MappingsFormatType, file, *namespaces)
+
+ file.forLines(skipLines = 1, preserveBlank = true) { lineNum, parts, _ ->
+ if (parts.isEmpty()) {
+ return@forLines true
+ }
+
+ val key = parts[0].value
+ when (key) {
+ "CLASS" -> parseClass(builder, namespaces.size, lineNum, parts)
+ "FIELD" -> parseField(builder, namespaces.size, lineNum, parts)
+ "METHOD" -> parseMethod(builder, namespaces.size, lineNum, parts)
+ }
+
+ return@forLines true
+ }
+
+ return builder.build()
+ }
+
+ private fun getNamespaces(line: String?): Array? {
+ if (line == null) {
+ return null
+ }
+
+ val header = line.splitMappingLine()
+ if (header.size < 3) {
+ return null
+ }
+
+ if (header[0].value != "v1") {
+ return null
+ }
+
+ val namespaces = arrayOfNulls(header.size - 1)
+ for (index in 1 until header.size) {
+ namespaces[index - 1] = header[index].value
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return namespaces as Array
+ }
+
+ private fun parseClass(
+ builder: MappingsDefinitionBuilder,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ) {
+ if (parts.size - 1 > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size == 1) {
+ builder.warning("Class mapping line has no names", FileCoords(lineNum))
+ return
+ }
+
+ builder.clazz(lineNum.highPriority) {
+ for (i in parts.indicesFrom(1)) {
+ with(parts[i].asClass().ns(i - 1))
+ }
+ }
+ }
+
+ private fun parseField(
+ builder: MappingsDefinitionBuilder,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ) {
+ if (parts.size - 3 > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size < 3) {
+ builder.error("Field mapping line does not have all necessary components", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size == 3) {
+ builder.warning("Field mapping line has no names", FileCoords(lineNum))
+ }
+
+ val owner = parts[1]
+ val fieldType = parts[2].value.asTypeDef()
+ ?: return builder.error("Field mapping has an invalid type", FileCoords(lineNum, parts[2]))
+
+ builder.clazz(owner.asClass().ns(0), lineNum.lowPriority).field {
+ type = fieldType
+
+ for (i in parts.indicesFrom(3)) {
+ with(parts[i].asField().ns(i - 3))
+ }
+ }
+ }
+
+ private fun parseMethod(
+ builder: MappingsDefinitionBuilder,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ) {
+ if (parts.size - 3 > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size < 3) {
+ builder.error("Method mapping line does not have all necessary components", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size == 3) {
+ builder.warning("Method mapping line has no names", FileCoords(lineNum))
+ }
+
+ val owner = parts[1]
+ val methodDesc = parts[2].value.asMethodDesc()
+ ?: return builder.error("Method mapping has an invalid descriptor", FileCoords(lineNum, parts[2]))
+
+ builder.clazz(owner.asClass().ns(0)).method {
+ desc = methodDesc
+
+ for (i in parts.indicesFrom(3)) {
+ with(parts[i].asMethod().ns(i - 3))
+ }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/TinyV1MappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/TinyV1MappingsFormatType.kt
new file mode 100644
index 000000000..97fa275e0
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/TinyV1MappingsFormatType.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1
+
+import io.mcdev.obfex.ObfIcons
+import io.mcdev.obfex.formats.MappingsFormatType
+import javax.swing.Icon
+
+object TinyV1MappingsFormatType : MappingsFormatType("tiny-v1") {
+
+ override val icon: Icon = ObfIcons.TINY_V1_ICON
+ override val name: String = "Tiny v2"
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyFileTypeDetector.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyFileTypeDetector.kt
new file mode 100644
index 000000000..1335d5f16
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyFileTypeDetector.kt
@@ -0,0 +1,48 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang
+
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.openapi.fileTypes.FileTypeRegistry
+import com.intellij.openapi.util.io.ByteSequence
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.formats.tinyv2.lang.TinyV2FileType
+
+class TinyFileTypeDetector : FileTypeRegistry.FileTypeDetector {
+
+ override fun detect(file: VirtualFile, firstBytes: ByteSequence, firstCharsIfText: CharSequence?): FileType? {
+ if (firstCharsIfText == null) {
+ return null
+ }
+
+ if (!file.name.endsWith(".tiny")) {
+ return null
+ }
+
+ if (firstCharsIfText.startsWith("v1")) {
+ return TinyV1FileType
+ } else if (firstCharsIfText.startsWith("tiny\t2\t0")) {
+ return TinyV2FileType
+ }
+
+ return null
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1File.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1File.kt
new file mode 100644
index 000000000..8ce70d216
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1File.kt
@@ -0,0 +1,30 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang
+
+import com.intellij.extapi.psi.PsiFileBase
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.psi.FileViewProvider
+
+class TinyV1File(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, TinyV1Language) {
+ override fun getFileType(): FileType = TinyV1FileType
+ override fun toString(): String = TinyV1FileType.description
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1FileType.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1FileType.kt
new file mode 100644
index 000000000..81a6e88ca
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1FileType.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import io.mcdev.obfex.ObfIcons
+import javax.swing.Icon
+
+object TinyV1FileType : LanguageFileType(TinyV1Language) {
+
+ override fun getName(): String = "Tiny v1"
+ override fun getDescription(): String = "Tiny v1 obfuscation mapping file"
+ override fun getDefaultExtension(): String = "tiny"
+ override fun getIcon(): Icon = ObfIcons.TINY_V1_ICON
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1Language.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1Language.kt
new file mode 100644
index 000000000..96e41f60e
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1Language.kt
@@ -0,0 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang
+
+import com.intellij.lang.Language
+
+object TinyV1Language : Language("TinyV1")
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1ParserDefinition.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1ParserDefinition.kt
new file mode 100644
index 000000000..ef41f962d
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/TinyV1ParserDefinition.kt
@@ -0,0 +1,50 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.Language
+import com.intellij.lang.ParserDefinition
+import com.intellij.lang.PsiParser
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.project.Project
+import com.intellij.psi.FileViewProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.tree.IFileElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.tinyv1.gen.TinyV1Parser
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1Types
+import io.mcdev.obfex.formats.tinyv1.lang.psi.TinyV1LexerAdapter
+
+class TinyV1ParserDefinition : ParserDefinition {
+
+ override fun createLexer(project: Project?): Lexer = TinyV1LexerAdapter()
+ override fun getCommentTokens(): TokenSet = COMMENTS
+ override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
+ override fun createParser(project: Project?): PsiParser = TinyV1Parser()
+ override fun getFileNodeType(): IFileElementType = FILE
+ override fun createFile(viewProvider: FileViewProvider): PsiFile = TinyV1File(viewProvider)
+ override fun createElement(node: ASTNode): PsiElement = TinyV1Types.Factory.createElement(node)
+}
+
+private val COMMENTS = TokenSet.create(TinyV1Types.COMMENT)
+private val FILE = IFileElementType(Language.findInstance(TinyV1Language::class.java))
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1Annotator.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1Annotator.kt
new file mode 100644
index 000000000..0ad56fbc5
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1Annotator.kt
@@ -0,0 +1,51 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang.colors
+
+import com.intellij.lang.annotation.AnnotationHolder
+import com.intellij.lang.annotation.Annotator
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.childrenOfType
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1ClassMapping
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1FieldMapping
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1MappingPart
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1MethodMapping
+import io.mcdev.obfex.formats.util.registerHighlight
+
+class TinyV1Annotator : Annotator {
+
+ override fun annotate(element: PsiElement, holder: AnnotationHolder) {
+ val key = when (element) {
+ is TinyV1ClassMapping -> TinyV1SyntaxHighlighter.CLASS_NAME
+ is TinyV1MethodMapping -> TinyV1SyntaxHighlighter.METHOD
+ is TinyV1FieldMapping -> TinyV1SyntaxHighlighter.FIELD
+ else -> return
+ }
+
+ for ((i, part) in element.childrenOfType().withIndex()) {
+ if (i == 0) {
+ registerHighlight(part, TinyV1SyntaxHighlighter.CLASS_NAME, holder)
+ } else {
+ registerHighlight(part, key, holder)
+ }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1RainbowVisitor.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1RainbowVisitor.kt
new file mode 100644
index 000000000..3c92da078
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1RainbowVisitor.kt
@@ -0,0 +1,94 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang.colors
+
+import com.intellij.codeInsight.daemon.impl.HighlightVisitor
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.findParentOfType
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1ClassMapping
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1FieldMapping
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1FileElement
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1MappingPart
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1MethodMapping
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1MethodSignature
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1Namespace
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1TypeDesc
+import io.mcdev.obfex.formats.tinyv1.lang.TinyV1Language
+import io.mcdev.obfex.formats.util.NamespaceRainbowVisitor
+
+class TinyV1RainbowVisitor : NamespaceRainbowVisitor(TinyV1Language) {
+
+ override fun visit(element: PsiElement) {
+ if (element is TinyV1Namespace) {
+ highlightNamespaceDecl(element)
+ return
+ }
+
+ val file = element.findParentOfType() ?: return
+ val namespaces = file.header.namespaceList
+
+ if (element is TinyV1MappingPart) {
+ handleMappingPart(element, namespaces)
+ } else if (element is TinyV1TypeDesc && element.parent is TinyV1FieldMapping) {
+ handleTypeSignature(element, namespaces)
+ } else if (element is TinyV1MethodSignature) {
+ handleTypeSignature(element, namespaces)
+ }
+ }
+
+ private fun handleMappingPart(element: TinyV1MappingPart, namespaces: List) {
+ var index = indexOf(element)
+
+ if (element.parent is TinyV1ClassMapping) {
+ if (namespaces.size > index) {
+ highlightElement(element, index, HighlightType.CLASS)
+ }
+ return
+ }
+
+ // fields and methods
+ // first part is always class name
+ if (index == 0 && namespaces.isNotEmpty()) {
+ highlightElement(element, index, HighlightType.CLASS)
+ return
+ }
+
+ index--
+ if (namespaces.size <= index) {
+ return
+ }
+
+ when (element.parent) {
+ is TinyV1MethodMapping -> highlightElement(element, index, HighlightType.METHOD)
+ is TinyV1FieldMapping -> highlightElement(element, index, HighlightType.FIELD)
+ }
+ }
+
+ private fun handleTypeSignature(element: PsiElement, namespaces: List) {
+ if (namespaces.isEmpty()) {
+ return
+ }
+
+ highlightTypeSignature(element)
+ }
+
+ override fun clone(): HighlightVisitor = TinyV1RainbowVisitor()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1SyntaxHighlighter.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1SyntaxHighlighter.kt
new file mode 100644
index 000000000..53efb8b09
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1SyntaxHighlighter.kt
@@ -0,0 +1,87 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang.colors
+
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tinyv1.gen.psi.TinyV1Types
+import io.mcdev.obfex.formats.tinyv1.lang.psi.TinyV1LexerAdapter
+
+class TinyV1SyntaxHighlighter : SyntaxHighlighterBase() {
+
+ override fun getHighlightingLexer(): Lexer = TinyV1LexerAdapter()
+
+ override fun getTokenHighlights(tokenType: IElementType?): Array {
+ return when (tokenType) {
+ TinyV1Types.V1_KEY, TinyV1Types.CLASS_KEY, TinyV1Types.FIELD_KEY, TinyV1Types.METHOD_KEY -> KEYWORD_KEYS
+ TinyV1Types.NAMESPACE_KEY -> NAMESPACE_KEYS
+ TinyV1Types.CLASS_TYPE -> CLASS_TYPE_KEYS
+ TinyV1Types.PRIMITIVE -> PRIMITIVE_KEYS
+ TinyV1Types.COMMENT -> COMMENT_KEYS
+ else -> TextAttributesKey.EMPTY_ARRAY
+ }
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ companion object {
+ val KEYWORD = TextAttributesKey.createTextAttributesKey(
+ "TINYV1_KEYWORD",
+ DefaultLanguageHighlighterColors.KEYWORD
+ )
+ val NAMESPACE = TextAttributesKey.createTextAttributesKey(
+ "TINYV1_NAMESPACE",
+ DefaultLanguageHighlighterColors.METADATA
+ )
+ val CLASS_NAME = TextAttributesKey.createTextAttributesKey(
+ "TINYV1_CLASS_NAME",
+ DefaultLanguageHighlighterColors.STRING
+ )
+ val METHOD = TextAttributesKey.createTextAttributesKey(
+ "TINYV1_METHOD",
+ DefaultLanguageHighlighterColors.INSTANCE_METHOD
+ )
+ val FIELD = TextAttributesKey.createTextAttributesKey(
+ "TINYV1_FIELD",
+ DefaultLanguageHighlighterColors.INSTANCE_FIELD
+ )
+ val PRIMITIVE = TextAttributesKey.createTextAttributesKey(
+ "TINYV1_PRIMITIVE",
+ DefaultLanguageHighlighterColors.NUMBER
+ )
+ val CLASS_TYPE = TextAttributesKey.createTextAttributesKey(
+ "TINYV1_CLASS_TYPE",
+ DefaultLanguageHighlighterColors.CLASS_REFERENCE
+ )
+ val COMMENT = TextAttributesKey.createTextAttributesKey(
+ "TINYV1_COMMENT",
+ DefaultLanguageHighlighterColors.LINE_COMMENT
+ )
+
+ private val KEYWORD_KEYS = arrayOf(KEYWORD)
+ private val NAMESPACE_KEYS = arrayOf(NAMESPACE)
+ private val PRIMITIVE_KEYS = arrayOf(PRIMITIVE)
+ private val CLASS_TYPE_KEYS = arrayOf(CLASS_TYPE)
+ private val COMMENT_KEYS = arrayOf(COMMENT)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1SyntaxHighlighterFactory.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1SyntaxHighlighterFactory.kt
new file mode 100644
index 000000000..b60e6c0e6
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/colors/TinyV1SyntaxHighlighterFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang.colors
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighter
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+
+class TinyV1SyntaxHighlighterFactory : SyntaxHighlighterFactory() {
+ override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?): SyntaxHighlighter =
+ TinyV1SyntaxHighlighter()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/psi/TinyV1ElementType.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/psi/TinyV1ElementType.kt
new file mode 100644
index 000000000..b8feef501
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/psi/TinyV1ElementType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tinyv1.lang.TinyV1Language
+import org.jetbrains.annotations.NonNls
+
+class TinyV1ElementType(@NonNls debugName: String) : IElementType(debugName, TinyV1Language)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/psi/TinyV1LexerAdapter.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/psi/TinyV1LexerAdapter.kt
new file mode 100644
index 000000000..7fa56b00a
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/psi/TinyV1LexerAdapter.kt
@@ -0,0 +1,26 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang.psi
+
+import com.intellij.lexer.FlexAdapter
+import io.mcdev.obfex.formats.tinyv1.gen.TinyV1Lexer
+
+class TinyV1LexerAdapter : FlexAdapter(TinyV1Lexer())
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/psi/TinyV1TokenType.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/psi/TinyV1TokenType.kt
new file mode 100644
index 000000000..3cc0cbc8e
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv1/lang/psi/TinyV1TokenType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv1.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tinyv1.lang.TinyV1Language
+import org.jetbrains.annotations.NonNls
+
+class TinyV1TokenType(@NonNls debugName: String) : IElementType(debugName, TinyV1Language)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/TinyV2LanguageCodeStyleSettingsProvider.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/TinyV2LanguageCodeStyleSettingsProvider.kt
new file mode 100644
index 000000000..80747f030
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/TinyV2LanguageCodeStyleSettingsProvider.kt
@@ -0,0 +1,43 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2
+
+import io.mcdev.obfex.formats.tinyv2.lang.TinyV2Language
+import io.mcdev.obfex.formats.util.TabCodeStyleSettingsProvider
+import org.intellij.lang.annotations.Language
+
+class TinyV2LanguageCodeStyleSettingsProvider : TabCodeStyleSettingsProvider() {
+ override fun getLanguage() = TinyV2Language
+ override fun getCodeSample(settingsType: SettingsType) = SAMPLE
+}
+
+@Language("TinyV2")
+private const val SAMPLE = """
+tiny 2 0 source target target2
+c class_1 class1Ns0Rename class1Ns1Rename
+ f I field_1 field1Ns0Rename field1Ns1Rename
+ m ()I method_1 method1Ns0Rename method1Ns1Rename
+ p 1 param_1 param1Ns0Rename param1Ns1Rename
+ v 2 2 2 var_1 var1Ns0Rename var1Ns1Rename
+c class_1${'$'}class_2 class1Ns0Rename${'$'}class2Ns0Rename class1Ns1Rename${'$'}class2Ns1Rename
+ f I field_2 field2Ns0Rename field2Ns1Rename
+c class_3 class3Ns0Rename class3Ns1Rename
+"""
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/TinyV2MappingsFormatParser.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/TinyV2MappingsFormatParser.kt
new file mode 100644
index 000000000..deeb8c98e
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/TinyV2MappingsFormatParser.kt
@@ -0,0 +1,325 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2
+
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.MappingPart
+import io.mcdev.obfex.forLinesIndent
+import io.mcdev.obfex.formats.util.indicesFrom
+import io.mcdev.obfex.mappings.ClassMappingBuilder
+import io.mcdev.obfex.mappings.FileCoords
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsDefinitionBuilder
+import io.mcdev.obfex.mappings.MappingsFormatParser
+import io.mcdev.obfex.mappings.MethodMappingBuilder
+import io.mcdev.obfex.mappings.clazz
+import io.mcdev.obfex.mappings.field
+import io.mcdev.obfex.mappings.localVar
+import io.mcdev.obfex.mappings.method
+import io.mcdev.obfex.mappings.ns
+import io.mcdev.obfex.mappings.param
+import io.mcdev.obfex.ref.LvtIndex
+import io.mcdev.obfex.ref.asClass
+import io.mcdev.obfex.ref.asField
+import io.mcdev.obfex.ref.asLocal
+import io.mcdev.obfex.ref.asLocalVar
+import io.mcdev.obfex.ref.asLvtIndex
+import io.mcdev.obfex.ref.asMethod
+import io.mcdev.obfex.ref.asMethodDesc
+import io.mcdev.obfex.ref.asParam
+import io.mcdev.obfex.ref.asParamIndex
+import io.mcdev.obfex.ref.asTypeDef
+import io.mcdev.obfex.ref.highPriority
+import io.mcdev.obfex.splitMappingLine
+
+class TinyV2MappingsFormatParser : MappingsFormatParser {
+
+ override val expectedFileExtensions: Array = arrayOf("tiny")
+
+ override fun isSupportedFile(file: VirtualFile): Boolean {
+ val firstLine = file.inputStream.bufferedReader(file.charset).use { reader ->
+ reader.readLine()
+ }
+ return getNamespaces(firstLine) != null
+ }
+
+ override fun parse(file: VirtualFile): MappingsDefinition? {
+ val firstLine = file.inputStream.bufferedReader(file.charset).use { reader ->
+ reader.readLine()
+ }
+
+ val namespaces = getNamespaces(firstLine) ?: return null
+
+ val builder = MappingsDefinitionBuilder(TinyV2MappingsFormatType, file, *namespaces)
+
+ var classMapping: ClassMappingBuilder? = null
+ var methodMapping: MethodMappingBuilder? = null
+ file.forLinesIndent(skipLines = 1, preserveBlank = true) { indent, lineNum, parts, _ ->
+ if (parts.isEmpty()) {
+ return@forLinesIndent true
+ }
+
+ val key = parts[0].value
+ when (key) {
+ "c" -> {
+ methodMapping = null
+ classMapping = parseClass(builder, indent, namespaces.size, lineNum, parts)
+ }
+ "f" -> {
+ methodMapping = null
+ parseField(classMapping, indent, namespaces.size, lineNum, parts)
+ }
+ "m" -> methodMapping = parseMethod(classMapping, indent, namespaces.size, lineNum, parts)
+ "p" -> parseParam(methodMapping, indent, namespaces.size, lineNum, parts)
+ "v" -> parseLocalVar(methodMapping, indent, namespaces.size, lineNum, parts)
+ }
+
+ return@forLinesIndent true
+ }
+
+ return builder.build()
+ }
+
+ private fun getNamespaces(line: String?): Array? {
+ if (line == null) {
+ return null
+ }
+
+ val header = line.splitMappingLine()
+ if (header.size < 5) {
+ return null
+ }
+
+ if (header[0].value != "tiny" || header[1].value != "2" || header[2].value != "0") {
+ return null
+ }
+
+ val namespaces = arrayOfNulls(header.size - 3)
+ for (index in header.indicesFrom(3)) {
+ namespaces[index - 3] = header[index].value
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return namespaces as Array
+ }
+
+ private fun parseClass(
+ builder: MappingsDefinitionBuilder,
+ indent: Int,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ): ClassMappingBuilder? {
+ if (indent != 0) {
+ builder.error("Unexpected indent of class mapping line", FileCoords(lineNum))
+ return null
+ }
+
+ if (parts.size - 1 > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return null
+ }
+
+ if (parts.size == 1) {
+ builder.warning("Class mapping has no names", FileCoords(lineNum))
+ }
+
+ return builder.clazz(lineNum.highPriority) {
+ for (i in parts.indicesFrom(1)) {
+ with(parts[i].asClass().ns(i - 1))
+ }
+ }
+ }
+
+ private fun parseField(
+ builder: ClassMappingBuilder?,
+ indent: Int,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ) {
+ if (builder == null) {
+ return
+ }
+
+ if (indent != 1) {
+ builder.error("Unexpected indent of field mapping line", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size - 2 > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size < 2) {
+ builder.error("Field mapping line does not have all necessary components", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size == 2) {
+ builder.warning("Field mapping line has no names", FileCoords(lineNum))
+ }
+
+ val fieldType = parts[1].value.asTypeDef()
+ ?: return builder.error("Field descriptor is invalid", FileCoords(lineNum, parts[1]))
+
+ builder.field(lineNum.highPriority) {
+ type = fieldType
+
+ for (i in parts.indicesFrom(2)) {
+ with(parts[i].asField().ns(i - 2))
+ }
+ }
+ }
+
+ private fun parseMethod(
+ builder: ClassMappingBuilder?,
+ indent: Int,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ): MethodMappingBuilder? {
+ if (builder == null) {
+ return null
+ }
+
+ if (indent != 1) {
+ builder.error("Unexpected indent of method mapping line", FileCoords(lineNum))
+ return null
+ }
+
+ if (parts.size - 2 > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return null
+ }
+
+ if (parts.size < 2) {
+ builder.error("Method mapping line does not have all necessary components", FileCoords(lineNum))
+ return null
+ }
+
+ if (parts.size == 2) {
+ builder.warning("Method mapping line has no names", FileCoords(lineNum))
+ }
+
+ val methodDesc = parts[1].value.asMethodDesc()
+ ?: run {
+ builder.error("Method descriptor is invalid", FileCoords(lineNum, parts[1]))
+ return null
+ }
+
+ return builder.method(lineNum.highPriority) {
+ desc = methodDesc
+
+ for (i in parts.indicesFrom(2)) {
+ with(parts[i].asMethod().ns(i - 2))
+ }
+ }
+ }
+
+ private fun parseParam(
+ builder: MethodMappingBuilder?,
+ indent: Int,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ) {
+ if (builder == null) {
+ return
+ }
+
+ if (indent != 2) {
+ builder.error("Unexpected indent of method parameter mapping line", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size - 2 > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size < 2) {
+ builder.error("Method parameter mapping line does not have all necessary components", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size == 2) {
+ builder.warning("Method parameter mapping line has no names", FileCoords(lineNum))
+ }
+
+ val index = parts[1].value.toIntOrNull()
+ ?: return builder.error("Method parameter index is not an integer", FileCoords(lineNum, parts[1]))
+
+ builder.param(index.asParamIndex(), lineNum.highPriority) {
+ for (i in parts.indicesFrom(2)) {
+ with(parts[i].asParam().ns(i - 2))
+ }
+ }
+ }
+
+ private fun parseLocalVar(
+ builder: MethodMappingBuilder?,
+ indent: Int,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ) {
+ if (builder == null) {
+ return
+ }
+
+ if (indent != 2) {
+ builder.error("Unexpected indent of method local var mapping line", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.size < 3) {
+ builder.error("Method local var mapping line does not have all necessary components", FileCoords(lineNum))
+ return
+ }
+
+ val localIndex = parts[1].value.toIntOrNull()?.asLocal()
+ ?: return builder.error("Method local var index is not an integer", FileCoords(lineNum, parts[1]))
+ val localStart = parts[2].value.toIntOrNull()?.asLocal()
+ ?: return builder.error("Method local var start offset is not an integer", FileCoords(lineNum, parts[2]))
+
+ val lvtIndex = if (parts.size >= 4) {
+ // lvtIndex is optional
+ parts[3].value.toIntOrNull()?.asLvtIndex()
+ } else {
+ null
+ } ?: LvtIndex.UNKNOWN
+
+ val nameStartIndex = if (lvtIndex.isKnown) 4 else 3
+
+ if (parts.size - nameStartIndex > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return
+ }
+
+ builder.localVar(localIndex.asLocalVar(startIndex = localStart), lvtIndex, lineNum.highPriority) {
+ for (i in parts.indicesFrom(nameStartIndex)) {
+ with(parts[i].asLocal().ns(i - nameStartIndex))
+ }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/TinyV2MappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/TinyV2MappingsFormatType.kt
new file mode 100644
index 000000000..49c93abb8
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/TinyV2MappingsFormatType.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2
+
+import io.mcdev.obfex.ObfIcons
+import io.mcdev.obfex.formats.MappingsFormatType
+import javax.swing.Icon
+
+object TinyV2MappingsFormatType : MappingsFormatType("tiny-v2") {
+
+ override val icon: Icon = ObfIcons.TINY_V2_ICON
+ override val name: String = "Tiny v2"
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2File.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2File.kt
new file mode 100644
index 000000000..fde59952f
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2File.kt
@@ -0,0 +1,30 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang
+
+import com.intellij.extapi.psi.PsiFileBase
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.psi.FileViewProvider
+
+class TinyV2File(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, TinyV2Language) {
+ override fun getFileType(): FileType = TinyV2FileType
+ override fun toString(): String = TinyV2FileType.description
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2FileType.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2FileType.kt
new file mode 100644
index 000000000..ad78d1aeb
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2FileType.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import io.mcdev.obfex.ObfIcons
+import javax.swing.Icon
+
+object TinyV2FileType : LanguageFileType(TinyV2Language) {
+
+ override fun getName(): String = "Tiny v2"
+ override fun getDescription(): String = "Tiny v2 obfuscation mapping file"
+ override fun getDefaultExtension(): String = "tiny"
+ override fun getIcon(): Icon = ObfIcons.TINY_V2_ICON
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2Language.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2Language.kt
new file mode 100644
index 000000000..b13323c81
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2Language.kt
@@ -0,0 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang
+
+import com.intellij.lang.Language
+
+object TinyV2Language : Language("TinyV2")
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2ParserDefinition.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2ParserDefinition.kt
new file mode 100644
index 000000000..9793d1107
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/TinyV2ParserDefinition.kt
@@ -0,0 +1,50 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.Language
+import com.intellij.lang.ParserDefinition
+import com.intellij.lang.PsiParser
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.project.Project
+import com.intellij.psi.FileViewProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.tree.IFileElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.tinyv2.gen.TinyV2Parser
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2Types
+import io.mcdev.obfex.formats.tinyv2.lang.psi.TinyV2LexerAdapter
+
+class TinyV2ParserDefinition : ParserDefinition {
+
+ override fun createLexer(project: Project?): Lexer = TinyV2LexerAdapter()
+ override fun getCommentTokens(): TokenSet = COMMENTS
+ override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
+ override fun createParser(project: Project?): PsiParser = TinyV2Parser()
+ override fun getFileNodeType(): IFileElementType = FILE
+ override fun createFile(viewProvider: FileViewProvider): PsiFile = TinyV2File(viewProvider)
+ override fun createElement(node: ASTNode?): PsiElement = TinyV2Types.Factory.createElement(node)
+}
+
+private val COMMENTS = TokenSet.create(TinyV2Types.COMMENT)
+private val FILE = IFileElementType(Language.findInstance(TinyV2Language::class.java))
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2Annotator.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2Annotator.kt
new file mode 100644
index 000000000..47a79629e
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2Annotator.kt
@@ -0,0 +1,66 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang.colors
+
+import com.intellij.lang.annotation.AnnotationHolder
+import com.intellij.lang.annotation.Annotator
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.childrenOfType
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2ClassMapping
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2FieldMapping
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2LocalVarIndex
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2LocalVarMapping
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2LocalVarStart
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2LvtIndex
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2MappingPart
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2MethodMapping
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2ParamIndex
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2ParamMapping
+import io.mcdev.obfex.formats.util.registerHighlight
+
+class TinyV2Annotator : Annotator {
+
+ override fun annotate(element: PsiElement, holder: AnnotationHolder) {
+ when (element) {
+ is TinyV2ParamIndex -> {
+ registerHighlight(element, TinyV2SyntaxHighlighter.PARAM_INDEX, holder)
+ return
+ }
+ is TinyV2LocalVarIndex, is TinyV2LocalVarStart, is TinyV2LvtIndex -> {
+ registerHighlight(element, TinyV2SyntaxHighlighter.LOCAL_VAR_INDEX, holder)
+ return
+ }
+ }
+
+ val key = when (element) {
+ is TinyV2ClassMapping -> TinyV2SyntaxHighlighter.CLASS_NAME
+ is TinyV2MethodMapping -> TinyV2SyntaxHighlighter.METHOD
+ is TinyV2FieldMapping -> TinyV2SyntaxHighlighter.FIELD
+ is TinyV2ParamMapping -> TinyV2SyntaxHighlighter.PARAM
+ is TinyV2LocalVarMapping -> TinyV2SyntaxHighlighter.LOCAL_VAR
+ else -> return
+ }
+
+ for (part in element.childrenOfType()) {
+ registerHighlight(part, key, holder)
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2RainbowVisitor.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2RainbowVisitor.kt
new file mode 100644
index 000000000..0739002fe
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2RainbowVisitor.kt
@@ -0,0 +1,75 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang.colors
+
+import com.intellij.codeInsight.daemon.impl.HighlightVisitor
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.findParentOfType
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2ClassMapping
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2FieldMapping
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2FileElement
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2LocalVarMapping
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2MappingPart
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2MethodMapping
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2MethodSignature
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2Namespace
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2ParamMapping
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2TypeDesc
+import io.mcdev.obfex.formats.tinyv2.lang.TinyV2Language
+import io.mcdev.obfex.formats.util.NamespaceRainbowVisitor
+
+class TinyV2RainbowVisitor : NamespaceRainbowVisitor(TinyV2Language) {
+
+ override fun visit(element: PsiElement) {
+ if (element is TinyV2Namespace) {
+ highlightNamespaceDecl(element)
+ return
+ }
+
+ val file = element.findParentOfType() ?: return
+ val namespaces = file.header.namespaceList
+
+ if (element is TinyV2MappingPart) {
+ handleMappingPart(element, namespaces)
+ } else if (element is TinyV2TypeDesc && element.parent is TinyV2FieldMapping) {
+ highlightTypeSignature(element)
+ } else if (element is TinyV2MethodSignature) {
+ highlightTypeSignature(element)
+ }
+ }
+
+ private fun handleMappingPart(element: TinyV2MappingPart, namespaces: List) {
+ val index = indexOf(element.parent)
+ if (namespaces.size <= index) {
+ return
+ }
+
+ when (element.parent.parent) {
+ is TinyV2ClassMapping -> highlightElement(element, index, HighlightType.CLASS)
+ is TinyV2FieldMapping -> highlightElement(element, index, HighlightType.FIELD)
+ is TinyV2MethodMapping -> highlightElement(element, index, HighlightType.METHOD)
+ is TinyV2ParamMapping -> highlightElement(element, index, HighlightType.PARAM)
+ is TinyV2LocalVarMapping -> highlightElement(element, index, HighlightType.LOCAL_VAR)
+ }
+ }
+
+ override fun clone(): HighlightVisitor = TinyV2RainbowVisitor()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2SyntaxHighlighter.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2SyntaxHighlighter.kt
new file mode 100644
index 000000000..97aa4e1ea
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2SyntaxHighlighter.kt
@@ -0,0 +1,127 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang.colors
+
+import com.intellij.ide.highlighter.custom.CustomHighlighterColors
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tinyv2.gen.psi.TinyV2Types
+import io.mcdev.obfex.formats.tinyv2.lang.psi.TinyV2LexerAdapter
+
+class TinyV2SyntaxHighlighter : SyntaxHighlighterBase() {
+
+ override fun getHighlightingLexer(): Lexer = TinyV2LexerAdapter()
+
+ override fun getTokenHighlights(tokenType: IElementType?): Array {
+ return when (tokenType) {
+ TinyV2Types.TINY_KEY, TinyV2Types.VERSION_NUM -> KEYWORD_KEYS
+
+ TinyV2Types.CLASS_KEY, TinyV2Types.FIELD_KEY, TinyV2Types.METHOD_KEY, TinyV2Types.PARAM_KEY,
+ TinyV2Types.VAR_KEY -> KEYWORD_KEYS
+
+ TinyV2Types.COMMENT_KEY -> KEYWORD_KEYS
+ TinyV2Types.DOC_TEXT -> DOC_COMMENT_KEYS
+ TinyV2Types.NAMESPACE_KEY -> NAMESPACE_KEYS
+ TinyV2Types.PROPERTY_KEY -> PROPERTY_KEY_KEYS
+ TinyV2Types.PROPERTY_VALUE -> PROPERTY_VALUE_KEYS
+ TinyV2Types.CLASS_TYPE -> CLASS_TYPE_KEYS
+ TinyV2Types.PRIMITIVE -> PRIMITIVE_KEYS
+ TinyV2Types.COMMENT -> COMMENT_KEYS
+ else -> TextAttributesKey.EMPTY_ARRAY
+ }
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ companion object {
+ val KEYWORD = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_KEYWORD",
+ DefaultLanguageHighlighterColors.KEYWORD
+ )
+ val NAMESPACE = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_NAMESPACE",
+ DefaultLanguageHighlighterColors.METADATA
+ )
+ val PROPERTY_KEY = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_PROPERTY_KEY",
+ CustomHighlighterColors.CUSTOM_KEYWORD2_ATTRIBUTES
+ )
+ val PROPERTY_VALUE = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_PROPERTY_VALUE",
+ CustomHighlighterColors.CUSTOM_KEYWORD3_ATTRIBUTES
+ )
+ val DOC_COMMENT = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_DOC_COMMENT",
+ DefaultLanguageHighlighterColors.DOC_COMMENT
+ )
+ val CLASS_NAME = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_CLASS_NAME",
+ DefaultLanguageHighlighterColors.STRING
+ )
+ val METHOD = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_METHOD",
+ DefaultLanguageHighlighterColors.INSTANCE_METHOD
+ )
+ val FIELD = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_FIELD",
+ DefaultLanguageHighlighterColors.INSTANCE_FIELD
+ )
+ val PARAM_INDEX = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_PARAM_INDEX",
+ DefaultLanguageHighlighterColors.INLAY_DEFAULT
+ )
+ val PARAM = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_PARAM",
+ DefaultLanguageHighlighterColors.PARAMETER
+ )
+ val LOCAL_VAR_INDEX = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_LOCAL_VAR_INDEX",
+ DefaultLanguageHighlighterColors.INLAY_DEFAULT
+ )
+ val LOCAL_VAR = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_LOCAL_VAR",
+ DefaultLanguageHighlighterColors.PARAMETER
+ )
+ val PRIMITIVE = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_PRIMITIVE",
+ DefaultLanguageHighlighterColors.NUMBER
+ )
+ val CLASS_TYPE = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_CLASS_TYPE",
+ DefaultLanguageHighlighterColors.CLASS_REFERENCE
+ )
+ val COMMENT = TextAttributesKey.createTextAttributesKey(
+ "TINYV2_COMMENT",
+ DefaultLanguageHighlighterColors.LINE_COMMENT
+ )
+
+ private val KEYWORD_KEYS = arrayOf(KEYWORD)
+ private val DOC_COMMENT_KEYS = arrayOf(DOC_COMMENT)
+ private val NAMESPACE_KEYS = arrayOf(NAMESPACE)
+ private val PROPERTY_KEY_KEYS = arrayOf(PROPERTY_KEY)
+ private val PROPERTY_VALUE_KEYS = arrayOf(PROPERTY_VALUE)
+ private val PRIMITIVE_KEYS = arrayOf(PRIMITIVE)
+ private val CLASS_TYPE_KEYS = arrayOf(CLASS_TYPE)
+ private val COMMENT_KEYS = arrayOf(COMMENT)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2SyntaxHighlighterFactory.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2SyntaxHighlighterFactory.kt
new file mode 100644
index 000000000..0b394db55
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/colors/TinyV2SyntaxHighlighterFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang.colors
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighter
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+
+class TinyV2SyntaxHighlighterFactory : SyntaxHighlighterFactory() {
+ override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?): SyntaxHighlighter =
+ TinyV2SyntaxHighlighter()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/psi/TinyV2ElementType.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/psi/TinyV2ElementType.kt
new file mode 100644
index 000000000..948aab3f6
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/psi/TinyV2ElementType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tinyv2.lang.TinyV2Language
+import org.jetbrains.annotations.NonNls
+
+class TinyV2ElementType(@NonNls debugName: String) : IElementType(debugName, TinyV2Language)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/psi/TinyV2LexerAdapter.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/psi/TinyV2LexerAdapter.kt
new file mode 100644
index 000000000..ebad18636
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/psi/TinyV2LexerAdapter.kt
@@ -0,0 +1,26 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang.psi
+
+import com.intellij.lexer.FlexAdapter
+import io.mcdev.obfex.formats.tinyv2.gen.TinyV2Lexer
+
+class TinyV2LexerAdapter : FlexAdapter(TinyV2Lexer())
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/psi/TinyV2TokenType.kt b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/psi/TinyV2TokenType.kt
new file mode 100644
index 000000000..94a30ab94
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tinyv2/lang/psi/TinyV2TokenType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tinyv2.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tinyv2.lang.TinyV2Language
+import org.jetbrains.annotations.NonNls
+
+class TinyV2TokenType(@NonNls debugName: String) : IElementType(debugName, TinyV2Language)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/TSrgLanguageCodeStyleSettingsProvider.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/TSrgLanguageCodeStyleSettingsProvider.kt
new file mode 100644
index 000000000..353d1df0f
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/TSrgLanguageCodeStyleSettingsProvider.kt
@@ -0,0 +1,40 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg
+
+import io.mcdev.obfex.formats.tsrg.lang.TSrgLanguage
+import io.mcdev.obfex.formats.util.TabCodeStyleSettingsProvider
+import org.intellij.lang.annotations.Language
+
+class TSrgLanguageCodeStyleSettingsProvider : TabCodeStyleSettingsProvider() {
+ override fun getLanguage() = TSrgLanguage
+ override fun getCodeSample(settingsType: SettingsType) = SAMPLE
+}
+
+@Language("TSRG")
+private const val SAMPLE = """
+class_1 class1Ns0Rename
+ field_1 field1Ns0Rename
+ method_1 ()I method1Ns0Rename
+class_1${'$'}class_2 class1Ns0Rename${'$'}class2Ns0Rename
+ field_2 field2Ns0Rename
+class_3 class3Ns0Rename
+"""
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/TSrgMappingsFormatParser.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/TSrgMappingsFormatParser.kt
new file mode 100644
index 000000000..eca112c29
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/TSrgMappingsFormatParser.kt
@@ -0,0 +1,120 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg
+
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.MappingPart
+import io.mcdev.obfex.forLinesIndent
+import io.mcdev.obfex.mappings.ClassMappingBuilder
+import io.mcdev.obfex.mappings.FileCoords
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsDefinitionBuilder
+import io.mcdev.obfex.mappings.UnnamedMappingsFormatParser
+import io.mcdev.obfex.mappings.clazz
+import io.mcdev.obfex.mappings.field
+import io.mcdev.obfex.mappings.method
+import io.mcdev.obfex.ref.asClass
+import io.mcdev.obfex.ref.asField
+import io.mcdev.obfex.ref.asMethod
+import io.mcdev.obfex.ref.asMethodDesc
+import io.mcdev.obfex.ref.highPriority
+
+@Suppress("DuplicatedCode")
+class TSrgMappingsFormatParser : UnnamedMappingsFormatParser {
+
+ override val expectedFileExtensions: Array = arrayOf("tsrg")
+
+ override fun parse(file: VirtualFile): MappingsDefinition {
+ val builder = MappingsDefinitionBuilder(TSrgMappingsFormatType, file)
+
+ var classMapping: ClassMappingBuilder? = null
+ file.forLinesIndent { indent, lineNum, parts, _ ->
+ if (indent > 0) {
+ val c = classMapping
+ if (c == null) {
+ builder.error("Member mapping line with no associated class", FileCoords(lineNum))
+ } else {
+ parseMember(c, lineNum, parts)
+ }
+ } else {
+ classMapping = parseClass(builder, lineNum, parts)
+ }
+
+ return@forLinesIndent true
+ }
+
+ return builder.build()
+ }
+
+ private fun parseClass(
+ builder: MappingsDefinitionBuilder,
+ lineNum: Int,
+ parts: Array,
+ ): ClassMappingBuilder? {
+ if (parts.size != 2) {
+ builder.error("Invalid class line", FileCoords(lineNum))
+ return null
+ }
+
+ val (leftClass, rightClass) = parts
+
+ return builder.clazz(lineNum.highPriority) {
+ with(leftClass.asClass().from)
+ with(rightClass.asClass().to)
+ }
+ }
+
+ private fun parseMember(builder: ClassMappingBuilder, lineNum: Int, parts: Array) {
+ when (parts.size) {
+ 2 -> parseField(builder, lineNum, parts[0], parts[1])
+ 3 -> parseMethod(builder, lineNum, parts[0], parts[1], parts[2])
+ else -> builder.error("Invalid member line", FileCoords(lineNum))
+ }
+ }
+
+ private fun parseField(builder: ClassMappingBuilder, lineNum: Int, leftName: MappingPart, rightName: MappingPart) {
+ builder.field(lineNum.highPriority) {
+ with(leftName.asField().from)
+ with(rightName.asField().to)
+ }
+ }
+
+ private fun parseMethod(
+ builder: ClassMappingBuilder,
+ lineNum: Int,
+ leftName: MappingPart,
+ leftDesc: MappingPart,
+ rightName: MappingPart,
+ ) {
+ val methodDesc = leftDesc.value.asMethodDesc()
+ if (methodDesc == null) {
+ builder.error("Invalid method descriptor", FileCoords(lineNum, leftDesc))
+ return
+ }
+
+ builder.method(lineNum.highPriority) {
+ desc = methodDesc
+
+ with(leftName.asMethod().from)
+ with(rightName.asMethod().to)
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/TSrgMappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/TSrgMappingsFormatType.kt
new file mode 100644
index 000000000..91e44374e
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/TSrgMappingsFormatType.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg
+
+import io.mcdev.obfex.ObfIcons
+import io.mcdev.obfex.formats.MappingsFormatType
+import javax.swing.Icon
+
+object TSrgMappingsFormatType : MappingsFormatType("TSRG") {
+
+ override val icon: Icon = ObfIcons.TSRG_ICON
+ override val name: String = "TSRG"
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgFile.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgFile.kt
new file mode 100644
index 000000000..715d56f6d
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgFile.kt
@@ -0,0 +1,30 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang
+
+import com.intellij.extapi.psi.PsiFileBase
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.psi.FileViewProvider
+
+class TSrgFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, TSrgLanguage) {
+ override fun getFileType(): FileType = TSrgFileType
+ override fun toString(): String = TSrgFileType.description
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgFileType.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgFileType.kt
new file mode 100644
index 000000000..1c98cb5fb
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgFileType.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import io.mcdev.obfex.ObfIcons
+import javax.swing.Icon
+
+object TSrgFileType : LanguageFileType(TSrgLanguage) {
+
+ override fun getName(): String = "TSRG"
+ override fun getDescription(): String = "TSRG obfuscation mapping file"
+ override fun getDefaultExtension(): String = "tsrg"
+ override fun getIcon(): Icon = ObfIcons.TSRG_ICON
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgFileTypeDetector.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgFileTypeDetector.kt
new file mode 100644
index 000000000..833f1bd13
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgFileTypeDetector.kt
@@ -0,0 +1,45 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang
+
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.openapi.fileTypes.FileTypeRegistry
+import com.intellij.openapi.util.io.ByteSequence
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.formats.tsrg2.lang.TSrg2FileType
+
+class TSrgFileTypeDetector : FileTypeRegistry.FileTypeDetector {
+
+ override fun detect(file: VirtualFile, firstBytes: ByteSequence, firstCharsIfText: CharSequence?): FileType? {
+ if (firstCharsIfText == null) {
+ return null
+ }
+ if (!file.name.endsWith(".tsrg")) {
+ return null
+ }
+
+ return if (firstCharsIfText.startsWith("tsrg2")) {
+ TSrg2FileType
+ } else {
+ TSrgFileType
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgLanguage.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgLanguage.kt
new file mode 100644
index 000000000..cdcc43184
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgLanguage.kt
@@ -0,0 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang
+
+import com.intellij.lang.Language
+
+object TSrgLanguage : Language("TSRG")
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgParserDefinition.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgParserDefinition.kt
new file mode 100644
index 000000000..ed27b2239
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/TSrgParserDefinition.kt
@@ -0,0 +1,50 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.Language
+import com.intellij.lang.ParserDefinition
+import com.intellij.lang.PsiParser
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.project.Project
+import com.intellij.psi.FileViewProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.tree.IFileElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.tsrg.gen.TSrgParser
+import io.mcdev.obfex.formats.tsrg.gen.psi.TSrgTypes
+import io.mcdev.obfex.formats.tsrg.lang.psi.TSrgLexerAdapter
+
+class TSrgParserDefinition : ParserDefinition {
+
+ override fun createLexer(project: Project): Lexer = TSrgLexerAdapter()
+ override fun getCommentTokens(): TokenSet = COMMENTS
+ override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
+ override fun createParser(project: Project): PsiParser = TSrgParser()
+ override fun getFileNodeType(): IFileElementType = FILE
+ override fun createFile(viewProvider: FileViewProvider): PsiFile = TSrgFile(viewProvider)
+ override fun createElement(node: ASTNode): PsiElement = TSrgTypes.Factory.createElement(node)
+}
+
+private val COMMENTS = TokenSet.create(TSrgTypes.COMMENT)
+private val FILE = IFileElementType(Language.findInstance(TSrgLanguage::class.java))
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/colors/TSrgAnnotator.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/colors/TSrgAnnotator.kt
new file mode 100644
index 000000000..efca82b58
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/colors/TSrgAnnotator.kt
@@ -0,0 +1,50 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang.colors
+
+import com.intellij.lang.annotation.AnnotationHolder
+import com.intellij.lang.annotation.Annotator
+import com.intellij.psi.PsiElement
+import io.mcdev.obfex.formats.tsrg.gen.psi.TSrgDeobfClassMappingPart
+import io.mcdev.obfex.formats.tsrg.gen.psi.TSrgDeobfFieldMappingPart
+import io.mcdev.obfex.formats.tsrg.gen.psi.TSrgDeobfMethodMappingPart
+import io.mcdev.obfex.formats.tsrg.gen.psi.TSrgObfClassMappingPart
+import io.mcdev.obfex.formats.tsrg.gen.psi.TSrgObfFieldMappingPart
+import io.mcdev.obfex.formats.tsrg.gen.psi.TSrgObfMethodMappingPart
+import io.mcdev.obfex.formats.util.registerHighlight
+
+class TSrgAnnotator : Annotator {
+ override fun annotate(element: PsiElement, holder: AnnotationHolder) {
+ val key = when (element) {
+ is TSrgObfClassMappingPart, is TSrgDeobfClassMappingPart -> TSrgSyntaxHighlighter.CLASS_NAME
+ is TSrgObfMethodMappingPart, is TSrgDeobfMethodMappingPart -> TSrgSyntaxHighlighter.METHOD
+ is TSrgObfFieldMappingPart, is TSrgDeobfFieldMappingPart -> TSrgSyntaxHighlighter.FIELD
+ else -> return
+ }
+
+ val highlight = when (element) {
+ is TSrgDeobfClassMappingPart, is TSrgDeobfMethodMappingPart, is TSrgDeobfFieldMappingPart -> true
+ else -> false
+ }
+
+ registerHighlight(element, key, holder, highlight)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/colors/TSrgSyntaxHighlighter.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/colors/TSrgSyntaxHighlighter.kt
new file mode 100644
index 000000000..ffd0a711a
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/colors/TSrgSyntaxHighlighter.kt
@@ -0,0 +1,71 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang.colors
+
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tsrg.gen.psi.TSrgTypes
+import io.mcdev.obfex.formats.tsrg.lang.psi.TSrgLexerAdapter
+
+class TSrgSyntaxHighlighter : SyntaxHighlighterBase() {
+ override fun getHighlightingLexer() = TSrgLexerAdapter()
+
+ override fun getTokenHighlights(tokenType: IElementType?): Array =
+ when (tokenType) {
+ TSrgTypes.CLASS_TYPE -> CLASS_TYPE_KEYS
+ TSrgTypes.PRIMITIVE -> PRIMITIVE_KEYS
+ TSrgTypes.COMMENT -> COMMENT_KEYS
+ else -> TextAttributesKey.EMPTY_ARRAY
+ }
+
+ companion object {
+ val CLASS_NAME = TextAttributesKey.createTextAttributesKey(
+ "TSRG_CLASS_NAME",
+ DefaultLanguageHighlighterColors.STRING
+ )
+ val METHOD = TextAttributesKey.createTextAttributesKey(
+ "TSRG_METHOD",
+ DefaultLanguageHighlighterColors.INSTANCE_METHOD
+ )
+ val FIELD = TextAttributesKey.createTextAttributesKey(
+ "TSRG_FIELD",
+ DefaultLanguageHighlighterColors.INSTANCE_FIELD
+ )
+ val PRIMITIVE = TextAttributesKey.createTextAttributesKey(
+ "TSRG_PRIMITIVE",
+ DefaultLanguageHighlighterColors.NUMBER
+ )
+ val CLASS_TYPE = TextAttributesKey.createTextAttributesKey(
+ "TSRG_CLASS_TYPE",
+ DefaultLanguageHighlighterColors.CLASS_REFERENCE
+ )
+ val COMMENT = TextAttributesKey.createTextAttributesKey(
+ "TSRG_COMMENT",
+ DefaultLanguageHighlighterColors.LINE_COMMENT
+ )
+
+ private val PRIMITIVE_KEYS = arrayOf(PRIMITIVE)
+ private val CLASS_TYPE_KEYS = arrayOf(CLASS_TYPE)
+ private val COMMENT_KEYS = arrayOf(COMMENT)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/colors/TSrgSyntaxHighlighterFactory.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/colors/TSrgSyntaxHighlighterFactory.kt
new file mode 100644
index 000000000..ca4be21ac
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/colors/TSrgSyntaxHighlighterFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang.colors
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighter
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+
+class TSrgSyntaxHighlighterFactory : SyntaxHighlighterFactory() {
+ override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?): SyntaxHighlighter =
+ TSrgSyntaxHighlighter()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/psi/TSrgElementType.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/psi/TSrgElementType.kt
new file mode 100644
index 000000000..d30f81466
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/psi/TSrgElementType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tsrg.lang.TSrgLanguage
+import org.jetbrains.annotations.NonNls
+
+class TSrgElementType(@NonNls debugName: String) : IElementType(debugName, TSrgLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/psi/TSrgLexerAdapter.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/psi/TSrgLexerAdapter.kt
new file mode 100644
index 000000000..9cbff8905
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/psi/TSrgLexerAdapter.kt
@@ -0,0 +1,26 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang.psi
+
+import com.intellij.lexer.FlexAdapter
+import io.mcdev.obfex.formats.tsrg.gen.TSrgLexer
+
+class TSrgLexerAdapter : FlexAdapter(TSrgLexer())
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/psi/TSrgTokenType.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/psi/TSrgTokenType.kt
new file mode 100644
index 000000000..3a88a017a
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg/lang/psi/TSrgTokenType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tsrg.lang.TSrgLanguage
+import org.jetbrains.annotations.NonNls
+
+class TSrgTokenType(@NonNls debugName: String) : IElementType(debugName, TSrgLanguage)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/TSrg2LanguageCodeStyleSettingsProvider.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/TSrg2LanguageCodeStyleSettingsProvider.kt
new file mode 100644
index 000000000..6ef69e11a
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/TSrg2LanguageCodeStyleSettingsProvider.kt
@@ -0,0 +1,44 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2
+
+import io.mcdev.obfex.formats.tsrg2.lang.TSrg2Language
+import io.mcdev.obfex.formats.util.TabCodeStyleSettingsProvider
+import org.intellij.lang.annotations.Language
+
+class TSrg2LanguageCodeStyleSettingsProvider : TabCodeStyleSettingsProvider() {
+ override fun getLanguage() = TSrg2Language
+ override fun getCodeSample(settingsType: SettingsType): String? {
+ TODO("Not yet implemented")
+ }
+}
+
+@Language("TSRGv2")
+private const val SAMPLE = """
+tsrg2 source target target2
+class_1 class1Ns0Rename class1Ns1Rename
+ field_1 I field1Ns0Rename field1Ns1Rename
+ method_1 ()I method1Ns0Rename method1Ns1Rename
+ 1 param_1 param1Ns0Rename param1Ns1Rename
+class_1${'$'}class_2 class1Ns0Rename$${'$'}class2Ns0Rename class1Ns1Rename$${'$'}class2Ns1Rename
+ field_2 I field2Ns0Rename field2Ns1Rename
+class_3 class3Ns0Rename class3Ns1Rename
+"""
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/TSrg2MappingsFormatParser.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/TSrg2MappingsFormatParser.kt
new file mode 100644
index 000000000..9e38da4cd
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/TSrg2MappingsFormatParser.kt
@@ -0,0 +1,231 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2
+
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.MappingPart
+import io.mcdev.obfex.Tristate
+import io.mcdev.obfex.forLinesIndent
+import io.mcdev.obfex.formats.util.indicesFrom
+import io.mcdev.obfex.mappings.ClassMappingBuilder
+import io.mcdev.obfex.mappings.FileCoords
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsDefinitionBuilder
+import io.mcdev.obfex.mappings.MappingsFormatParser
+import io.mcdev.obfex.mappings.MethodMappingBuilder
+import io.mcdev.obfex.mappings.clazz
+import io.mcdev.obfex.mappings.field
+import io.mcdev.obfex.mappings.method
+import io.mcdev.obfex.mappings.ns
+import io.mcdev.obfex.mappings.param
+import io.mcdev.obfex.ref.MethodDescriptor
+import io.mcdev.obfex.ref.asClass
+import io.mcdev.obfex.ref.asField
+import io.mcdev.obfex.ref.asMethod
+import io.mcdev.obfex.ref.asMethodDesc
+import io.mcdev.obfex.ref.asParam
+import io.mcdev.obfex.ref.asParamIndex
+import io.mcdev.obfex.ref.highPriority
+import io.mcdev.obfex.splitMappingLine
+
+class TSrg2MappingsFormatParser : MappingsFormatParser {
+
+ override val expectedFileExtensions: Array = arrayOf("tsrg")
+
+ override fun isSupportedFile(file: VirtualFile): Boolean {
+ val firstLine = file.inputStream.bufferedReader(file.charset).use { reader ->
+ reader.readLine()
+ }
+ return getNamespaces(firstLine) != null
+ }
+
+ override fun parse(file: VirtualFile): MappingsDefinition? {
+ val firstLine = file.inputStream.bufferedReader(file.charset).use { reader ->
+ reader.readLine()
+ }
+ val namespaces = getNamespaces(firstLine) ?: return null
+ val namespaceCount = namespaces.size
+
+ val builder = MappingsDefinitionBuilder(TSrg2MappingsFormatType, file, *namespaces)
+
+ var classMapping: ClassMappingBuilder? = null
+ var methodMapping: MethodMappingBuilder? = null
+ file.forLinesIndent(skipLines = 1, preserveBlank = true) { indent, lineNum, parts, _ ->
+ when (indent) {
+ 0 -> {
+ methodMapping = null
+ classMapping = parseClass(builder, namespaceCount, lineNum, parts)
+ }
+ 1 -> {
+ methodMapping = parseClassMember(classMapping, namespaceCount, lineNum, parts)
+ }
+ 2 -> parseMethodMember(methodMapping, namespaceCount, lineNum, parts)
+ }
+
+ return@forLinesIndent true
+ }
+
+ return builder.build()
+ }
+
+ private fun parseClass(
+ builder: MappingsDefinitionBuilder,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ): ClassMappingBuilder? {
+ if (parts.size > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return null
+ }
+
+ return builder.clazz(lineNum.highPriority) {
+ for (i in parts.indices) {
+ with(parts[i].asClass().ns(i))
+ }
+ }
+ }
+
+ private fun parseClassMember(
+ builder: ClassMappingBuilder?,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ): MethodMappingBuilder? {
+ val desc = parts.getOrNull(1)?.value?.asMethodDesc()
+ if (desc != null) {
+ return parseMethod(builder, namespaceCount, lineNum, parts, desc)
+ } else {
+ parseField(builder, namespaceCount, lineNum, parts)
+ return null
+ }
+ }
+
+ private fun parseField(
+ builder: ClassMappingBuilder?,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ) {
+ if (builder == null) {
+ return
+ }
+
+ if (parts.size > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return
+ }
+
+ builder.field(lineNum.highPriority) {
+ for (i in parts.indices) {
+ with(parts[i].asField().ns(i))
+ }
+ }
+ }
+
+ private fun parseMethod(
+ builder: ClassMappingBuilder?,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array,
+ methodDesc: MethodDescriptor,
+ ): MethodMappingBuilder? {
+ if (builder == null) {
+ return null
+ }
+
+ if (parts.size - 1 > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return null
+ }
+
+ return builder.method(lineNum.highPriority) {
+ desc = methodDesc
+ // We default static to false, it will be set to true later if we see the static keyword
+ meta = meta.copy(isStatic = Tristate.FALSE)
+
+ with(parts[0].asMethod().ns(0))
+
+ for (i in parts.indicesFrom(2)) {
+ with(parts[i].asMethod().ns(i - 1))
+ }
+ }
+ }
+
+ private fun parseMethodMember(
+ builder: MethodMappingBuilder?,
+ namespaceCount: Int,
+ lineNum: Int,
+ parts: Array
+ ) {
+ if (builder == null) {
+ return
+ }
+
+ if (parts.size == 1 && parts[0].value == "static") {
+ builder.meta = builder.meta.copy(isStatic = Tristate.TRUE)
+ return
+ }
+
+ if (parts.size - 1 > namespaceCount) {
+ builder.error("Unexpected number of names present", FileCoords(lineNum))
+ return
+ }
+
+ if (parts.isEmpty()) {
+ return
+ }
+ val index = parts[0].value.toIntOrNull()
+ if (index == null) {
+ builder.error("Method parameter index is not an integer", FileCoords(lineNum, parts[0]))
+ return
+ }
+
+ builder.param(index.asParamIndex(), lineNum.highPriority) {
+ for (i in parts.indicesFrom(1)) {
+ with(parts[i].asParam().ns(i - 1))
+ }
+ }
+ }
+
+ private fun getNamespaces(line: String?): Array? {
+ if (line == null) {
+ return null
+ }
+
+ val header = line.splitMappingLine()
+ if (header.size < 3) {
+ return null
+ }
+
+ if (header[0].value != "tsrg2") {
+ return null
+ }
+
+ val namespaces = arrayOfNulls(header.size - 1)
+ for (index in header.indicesFrom(1)) {
+ namespaces[index - 1] = header[index].value
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return namespaces as Array
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/TSrg2MappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/TSrg2MappingsFormatType.kt
new file mode 100644
index 000000000..3b86ed5db
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/TSrg2MappingsFormatType.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2
+
+import io.mcdev.obfex.ObfIcons
+import io.mcdev.obfex.formats.MappingsFormatType
+import javax.swing.Icon
+
+object TSrg2MappingsFormatType : MappingsFormatType("TSRG2") {
+
+ override val icon: Icon = ObfIcons.TSRG2_ICON
+ override val name: String = "TSRG v2"
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2File.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2File.kt
new file mode 100644
index 000000000..fb4cf7d3c
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2File.kt
@@ -0,0 +1,30 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang
+
+import com.intellij.extapi.psi.PsiFileBase
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.psi.FileViewProvider
+
+class TSrg2File(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, TSrg2Language) {
+ override fun getFileType(): FileType = TSrg2FileType
+ override fun toString(): String = TSrg2FileType.description
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2FileType.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2FileType.kt
new file mode 100644
index 000000000..116dd6920
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2FileType.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import io.mcdev.obfex.ObfIcons
+import javax.swing.Icon
+
+object TSrg2FileType : LanguageFileType(TSrg2Language) {
+
+ override fun getName(): String = "TSRGv2"
+ override fun getDescription(): String = "TSRGv2 obfuscation mapping file"
+ override fun getDefaultExtension(): String = "tsrg"
+ override fun getIcon(): Icon = ObfIcons.TSRG2_ICON
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2Language.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2Language.kt
new file mode 100644
index 000000000..2f97555d5
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2Language.kt
@@ -0,0 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang
+
+import com.intellij.lang.Language
+
+object TSrg2Language : Language("TSRGv2")
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2LayoutLexer.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2LayoutLexer.kt
new file mode 100644
index 000000000..f053f17b9
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2LayoutLexer.kt
@@ -0,0 +1,35 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang
+
+import com.intellij.psi.tree.IElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2Types
+import io.mcdev.obfex.formats.tsrg2.lang.psi.TSrg2LexerAdapter
+import io.mcdev.obfex.formats.util.sigws.SignificantWhitespaceLexer
+
+class TSrg2LayoutLexer : SignificantWhitespaceLexer(TSrg2LexerAdapter()) {
+
+ override val newlineTokens: TokenSet = TokenSet.create(TSrg2Types.CRLF)
+ override val tabTokens: TokenSet = TokenSet.create(TSrg2Types.TAB)
+ override val virtualOpenToken: IElementType = TSrg2Types.VIRTUAL_OPEN
+ override val virtualCloseToken: IElementType = TSrg2Types.VIRTUAL_CLOSE
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2ParserDefinition.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2ParserDefinition.kt
new file mode 100644
index 000000000..2a8814535
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/TSrg2ParserDefinition.kt
@@ -0,0 +1,51 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.Language
+import com.intellij.lang.ParserDefinition
+import com.intellij.lang.PsiParser
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.project.Project
+import com.intellij.psi.FileViewProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.tree.IFileElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.tsrg2.gen.TSrg2Parser
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2Types
+
+class TSrg2ParserDefinition : ParserDefinition {
+
+ override fun createLexer(project: Project): Lexer = TSrg2LayoutLexer()
+ override fun getCommentTokens(): TokenSet = COMMENTS
+ override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
+ override fun createParser(project: Project): PsiParser = TSrg2Parser()
+ override fun getFileNodeType(): IFileElementType = FILE
+ override fun createFile(viewProvider: FileViewProvider): PsiFile = TSrg2File(viewProvider)
+ override fun createElement(node: ASTNode): PsiElement = TSrg2Types.Factory.createElement(node)
+ override fun getWhitespaceTokens(): TokenSet = WHITESPACE
+}
+
+private val WHITESPACE = TokenSet.orSet(TokenSet.WHITE_SPACE, TokenSet.create(TSrg2Types.TAB))
+private val COMMENTS = TokenSet.create(TSrg2Types.COMMENT)
+private val FILE = IFileElementType(Language.findInstance(TSrg2Language::class.java))
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2Annotator.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2Annotator.kt
new file mode 100644
index 000000000..04144b29b
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2Annotator.kt
@@ -0,0 +1,55 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang.colors
+
+import com.intellij.lang.annotation.AnnotationHolder
+import com.intellij.lang.annotation.Annotator
+import com.intellij.psi.PsiElement
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2ClassMapping
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2FieldMapping
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2MappingPart
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2MethodMapping
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2ParamIndex
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2ParamMapping
+import io.mcdev.obfex.formats.util.registerHighlight
+
+class TSrg2Annotator : Annotator {
+ override fun annotate(element: PsiElement, holder: AnnotationHolder) {
+ if (element is TSrg2ParamIndex) {
+ registerHighlight(element, TSrg2SyntaxHighlighter.PARAM_INDEX, holder)
+ return
+ }
+
+ if (element !is TSrg2MappingPart) {
+ return
+ }
+
+ val key = when (element.parent) {
+ is TSrg2ClassMapping -> TSrg2SyntaxHighlighter.CLASS_NAME
+ is TSrg2MethodMapping -> TSrg2SyntaxHighlighter.METHOD
+ is TSrg2FieldMapping -> TSrg2SyntaxHighlighter.FIELD
+ is TSrg2ParamMapping -> TSrg2SyntaxHighlighter.PARAM
+ else -> return
+ }
+
+ registerHighlight(element, key, holder)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2RainbowVisitor.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2RainbowVisitor.kt
new file mode 100644
index 000000000..9952c2e6d
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2RainbowVisitor.kt
@@ -0,0 +1,78 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang.colors
+
+import com.intellij.codeInsight.daemon.impl.HighlightVisitor
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.findParentOfType
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2ClassMapping
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2FieldMapping
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2FileElement
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2MappingPart
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2MethodMapping
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2MethodSignature
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2Namespace
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2ParamMapping
+import io.mcdev.obfex.formats.tsrg2.lang.TSrg2Language
+import io.mcdev.obfex.formats.util.NamespaceRainbowVisitor
+
+class TSrg2RainbowVisitor : NamespaceRainbowVisitor(TSrg2Language) {
+
+ override fun visit(element: PsiElement) {
+ if (element is TSrg2Namespace) {
+ highlightNamespaceDecl(element)
+ return
+ }
+
+ val file = element.findParentOfType() ?: return
+ val namespaces = file.header.namespaceList
+
+ if (element is TSrg2MappingPart) {
+ handleMappingPart(element, namespaces)
+ } else if (element is TSrg2MethodSignature) {
+ handleMethodSignature(element, namespaces)
+ }
+ }
+
+ private fun handleMappingPart(element: TSrg2MappingPart, namespaces: List) {
+ val index = indexOf(element)
+ if (namespaces.size <= index) {
+ return
+ }
+
+ when (element.parent) {
+ is TSrg2ClassMapping -> highlightElement(element, index, HighlightType.CLASS)
+ is TSrg2MethodMapping -> highlightElement(element, index, HighlightType.METHOD)
+ is TSrg2FieldMapping -> highlightElement(element, index, HighlightType.FIELD)
+ is TSrg2ParamMapping -> highlightElement(element, index, HighlightType.PARAM)
+ }
+ }
+
+ private fun handleMethodSignature(element: TSrg2MethodSignature, namespaces: List) {
+ if (namespaces.isEmpty()) {
+ return
+ }
+
+ highlightTypeSignature(element)
+ }
+
+ override fun clone(): HighlightVisitor = TSrg2RainbowVisitor()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2SyntaxHighlighter.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2SyntaxHighlighter.kt
new file mode 100644
index 000000000..d26b9b5d1
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2SyntaxHighlighter.kt
@@ -0,0 +1,95 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang.colors
+
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.fileTypes.SyntaxHighlighterBase
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tsrg.lang.colors.TSrgSyntaxHighlighter
+import io.mcdev.obfex.formats.tsrg2.gen.psi.TSrg2Types
+import io.mcdev.obfex.formats.tsrg2.lang.psi.TSrg2LexerAdapter
+
+class TSrg2SyntaxHighlighter : SyntaxHighlighterBase() {
+
+ override fun getHighlightingLexer(): Lexer = TSrg2LexerAdapter()
+
+ override fun getTokenHighlights(tokenType: IElementType?): Array =
+ when (tokenType) {
+ TSrg2Types.TSRG2_KEY, TSrg2Types.STATIC -> KEYWORD_KEYS
+ TSrg2Types.NAMESPACE_KEY -> NAMESPACE_KEYS
+ TSrg2Types.PRIMITIVE -> PRIMITIVE_KEYS
+ TSrg2Types.COMMENT -> COMMENT_KEYS
+ TSrg2Types.CLASS_TYPE -> CLASS_TYPE_KEYS
+ else -> TextAttributesKey.EMPTY_ARRAY
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ companion object {
+ val KEYWORD = TextAttributesKey.createTextAttributesKey(
+ "TSRG2_KEYWORD",
+ DefaultLanguageHighlighterColors.KEYWORD
+ )
+ val NAMESPACE = TextAttributesKey.createTextAttributesKey(
+ "TSRG2_NAMESPACE",
+ DefaultLanguageHighlighterColors.METADATA
+ )
+ val CLASS_NAME = TextAttributesKey.createTextAttributesKey(
+ "TSRG2_CLASS_NAME",
+ TSrgSyntaxHighlighter.CLASS_NAME
+ )
+ val METHOD = TextAttributesKey.createTextAttributesKey(
+ "TSRG2_METHOD",
+ TSrgSyntaxHighlighter.METHOD
+ )
+ val FIELD = TextAttributesKey.createTextAttributesKey(
+ "TSRG2_FIELD",
+ TSrgSyntaxHighlighter.FIELD
+ )
+ val PRIMITIVE = TextAttributesKey.createTextAttributesKey(
+ "TSRG2_PRIMITIVE",
+ TSrgSyntaxHighlighter.PRIMITIVE
+ )
+ val CLASS_TYPE = TextAttributesKey.createTextAttributesKey(
+ "TSRG2_CLASS_TYPE",
+ TSrgSyntaxHighlighter.CLASS_TYPE
+ )
+ val COMMENT = TextAttributesKey.createTextAttributesKey(
+ "TSRG2_COMMENT",
+ TSrgSyntaxHighlighter.COMMENT
+ )
+ val PARAM_INDEX = TextAttributesKey.createTextAttributesKey(
+ "TSRG2_PARAM_INDEX",
+ DefaultLanguageHighlighterColors.INLAY_DEFAULT
+ )
+ val PARAM = TextAttributesKey.createTextAttributesKey(
+ "TSRG2_PARAM",
+ DefaultLanguageHighlighterColors.PARAMETER
+ )
+
+ private val KEYWORD_KEYS = arrayOf(KEYWORD)
+ private val NAMESPACE_KEYS = arrayOf(NAMESPACE)
+ private val PRIMITIVE_KEYS = arrayOf(PRIMITIVE)
+ private val COMMENT_KEYS = arrayOf(COMMENT)
+ private val CLASS_TYPE_KEYS = arrayOf(CLASS_TYPE)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2SyntaxHighlighterFactory.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2SyntaxHighlighterFactory.kt
new file mode 100644
index 000000000..0570765e2
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/colors/TSrg2SyntaxHighlighterFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang.colors
+
+import com.intellij.openapi.fileTypes.SyntaxHighlighter
+import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+
+class TSrg2SyntaxHighlighterFactory : SyntaxHighlighterFactory() {
+ override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?): SyntaxHighlighter =
+ TSrg2SyntaxHighlighter()
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/psi/TSrg2ElementType.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/psi/TSrg2ElementType.kt
new file mode 100644
index 000000000..b6332331f
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/psi/TSrg2ElementType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tsrg2.lang.TSrg2Language
+import org.jetbrains.annotations.NonNls
+
+class TSrg2ElementType(@NonNls debugName: String) : IElementType(debugName, TSrg2Language)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/psi/TSrg2LexerAdapter.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/psi/TSrg2LexerAdapter.kt
new file mode 100644
index 000000000..73fa281b5
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/psi/TSrg2LexerAdapter.kt
@@ -0,0 +1,26 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang.psi
+
+import com.intellij.lexer.FlexAdapter
+import io.mcdev.obfex.formats.tsrg2.gen.TSrg2Lexer
+
+class TSrg2LexerAdapter : FlexAdapter(TSrg2Lexer())
diff --git a/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/psi/TSrg2TokenType.kt b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/psi/TSrg2TokenType.kt
new file mode 100644
index 000000000..4b219b2d2
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/tsrg2/lang/psi/TSrg2TokenType.kt
@@ -0,0 +1,27 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.tsrg2.lang.psi
+
+import com.intellij.psi.tree.IElementType
+import io.mcdev.obfex.formats.tsrg2.lang.TSrg2Language
+import org.jetbrains.annotations.NonNls
+
+class TSrg2TokenType(@NonNls debugName: String) : IElementType(debugName, TSrg2Language)
diff --git a/obfuscation-explorer/src/main/kotlin/formats/util/NamespaceRainbowVisitor.kt b/obfuscation-explorer/src/main/kotlin/formats/util/NamespaceRainbowVisitor.kt
new file mode 100644
index 000000000..d2597ef1b
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/util/NamespaceRainbowVisitor.kt
@@ -0,0 +1,137 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.util
+
+import com.intellij.codeHighlighting.ColorGenerator
+import com.intellij.codeHighlighting.RainbowHighlighter
+import com.intellij.codeInsight.daemon.RainbowVisitor
+import com.intellij.codeInsight.daemon.impl.HighlightInfo
+import com.intellij.codeInsight.daemon.impl.analysis.HighlightInfoHolder
+import com.intellij.lang.Language
+import com.intellij.openapi.editor.markup.EffectType
+import com.intellij.openapi.editor.markup.TextAttributes
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import java.awt.Color
+import java.awt.Font
+
+abstract class NamespaceRainbowVisitor(private val language: Language) : RainbowVisitor() {
+
+ private var rainbowColors: Array? = null
+
+ override fun suitableForFile(file: PsiFile): Boolean = file.language == language
+
+ override fun analyze(
+ file: PsiFile,
+ updateWholeFile: Boolean,
+ holder: HighlightInfoHolder,
+ action: Runnable,
+ ): Boolean {
+ computeColors(holder)
+ val res = super.analyze(file, updateWholeFile, holder, action)
+ rainbowColors = null
+ return res
+ }
+
+ protected fun highlightNamespaceDecl(element: PsiElement) {
+ val index = indexOf(element)
+ val color = getColorFromIndex(index)
+
+ val attribute = TextAttributes(color, null, null, null, Font.PLAIN)
+ val highlightInfo = HighlightInfo
+ .newHighlightInfo(RainbowHighlighter.RAINBOW_ELEMENT)
+ .textAttributes(attribute)
+ .range(element)
+ .create()
+ addInfo(highlightInfo)
+ }
+
+ protected fun highlightElement(
+ element: PsiElement,
+ index: Int,
+ type: HighlightType
+ ) {
+ val color = getColorFromIndex(index)
+
+ val fontType = when (type) {
+ HighlightType.CLASS -> Font.ITALIC
+ HighlightType.METHOD -> Font.BOLD
+ HighlightType.FIELD, HighlightType.PARAM, HighlightType.LOCAL_VAR -> Font.PLAIN
+ }
+
+ var attr = TextAttributes(color, null, null, null, fontType)
+ if (type == HighlightType.FIELD) {
+ attr = attr.highlight()
+ }
+
+ val info = HighlightInfo
+ .newHighlightInfo(RainbowHighlighter.RAINBOW_ELEMENT)
+ .textAttributes(attr)
+ .range(element)
+ .create()
+ addInfo(info)
+ }
+
+ protected fun highlightTypeSignature(element: PsiElement) {
+ val color = getColorFromIndex(0)
+
+ val textAttributes = TextAttributes(null, null, color, EffectType.SLIGHTLY_WIDER_BOX, Font.PLAIN)
+ val highlightInfo = HighlightInfo
+ .newHighlightInfo(RainbowHighlighter.RAINBOW_ELEMENT)
+ .textAttributes(textAttributes)
+ .range(element)
+ .create()
+ addInfo(highlightInfo)
+ }
+
+ protected fun indexOf(element: PsiElement): Int {
+ return element.parent.childrenOfType(element::class).indexOf(element)
+ }
+
+ protected enum class HighlightType {
+ CLASS, METHOD, FIELD, PARAM, LOCAL_VAR
+ }
+
+ protected fun getColorFromIndex(index: Int): Color {
+ val colors = rainbowColors ?: error("rainbowColors is not initialized")
+ return colors[index % colors.size]
+ }
+
+ private fun computeColors(holder: HighlightInfoHolder) {
+ val colorScheme = holder.colorsScheme
+ val stopRainbowColors = RainbowHighlighter.RAINBOW_COLOR_KEYS.map {
+ colorScheme.getAttributes(it).foregroundColor
+ }
+
+ val colors = ColorGenerator.generateLinearColorSequence(stopRainbowColors, 4)
+
+ var startingIndex = 0
+ var index = 0
+ rainbowColors = Array(colors.size) {
+ val color = colors[index]
+ index += 5
+ if (index >= colors.size) {
+ index = ++startingIndex
+ }
+ color
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/util/TabCodeStyleSettingsProvider.kt b/obfuscation-explorer/src/main/kotlin/formats/util/TabCodeStyleSettingsProvider.kt
new file mode 100644
index 000000000..9a42e1436
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/util/TabCodeStyleSettingsProvider.kt
@@ -0,0 +1,36 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.util
+
+import com.intellij.psi.codeStyle.CommonCodeStyleSettings
+import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider
+
+abstract class TabCodeStyleSettingsProvider : LanguageCodeStyleSettingsProvider() {
+
+ override fun customizeDefaults(
+ commonSettings: CommonCodeStyleSettings,
+ indentOptions: CommonCodeStyleSettings.IndentOptions
+ ) {
+ indentOptions.USE_TAB_CHARACTER = true
+ indentOptions.TAB_SIZE = 4
+ indentOptions.INDENT_SIZE = indentOptions.TAB_SIZE
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/util/sigws/SignificantWhitespaceLexer.kt b/obfuscation-explorer/src/main/kotlin/formats/util/sigws/SignificantWhitespaceLexer.kt
new file mode 100644
index 000000000..477ef4e9c
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/util/sigws/SignificantWhitespaceLexer.kt
@@ -0,0 +1,144 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.util.sigws
+
+import com.intellij.lexer.Lexer
+import com.intellij.lexer.LexerBase
+import com.intellij.psi.tree.IElementType
+import com.intellij.psi.tree.TokenSet
+
+abstract class SignificantWhitespaceLexer(private val delegate: Lexer) : LexerBase() {
+
+ private var lookingForIndent: Boolean = true
+ private var lastSeenTabCount = 0
+ private var currentToken: Token? = null
+
+ private val tokenStack = ArrayDeque()
+
+ abstract val newlineTokens: TokenSet
+ abstract val tabTokens: TokenSet
+
+ abstract val virtualOpenToken: IElementType
+ abstract val virtualCloseToken: IElementType
+
+ override fun getState(): Int = delegate.state
+
+ override fun getTokenType(): IElementType? = currentToken?.tokenType
+ override fun getTokenStart(): Int = currentToken?.start ?: 0
+ override fun getTokenEnd(): Int = currentToken?.end ?: 0
+
+ override fun getBufferSequence(): CharSequence = delegate.bufferSequence
+ override fun getBufferEnd(): Int = delegate.bufferEnd
+
+ override fun start(buffer: CharSequence, startOffset: Int, endOffset: Int, initialState: Int) {
+ delegate.start(buffer, startOffset, endOffset, initialState)
+ compute(getCurrentToken())
+ }
+
+ override fun advance() {
+ if (tokenStack.isNotEmpty()) {
+ currentToken = tokenStack.removeFirst()
+ return
+ }
+
+ compute()
+ }
+
+ private fun compute(firstToken: Token? = null) {
+ var newToken = firstToken ?: getNextToken() ?: return
+ val firstNewToken = newToken
+
+ if (!lookingForIndent) {
+ currentToken = newToken
+ if (newToken.tokenType in newlineTokens) {
+ lookingForIndent = true
+ }
+ return
+ }
+ lookingForIndent = false
+
+ tokenStack.addLast(newToken)
+
+ var tabCount = 0
+ while (newToken.tokenType in tabTokens) {
+ tabCount++
+ newToken = getNextToken()?.also { tokenStack.addLast(it) } ?: break
+ }
+
+ if (newToken.tokenType in newlineTokens) {
+ // this is a blank line, ignore it
+ currentToken = tokenStack.removeFirstOrNull()
+ return
+ }
+
+ when {
+ tabCount > lastSeenTabCount -> {
+ val indentToken = virtualOpen(firstNewToken)
+ repeat(tabCount - lastSeenTabCount) {
+ tokenStack.addFirst(indentToken)
+ }
+ }
+ tabCount < lastSeenTabCount -> {
+ val unindentToken = virtualClose(firstNewToken)
+ repeat(lastSeenTabCount - tabCount) {
+ tokenStack.addFirst(unindentToken)
+ }
+ }
+ }
+ lastSeenTabCount = tabCount
+
+ currentToken = tokenStack.removeFirstOrNull()
+ }
+
+ private fun getNextToken(): Token? {
+ delegate.advance()
+ return getCurrentToken()
+ }
+
+ private fun getCurrentToken(): Token? {
+ if (delegate.tokenType == null) {
+ currentToken = null
+ return null
+ }
+ return Token(delegate.tokenType, delegate.tokenStart, delegate.tokenEnd)
+ }
+
+ private fun virtualOpen(precedesToken: Token) = Token(
+ tokenType = virtualOpenToken,
+ start = precedesToken.start,
+ end = precedesToken.start,
+ )
+
+ private fun virtualClose(precedesToken: Token) = Token(
+ tokenType = virtualCloseToken,
+ start = precedesToken.start,
+ end = precedesToken.start,
+ )
+
+ private class Token(
+ val tokenType: IElementType?,
+ val start: Int,
+ val end: Int,
+ ) {
+ val isEof: Boolean
+ get() = tokenType == null
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/util/sigws/SignificantWhitespaceParserUtil.kt b/obfuscation-explorer/src/main/kotlin/formats/util/sigws/SignificantWhitespaceParserUtil.kt
new file mode 100644
index 000000000..d949400bd
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/util/sigws/SignificantWhitespaceParserUtil.kt
@@ -0,0 +1,54 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.util.sigws
+
+import com.intellij.lang.PsiBuilder
+import com.intellij.lang.parser.GeneratedParserUtilBase
+
+open class SignificantWhitespaceParserUtil : GeneratedParserUtilBase() {
+ companion object {
+ @JvmStatic
+ fun indent0(
+ builder: PsiBuilder,
+ level: Int,
+ virtualOpen: Parser,
+ virtualClose: Parser,
+ param: Parser,
+ ): Boolean {
+ if (!recursion_guard_(builder, level, "indent0")) {
+ return false
+ }
+
+ val marker = enter_section_(builder)
+
+ val res =
+ // VIRTUAL_OPEN
+ virtualOpen.parse(builder, level + 1) &&
+ // <>
+ param.parse(builder, level) &&
+ // (VIRTUAL_CLOSE | <>)
+ (virtualClose.parse(builder, level + 1) || eof(builder, level + 1))
+
+ exit_section_(builder, marker, null, res)
+ return res
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/util/util.kt b/obfuscation-explorer/src/main/kotlin/formats/util/util.kt
new file mode 100644
index 000000000..9d915d951
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/util/util.kt
@@ -0,0 +1,84 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.util
+
+import com.intellij.lang.annotation.AnnotationHolder
+import com.intellij.lang.annotation.HighlightSeverity
+import com.intellij.openapi.editor.colors.TextAttributesKey
+import com.intellij.openapi.editor.markup.TextAttributes
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.ui.JBColor
+import java.awt.Color
+import java.awt.Font
+import kotlin.reflect.KClass
+
+fun TextAttributesKey.highlight(): TextAttributes = defaultAttributes.highlight()
+fun TextAttributes.highlight(): TextAttributes =
+ TextAttributes.merge(
+ this,
+ TextAttributes(
+ null,
+ foregroundColor.toBackground(),
+ foregroundColor,
+ null,
+ Font.ITALIC
+ )
+
+ )
+
+fun registerHighlight(
+ element: PsiElement?,
+ key: TextAttributesKey,
+ holder: AnnotationHolder,
+ highlight: Boolean = false
+) {
+ if (element == null) {
+ return
+ }
+ val builder = holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
+ .range(element)
+ .textAttributes(key)
+ if (highlight) {
+ builder.enforcedTextAttributes(key.highlight())
+ }
+ builder.create()
+}
+
+private fun Color.toBackground(): Color {
+ val darkerBase = darker().darker().darker().darker()
+ val brighterBase = brighter().brighter().brighter().brighter()
+ return JBColor(
+ Color(brighterBase.red, brighterBase.green, brighterBase.blue, 128),
+ Color(darkerBase.red, darkerBase.green, darkerBase.blue, 128)
+ )
+}
+
+fun PsiElement.childrenOfType(type: KClass): List {
+ return PsiTreeUtil.getChildrenOfTypeAsList(this, type.java)
+}
+
+fun Array<*>.indicesFrom(first: Int): IntRange {
+ if (first >= size) {
+ return IntRange.EMPTY
+ }
+ return first until size
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/xsrg/XSrgMappingsFormatParser.kt b/obfuscation-explorer/src/main/kotlin/formats/xsrg/XSrgMappingsFormatParser.kt
new file mode 100644
index 000000000..5af4c9091
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/xsrg/XSrgMappingsFormatParser.kt
@@ -0,0 +1,224 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.xsrg
+
+import com.intellij.openapi.vfs.VirtualFile
+import io.mcdev.obfex.MappingPart
+import io.mcdev.obfex.forLines
+import io.mcdev.obfex.mappings.FileCoords
+import io.mcdev.obfex.mappings.MappingsDefinition
+import io.mcdev.obfex.mappings.MappingsDefinitionBuilder
+import io.mcdev.obfex.mappings.UnnamedMappingsFormatParser
+import io.mcdev.obfex.mappings.clazz
+import io.mcdev.obfex.mappings.field
+import io.mcdev.obfex.mappings.method
+import io.mcdev.obfex.mappings.pack
+import io.mcdev.obfex.ref.asClass
+import io.mcdev.obfex.ref.asField
+import io.mcdev.obfex.ref.asMethod
+import io.mcdev.obfex.ref.asMethodDesc
+import io.mcdev.obfex.ref.asPackage
+import io.mcdev.obfex.ref.asTypeDef
+import io.mcdev.obfex.ref.highPriority
+import io.mcdev.obfex.ref.lowPriority
+import io.mcdev.obfex.splitOnLast
+
+@Suppress("DuplicatedCode")
+class XSrgMappingsFormatParser : UnnamedMappingsFormatParser {
+
+ override val expectedFileExtensions: Array = arrayOf("xsrg")
+
+ override fun parse(file: VirtualFile): MappingsDefinition {
+ val builder = MappingsDefinitionBuilder(XSrgMappingsFormatType, file)
+
+ file.forLines { lineNum, parts, _ ->
+ if (parts.isEmpty()) {
+ return@forLines true
+ }
+
+ val key = parts.first()
+
+ when (key.value) {
+ "PK:" -> parsePackageLine(builder, lineNum, parts)
+ "CL:" -> parseClassLine(builder, lineNum, parts)
+ "MD:" -> parseMethodLine(builder, lineNum, parts)
+ "FD:" -> parseFieldLine(builder, lineNum, parts)
+ else -> builder.error("Unrecognized key: ${key.value.substring(0, 3)}", FileCoords(lineNum))
+ }
+
+ return@forLines true
+ }
+
+ return builder.build()
+ }
+
+ private fun parsePackageLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 3) {
+ builder.error("Package line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (_, leftPack, rightPack) = parts
+ builder.pack(lineNum.highPriority) {
+ with(leftPack.asPackage().from)
+ with(rightPack.asPackage().to)
+ }
+ }
+
+ private fun parseClassLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 3) {
+ builder.error("Class line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (_, leftClass, rightClass) = parts
+ builder.clazz(lineNum.highPriority) {
+ with(leftClass.asClass().from)
+ with(rightClass.asClass().to)
+ }
+ }
+
+ private fun parseFieldLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ when (parts.size) {
+ 5 -> {
+ val (leftClass, leftField) = parts[1].splitOnLast('/')
+ if (leftField == null) {
+ builder.error(
+ "Field mapping is invalid, no field is specified after class",
+ FileCoords(lineNum, parts[1])
+ )
+ return
+ }
+
+ val (rightClass, rightField) = parts[3].splitOnLast('/')
+ if (rightField == null) {
+ builder.error(
+ "Field mapping is invalid, no field is specified after class",
+ FileCoords(lineNum, parts[2])
+ )
+ return
+ }
+
+ val leftDesc = parts[2].value.asTypeDef()
+ if (leftDesc == null) {
+ builder.error("Field descriptor is invalid", FileCoords(lineNum, parts[2]))
+ return
+ }
+
+ val rightDesc = parts[4].value.asTypeDef()
+ if (rightDesc == null) {
+ builder.error("Field descriptor is invalid", FileCoords(lineNum, parts[4]))
+ return
+ }
+
+ builder.clazz(leftClass.asClass().from, lineNum.lowPriority) {
+ unlessExists(rightClass.asClass().to)
+
+ field(lineNum.highPriority) {
+ type = leftDesc
+
+ with(leftField.asField().from)
+ with(rightField.asField().to)
+ }
+ }
+ }
+ 3 -> {
+ val (leftClass, leftField) = parts[1].splitOnLast('/')
+ if (leftField == null) {
+ builder.error(
+ "Field mapping is invalid, no field is specified after class",
+ FileCoords(lineNum, parts[1])
+ )
+ return
+ }
+
+ val (rightClass, rightField) = parts[2].splitOnLast('/')
+ if (rightField == null) {
+ builder.error(
+ "Field mapping is invalid, no field is specified after class",
+ FileCoords(lineNum, parts[2])
+ )
+ return
+ }
+
+ builder.clazz(leftClass.asClass().from, lineNum.lowPriority) {
+ unlessExists(rightClass.asClass().to)
+
+ field(lineNum.highPriority) {
+ with(leftField.asField().from)
+ with(rightField.asField().to)
+ }
+ }
+ }
+ else -> {
+ builder.error("Field line is invalid", FileCoords(lineNum))
+ }
+ }
+ }
+
+ private fun parseMethodLine(builder: MappingsDefinitionBuilder, lineNum: Int, parts: Array) {
+ if (parts.size != 5) {
+ builder.error("Method line is invalid", FileCoords(lineNum))
+ return
+ }
+
+ val (leftClass, leftMethod) = parts[1].splitOnLast('/')
+ if (leftMethod == null) {
+ builder.error(
+ "Method mapping is invalid, no method is specified after class",
+ FileCoords(lineNum, parts[1])
+ )
+ return
+ }
+
+ val (rightClass, rightMethod) = parts[3].splitOnLast('/')
+ if (rightMethod == null) {
+ builder.error(
+ "Method mapping is invalid, no method is specified after class",
+ FileCoords(lineNum, parts[3])
+ )
+ return
+ }
+
+ val leftDesc = parts[2].value.asMethodDesc()
+ if (leftDesc == null) {
+ builder.error("Method descriptor is invalid", FileCoords(lineNum, parts[2]))
+ return
+ }
+
+ val rightDesc = parts[4].value.asMethodDesc()
+ if (rightDesc == null) {
+ builder.error("Method descriptor is invalid", FileCoords(lineNum, parts[4]))
+ return
+ }
+
+ builder.clazz(leftClass.asClass().from, lineNum.lowPriority) {
+ unlessExists(rightClass.asClass().to)
+
+ method(lineNum.highPriority) {
+ desc = leftDesc
+
+ with(leftMethod.asMethod().from)
+ with(rightMethod.asMethod().to)
+ }
+ }
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/xsrg/XSrgMappingsFormatType.kt b/obfuscation-explorer/src/main/kotlin/formats/xsrg/XSrgMappingsFormatType.kt
new file mode 100644
index 000000000..8df9c243f
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/xsrg/XSrgMappingsFormatType.kt
@@ -0,0 +1,31 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.xsrg
+
+import io.mcdev.obfex.ObfIcons
+import io.mcdev.obfex.formats.MappingsFormatType
+import javax.swing.Icon
+
+object XSrgMappingsFormatType : MappingsFormatType("XSRG") {
+
+ override val icon: Icon = ObfIcons.XSRG_ICON
+ override val name: String = "XSRG"
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgFile.kt b/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgFile.kt
new file mode 100644
index 000000000..f4498f9d4
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgFile.kt
@@ -0,0 +1,30 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.xsrg.lang
+
+import com.intellij.extapi.psi.PsiFileBase
+import com.intellij.openapi.fileTypes.FileType
+import com.intellij.psi.FileViewProvider
+
+class XSrgFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, XSrgLanguage) {
+ override fun getFileType(): FileType = XSrgFileType
+ override fun toString(): String = XSrgFileType.description
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgFileType.kt b/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgFileType.kt
new file mode 100644
index 000000000..e8a3035e3
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgFileType.kt
@@ -0,0 +1,33 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.xsrg.lang
+
+import com.intellij.openapi.fileTypes.LanguageFileType
+import io.mcdev.obfex.ObfIcons
+import javax.swing.Icon
+
+object XSrgFileType : LanguageFileType(XSrgLanguage) {
+
+ override fun getName(): String = "XSRG"
+ override fun getDescription(): String = "XSRG obfuscation mapping file"
+ override fun getDefaultExtension(): String = "xsrg"
+ override fun getIcon(): Icon = ObfIcons.XSRG_ICON
+}
diff --git a/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgLanguage.kt b/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgLanguage.kt
new file mode 100644
index 000000000..8eaff90e5
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgLanguage.kt
@@ -0,0 +1,25 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.xsrg.lang
+
+import com.intellij.lang.Language
+
+object XSrgLanguage : Language("XSRG")
diff --git a/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgParserDefinition.kt b/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgParserDefinition.kt
new file mode 100644
index 000000000..d938233d0
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/formats/xsrg/lang/XSrgParserDefinition.kt
@@ -0,0 +1,50 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.formats.xsrg.lang
+
+import com.intellij.lang.ASTNode
+import com.intellij.lang.Language
+import com.intellij.lang.ParserDefinition
+import com.intellij.lang.PsiParser
+import com.intellij.lexer.Lexer
+import com.intellij.openapi.project.Project
+import com.intellij.psi.FileViewProvider
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.tree.IFileElementType
+import com.intellij.psi.tree.TokenSet
+import io.mcdev.obfex.formats.srg.gen.SrgParser
+import io.mcdev.obfex.formats.srg.gen.psi.SrgTypes
+import io.mcdev.obfex.formats.srg.lang.psi.SrgLexerAdapter
+
+class XSrgParserDefinition : ParserDefinition {
+
+ override fun createLexer(project: Project): Lexer = SrgLexerAdapter()
+ override fun getCommentTokens(): TokenSet = COMMENTS
+ override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY
+ override fun createParser(project: Project): PsiParser = SrgParser()
+ override fun getFileNodeType(): IFileElementType = FILE
+ override fun createFile(viewProvider: FileViewProvider): PsiFile = XSrgFile(viewProvider)
+ override fun createElement(node: ASTNode): PsiElement = SrgTypes.Factory.createElement(node)
+}
+
+private val COMMENTS = TokenSet.create(SrgTypes.COMMENT)
+private val FILE = IFileElementType(Language.findInstance(XSrgLanguage::class.java))
diff --git a/obfuscation-explorer/src/main/kotlin/lookup/HashLookupIndex.kt b/obfuscation-explorer/src/main/kotlin/lookup/HashLookupIndex.kt
new file mode 100644
index 000000000..42fe83f98
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/lookup/HashLookupIndex.kt
@@ -0,0 +1,87 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.lookup
+
+import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
+import java.lang.invoke.MethodHandles
+import kotlin.reflect.KClass
+
+class HashLookupIndex(
+ private val table: HashLookupTable,
+ private val transformer: LookupIndexTransformer,
+) : LookupIndex {
+
+ private var map: Object2ObjectOpenHashMap>? = null
+
+ override fun query(key: K?): MultiLookupTable {
+ return map?.get(key) ?: MultiLookupTable.empty()
+ }
+
+ @Suppress("unused") // indirectly accessed by HashLookupTable
+ private fun rebuild() {
+ @Suppress("UNCHECKED_CAST")
+ val store = tableList.get(table.unwrap(HashLookupTable::class)) as ObjectLinkedOpenHashSet
+
+ map = Object2ObjectOpenHashMap(store.size)
+
+ for (t in store) {
+ add(t)
+ }
+ }
+
+ private fun add(value: T) {
+ val keys = transformer(value)
+
+ for (key in keys) {
+ map?.computeIfAbsent(key, Object2ObjectFunction { HashLookupTable() })?.add(value)
+ }
+ }
+
+ @Suppress("unused") // indirectly accessed by HashLookupTable
+ private fun remove(value: T) {
+ val keys = transformer(value)
+
+ for (key in keys) {
+ val list = map?.get(key)
+ list?.remove(value)
+
+ if (list?.isEmpty() == true) {
+ map?.remove(key)
+ }
+ }
+ }
+
+ override fun > unwrap(type: KClass): L? {
+ if (type.isInstance(this)) {
+ @Suppress("UNCHECKED_CAST")
+ return this as L
+ }
+ return null
+ }
+
+ private companion object {
+ @JvmStatic
+ private val tableList = MethodHandles.privateLookupIn(HashLookupTable::class.java, MethodHandles.lookup())
+ .findVarHandle(HashLookupTable::class.java, "store", ObjectLinkedOpenHashSet::class.java)
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/lookup/HashLookupTable.kt b/obfuscation-explorer/src/main/kotlin/lookup/HashLookupTable.kt
new file mode 100644
index 000000000..b5db39719
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/lookup/HashLookupTable.kt
@@ -0,0 +1,117 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.lookup
+
+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
+import java.lang.invoke.MethodHandles
+import java.lang.invoke.MethodType
+import java.util.Collections
+import kotlin.reflect.KClass
+
+/**
+ * [MultiLookupTable] implementation based on hash tables.
+ */
+open class HashLookupTable : MultiLookupTable {
+
+ private val store: ObjectLinkedOpenHashSet = ObjectLinkedOpenHashSet()
+
+ private val indices: MutableList> = mutableListOf()
+
+ override fun seq(): Sequence = store.asSequence()
+
+ override fun list(): List = Collections.unmodifiableList(store.toList())
+
+ override val size: Int
+ get() = store.size
+
+ override fun indexMulti(transformer: LookupIndexTransformer): LookupIndex {
+ for (index in indices) {
+ if (lookupIndexTransformer.get(index) === transformer) {
+ @Suppress("UNCHECKED_CAST")
+ return index as LookupIndex
+ }
+ }
+
+ val index = HashLookupIndex(this, transformer)
+ rebuildIndex.invoke(index)
+ indices += index
+
+ return index
+ }
+
+ override fun add(value: T) {
+ if (store.add(value)) {
+ for (index in indices) {
+ addToIndex.invoke(index, value)
+ }
+ }
+ }
+
+ override fun remove(value: T) {
+ if (store.remove(value)) {
+ for (index in indices) {
+ removeFromIndex.invoke(index, value)
+ }
+ }
+ }
+
+ override fun > unwrap(type: KClass): L? {
+ if (type.isInstance(this)) {
+ @Suppress("UNCHECKED_CAST")
+ return this as L
+ }
+ return null
+ }
+
+ companion object {
+ @JvmStatic
+ private val lookupIndexTransformer =
+ MethodHandles.privateLookupIn(HashLookupIndex::class.java, MethodHandles.lookup())
+ .findVarHandle(HashLookupIndex::class.java, "transformer", Function1::class.java)
+
+ @JvmStatic
+ private val rebuildIndex =
+ MethodHandles.privateLookupIn(HashLookupIndex::class.java, MethodHandles.lookup())
+ .findVirtual(
+ HashLookupIndex::class.java,
+ "rebuild",
+ MethodType.methodType(Void::class.javaPrimitiveType)
+ )
+
+ @JvmStatic
+ private val addToIndex =
+ MethodHandles.privateLookupIn(HashLookupIndex::class.java, MethodHandles.lookup())
+ .findVirtual(
+ HashLookupIndex::class.java,
+ "add",
+ MethodType.methodType(Void::class.javaPrimitiveType, Any::class.java)
+ )
+
+ @JvmStatic
+ private val removeFromIndex =
+ MethodHandles.privateLookupIn(HashLookupIndex::class.java, MethodHandles.lookup())
+ .findVirtual(
+ HashLookupIndex::class.java,
+ "remove",
+ MethodType.methodType(Void::class.javaPrimitiveType, Any::class.java)
+ )
+ }
+}
diff --git a/obfuscation-explorer/src/main/kotlin/lookup/LookupIndex.kt b/obfuscation-explorer/src/main/kotlin/lookup/LookupIndex.kt
new file mode 100644
index 000000000..fd7b1a235
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/lookup/LookupIndex.kt
@@ -0,0 +1,36 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.lookup
+
+import kotlin.reflect.KClass
+
+/**
+ * Index for optimized queries from [MultiLookupTable].
+ */
+interface LookupIndex {
+
+ /**
+ * Query the index using the given key.
+ */
+ fun query(key: K?): MultiLookupTable
+
+ fun > unwrap(type: KClass): L?
+}
diff --git a/obfuscation-explorer/src/main/kotlin/lookup/MultiLookupTable.kt b/obfuscation-explorer/src/main/kotlin/lookup/MultiLookupTable.kt
new file mode 100644
index 000000000..2d59acb5d
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/lookup/MultiLookupTable.kt
@@ -0,0 +1,96 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package io.mcdev.obfex.lookup
+
+import java.util.Collections
+import kotlin.reflect.KClass
+
+typealias SingleLookupIndexTransformer = (T) -> K?
+typealias LookupIndexTransformer = (T) -> List
+
+/**
+ * Data structure which uses [indices][LookupIndex] to improve query performance of multiple distinct lookups.
+ */
+interface MultiLookupTable {
+
+ /**
+ * Get the backing store for this table. This list should not be modified, use [add] or [remove] methods on
+ * this class instead.
+ */
+ fun seq(): Sequence
+
+ fun list(): List
+
+ fun firstOrNull(): T? {
+ return seq().firstOrNull()
+ }
+
+ fun singleOrNull(): T? {
+ return seq().singleOrNull()
+ }
+
+ /**
+ * Returns `true` if this table is empty.
+ */
+ fun isEmpty(): Boolean = size == 0
+
+ /**
+ * Returns the number of elements in this table.
+ */
+ val size: Int
+
+ /**
+ * Create a new [index][LookupIndex] to allow for efficient queries to the data store. Lookup tables with more
+ * indices have worse write performance, as the indices have to be rebuilt, so a balance is a good idea.
+ */
+ fun indexMulti(transformer: LookupIndexTransformer): LookupIndex
+
+ fun index(transformer: SingleLookupIndexTransformer): LookupIndex = indexMulti {
+ listOfNotNull(transformer(it))
+ }
+
+ /**
+ * Add a new value to the table.
+ */
+ fun add(value: T)
+
+ /**
+ * Remove a value from the table.
+ */
+ fun remove(value: T)
+
+ fun > unwrap(type: KClass): L?
+
+ companion object {
+ private val emptyTable = HashLookupTable()
+
+ @Suppress("UNCHECKED_CAST")
+ fun empty(): MultiLookupTable = emptyTable as MultiLookupTable
+ }
+}
+
+/**
+ * Naive query, not using any optimization, simply look through the raw store for any matches for the given
+ * predicate and return the result. For repeated queries consider using an [index][MultiLookupTable.index] to improve
+ * performance.
+ */
+inline fun MultiLookupTable.query(predicate: (T) -> Boolean): List =
+ Collections.unmodifiableList(list().filter(predicate))
diff --git a/obfuscation-explorer/src/main/kotlin/mappings/MappingElement.kt b/obfuscation-explorer/src/main/kotlin/mappings/MappingElement.kt
new file mode 100644
index 000000000..85a1eed66
--- /dev/null
+++ b/obfuscation-explorer/src/main/kotlin/mappings/MappingElement.kt
@@ -0,0 +1,394 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2024 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * 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 Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+@file:Suppress("MemberVisibilityCanBePrivate", "DuplicatedCode")
+
+package io.mcdev.obfex.mappings
+
+import com.intellij.util.containers.map2Array
+import io.mcdev.obfex.Tristate
+import io.mcdev.obfex.ref.FieldName
+import io.mcdev.obfex.ref.LocalFieldRef
+import io.mcdev.obfex.ref.LocalMethodRef
+import io.mcdev.obfex.ref.LocalVarIndex
+import io.mcdev.obfex.ref.LvtIndex
+import io.mcdev.obfex.ref.MethodDescriptor
+import io.mcdev.obfex.ref.MethodName
+import io.mcdev.obfex.ref.ParamIndex
+import io.mcdev.obfex.ref.ParamMap
+import io.mcdev.obfex.ref.TypeDef
+import io.mcdev.obfex.ref.asFieldRef
+import io.mcdev.obfex.ref.asMethodRef
+import io.mcdev.obfex.ref.asParamIndex
+import io.mcdev.obfex.ref.asRef
+import it.unimi.dsi.fastutil.objects.ObjectArrayList
+import java.lang.IllegalArgumentException
+import java.util.ArrayList
+import java.util.Collections
+
+sealed interface MappingElement {
+ val names: Array
+ val location: MappingLocation
+ val mappingSet: MappingSet
+
+ fun name(index: Int): String? = names[index]
+ fun name(ns: MappingNamespace): String? = name(index(ns))
+
+ fun index(ns: MappingNamespace): Int = mappingSet.namespaceIndex(ns)
+}
+
+val MappingElement.unnamedFrom: String?
+ get() = name(MappingNamespace.unnamedFrom(mappingSet))
+val MappingElement.unnamedTo: String?
+ get() = name(MappingNamespace.unnamedTo(mappingSet))
+
+class PackageMappingElement(
+ override val names: Array,
+ override val mappingSet: MappingSet,
+ override val location: MappingLocation,
+) : MappingElement
+
+class ClassMappingElement(
+ override val names: Array,
+ val parent: ClassMappingElement? = null,
+ override val mappingSet: MappingSet,
+ override val location: MappingLocation = UnknownLocation,
+) : MappingElement {
+
+ // classes generally have relatively few members (< 1000) so the naive approach for storing and querying them is
+ // probably better
+ private val fieldStore = ObjectArrayList()
+ private val methodStore = ObjectArrayList()
+
+ fun fullyQualifiedName(index: Int): String? = when (val p = parent) {
+ is ClassMappingElement -> p.fullyQualifiedName(index) + "." + name(index)
+ else -> name(index)
+ }
+
+ fun fullyQualifiedName(ns: MappingNamespace): String? = fullyQualifiedName(mappingSet.namespaceIndex(ns))
+
+ // fields
+
+ fun fields(): List {
+ return Collections.unmodifiableList(fieldStore)
+ }
+
+ fun fields(ref: LocalFieldRef): List {
+ if (ref.type == null) {
+ return fieldStore.filter { it.names.contains(ref.name.name) }
+ }
+
+ // We don't know which namespace this field type is, so attempt to map for each namespace
+ val mappedTypes = if (ref.type.isMappable()) {
+ mappingSet.namespaces.map2Array { mappingSet.mapTypeFrom(it, ref.type) }
+ } else {
+ Array(mappingSet.namespaces.size) { ref.type }
+ }
+
+ return fieldStore.filter { mapping ->
+ mappedTypes.withIndex().any { (i, type) ->
+ mapping.name(i) == ref.name.name && type == mapping.type
+ }
+ }
+ }
+
+ fun fields(name: FieldName, type: TypeDef? = null): List =
+ fields(name.asRef(type))
+
+ fun fields(name: String, type: TypeDef? = null): List =
+ fields(name.asFieldRef(type))
+
+ fun fields(ns: MappingNamespace, ref: LocalFieldRef): List {
+ if (ref.type == null) {
+ return fieldStore.filter { it.name(ns) == ref.name.name }
+ }
+
+ val type = if (ns != mappingSet.typeNamespace && ref.type.isMappable()) {
+ mappingSet.mapTypeFrom(ns, ref.type)
+ } else {
+ ref.type
+ }
+
+ return fieldStore.filter { it.name(ns) == ref.name.name && it.type == type }
+ }
+
+ fun fields(ns: MappingNamespace, name: FieldName, type: TypeDef? = null): List =
+ fields(ns, name.asRef(type))
+
+ fun fields(ns: MappingNamespace, name: String, type: TypeDef? = null): List =
+ fields(ns, name.asFieldRef(type))
+
+ fun field(ns: MappingNamespace, ref: LocalFieldRef): FieldMappingElement? =
+ fields(ns, ref).firstOrNull()
+
+ fun field(ns: MappingNamespace, name: FieldName, type: TypeDef? = null): FieldMappingElement? =
+ field(ns, name.asRef(type))
+
+ fun field(ns: MappingNamespace, name: String, type: TypeDef? = null): FieldMappingElement? =
+ field(ns, name.asFieldRef(type))
+
+ fun field(ref: LocalFieldRef): FieldMappingElement? {
+ return mappingSet.namespaces.firstNotNullOfOrNull { ns -> field(ns, ref) }
+ }
+
+ fun field(name: FieldName, type: TypeDef? = null): FieldMappingElement? = field(name.asRef(type))
+ fun field(name: String, type: TypeDef? = null): FieldMappingElement? = field(name.asFieldRef(type))
+
+ fun addFieldMapping(
+ names: Array,
+ location: MappingLocation,
+ type: TypeDef? = null,
+ metadata: MemberMetadata = MemberMetadata.UNKNOWN,
+ ): FieldMappingElement {
+ val element = FieldMappingElement(names, this, type, metadata, mappingSet, location)
+ fieldStore.add(element)
+ return element
+ }
+
+ // methods
+
+ fun methods(): List {
+ return Collections.unmodifiableList(methodStore)
+ }
+
+ fun methods(ref: LocalMethodRef): List {
+ if (ref.desc == null) {
+ return methodStore.filter { it.names.contains(ref.name.name) }
+ }
+
+ // We don't know which namespace this field type is, so attempt to map for each namespace
+ val mappedTypes = if (ref.desc.isMappable()) {
+ mappingSet.namespaces.map2Array { mappingSet.mapTypeFrom(it, ref.desc) }
+ } else {
+ Array(mappingSet.namespaces.size) { ref.desc }
+ }
+
+ return methodStore.filter { mapping ->
+ mappedTypes.withIndex().any { (i, type) ->
+ mapping.name(i) == ref.name.name && type == mapping.descriptor
+ }
+ }
+ }
+
+ fun methods(name: MethodName, desc: MethodDescriptor? = null): List =
+ methods(name.asRef(desc))
+
+ fun methods(name: String, desc: MethodDescriptor? = null): List