diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd60d251..60545ba7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+- Added CSRG writer
+- Added TSRG and TSRG2 writer
- Added Recaf Simple reader and writer
- Added `MappingFormat#hasWriter` boolean
diff --git a/src/main/java/net/fabricmc/mappingio/MappingWriter.java b/src/main/java/net/fabricmc/mappingio/MappingWriter.java
index 94832e3d..0e973ee4 100644
--- a/src/main/java/net/fabricmc/mappingio/MappingWriter.java
+++ b/src/main/java/net/fabricmc/mappingio/MappingWriter.java
@@ -29,7 +29,9 @@
import net.fabricmc.mappingio.format.enigma.EnigmaFileWriter;
import net.fabricmc.mappingio.format.proguard.ProGuardFileWriter;
import net.fabricmc.mappingio.format.simple.RecafSimpleFileWriter;
+import net.fabricmc.mappingio.format.srg.CsrgFileWriter;
import net.fabricmc.mappingio.format.srg.SrgFileWriter;
+import net.fabricmc.mappingio.format.srg.TsrgFileWriter;
import net.fabricmc.mappingio.format.tiny.Tiny1FileWriter;
import net.fabricmc.mappingio.format.tiny.Tiny2FileWriter;
@@ -56,6 +58,9 @@ static MappingWriter create(Writer writer, MappingFormat format) throws IOExcept
case ENIGMA_FILE: return new EnigmaFileWriter(writer);
case SRG_FILE: return new SrgFileWriter(writer, false);
case XSRG_FILE: return new SrgFileWriter(writer, true);
+ case CSRG_FILE: return new CsrgFileWriter(writer);
+ case TSRG_FILE: return new TsrgFileWriter(writer, false);
+ case TSRG_2_FILE: return new TsrgFileWriter(writer, true);
case PROGUARD_FILE: return new ProGuardFileWriter(writer);
case RECAF_SIMPLE_FILE: return new RecafSimpleFileWriter(writer);
default: return null;
diff --git a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
index 5d35cfcc..73ab0a50 100644
--- a/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
+++ b/src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
@@ -151,18 +151,18 @@ public enum MappingFormat {
/**
* The {@code CSRG} ("Compact SRG", since it saves disk space over SRG) mapping format, as specified here.
*/
- CSRG_FILE("CSRG file", "csrg", false, false, false, false, false, false),
+ CSRG_FILE("CSRG file", "csrg", false, false, false, false, false, true),
/**
* The {@code TSRG} ("Tiny SRG", since it saves disk space over SRG) mapping format, as specified here.
* Same as CSRG, but hierarchical instead of flat.
*/
- TSRG_FILE("TSRG file", "tsrg", false, false, false, false, false, false),
+ TSRG_FILE("TSRG file", "tsrg", false, false, false, false, false, true),
/**
* The {@code TSRG v2} mapping format, as specified here.
*/
- TSRG_2_FILE("TSRG2 file", "tsrg", true, true, false, true, false, false),
+ TSRG_2_FILE("TSRG2 file", "tsrg", true, true, false, true, false, true),
/**
* ProGuard's mapping format, as specified here.
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java
new file mode 100644
index 00000000..a434d4cf
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/CsrgFileWriter.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2023 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.mappingio.format.srg;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import org.jetbrains.annotations.Nullable;
+
+import net.fabricmc.mappingio.MappedElementKind;
+import net.fabricmc.mappingio.MappingFlag;
+import net.fabricmc.mappingio.MappingWriter;
+import net.fabricmc.mappingio.format.MappingFormat;
+
+/**
+ * {@linkplain MappingFormat#CSRG_FILE CSRG file} writer.
+ */
+public final class CsrgFileWriter implements MappingWriter {
+ public CsrgFileWriter(Writer writer) {
+ this.writer = writer;
+ }
+
+ @Override
+ public void close() throws IOException {
+ writer.close();
+ }
+
+ @Override
+ public Set getFlags() {
+ return flags;
+ }
+
+ @Override
+ public void visitNamespaces(String srcNamespace, List dstNamespaces) throws IOException {
+ // not supported, skip
+ }
+
+ @Override
+ public boolean visitClass(String srcName) throws IOException {
+ classSrcName = srcName;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitField(String srcName, @Nullable String srcDesc) throws IOException {
+ memberSrcName = srcName;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException {
+ memberSrcName = srcName;
+ methodSrcDesc = srcDesc;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException {
+ return false; // not supported, skip
+ }
+
+ @Override
+ public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName) throws IOException {
+ return false; // not supported, skip
+ }
+
+ @Override
+ public void visitDstName(MappedElementKind targetKind, int namespace, String name) {
+ if (namespace != 0) return;
+
+ dstName = name;
+ }
+
+ @Override
+ public boolean visitElementContent(MappedElementKind targetKind) throws IOException {
+ if (dstName == null) return false;
+
+ write(classSrcName);
+
+ if (targetKind != MappedElementKind.CLASS) {
+ writeSpace();
+ write(memberSrcName);
+
+ if (targetKind == MappedElementKind.METHOD) {
+ writeSpace();
+ write(methodSrcDesc);
+ }
+
+ memberSrcName = methodSrcDesc = null;
+ }
+
+ writeSpace();
+ write(dstName);
+ writeLn();
+
+ dstName = null;
+
+ return targetKind == MappedElementKind.CLASS; // only members are supported, skip anything but class contents
+ }
+
+ @Override
+ public void visitComment(MappedElementKind targetKind, String comment) throws IOException {
+ // not supported, skip
+ }
+
+ private void write(String str) throws IOException {
+ writer.write(str);
+ }
+
+ private void writeSpace() throws IOException {
+ writer.write(' ');
+ }
+
+ private void writeLn() throws IOException {
+ writer.write('\n');
+ }
+
+ private static final Set flags = EnumSet.of(MappingFlag.NEEDS_SRC_METHOD_DESC);
+
+ private final Writer writer;
+ private String classSrcName;
+ private String memberSrcName;
+ private String methodSrcDesc;
+ private String dstName;
+}
diff --git a/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileWriter.java b/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileWriter.java
new file mode 100644
index 00000000..e1726e73
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/format/srg/TsrgFileWriter.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2023 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.mappingio.format.srg;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import org.jetbrains.annotations.Nullable;
+
+import net.fabricmc.mappingio.MappedElementKind;
+import net.fabricmc.mappingio.MappingFlag;
+import net.fabricmc.mappingio.MappingWriter;
+import net.fabricmc.mappingio.format.MappingFormat;
+
+/**
+ * {@linkplain MappingFormat#TSRG_FILE TSRG file} and
+ * {@linkplain MappingFormat#TSRG_2_FILE TSRG2 file} writer.
+ */
+public final class TsrgFileWriter implements MappingWriter {
+ public TsrgFileWriter(Writer writer, boolean tsrg2) {
+ this.writer = writer;
+ this.tsrg2 = tsrg2;
+ }
+
+ @Override
+ public void close() throws IOException {
+ writer.close();
+ }
+
+ @Override
+ public Set getFlags() {
+ return tsrg2 ? tsrg2Flags : tsrgFlags;
+ }
+
+ @Override
+ public void visitNamespaces(String srcNamespace, List dstNamespaces) throws IOException {
+ dstNames = new String[dstNamespaces.size()];
+
+ if (tsrg2) {
+ write("tsrg2 ");
+ write(srcNamespace);
+
+ for (String dstNamespace : dstNamespaces) {
+ writeSpace();
+ write(dstNamespace);
+ }
+
+ writeLn();
+ }
+ }
+
+ @Override
+ public void visitMetadata(String key, @Nullable String value) throws IOException {
+ // TODO: Support the static method marker once https://github.com/FabricMC/mapping-io/pull/41 is merged
+ }
+
+ @Override
+ public boolean visitClass(String srcName) throws IOException {
+ this.srcName = srcName;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitField(String srcName, @Nullable String srcDesc) throws IOException {
+ this.srcName = srcName;
+ this.srcDesc = srcDesc;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException {
+ this.srcName = srcName;
+ this.srcDesc = srcDesc;
+
+ return true;
+ }
+
+ @Override
+ public boolean visitMethodArg(int argPosition, int lvIndex, @Nullable String srcName) throws IOException {
+ if (tsrg2) {
+ this.srcName = srcName;
+ this.lvIndex = lvIndex;
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName) throws IOException {
+ return false; // not supported, skip
+ }
+
+ @Override
+ public void visitDstName(MappedElementKind targetKind, int namespace, String name) {
+ if (!tsrg2 && namespace != 0) return;
+
+ dstNames[namespace] = name;
+ }
+
+ @Override
+ public boolean visitElementContent(MappedElementKind targetKind) throws IOException {
+ switch (targetKind) {
+ case CLASS:
+ break;
+ case FIELD:
+ case METHOD:
+ writeTab();
+ break;
+ case METHOD_ARG:
+ assert tsrg2;
+ writeTab();
+ writeTab();
+ write(Integer.toString(lvIndex));
+ writeSpace();
+ case METHOD_VAR:
+ assert tsrg2;
+ break;
+ }
+
+ write(srcName);
+
+ if (targetKind == MappedElementKind.METHOD
+ || (targetKind == MappedElementKind.FIELD && tsrg2)) {
+ writeSpace();
+ write(srcDesc);
+ }
+
+ int dstNsCount = tsrg2 ? dstNames.length : 1;
+
+ for (int i = 0; i < dstNsCount; i++) {
+ String dstName = dstNames[i];
+ writeSpace();
+ write(dstName != null ? dstName : srcName);
+ }
+
+ writeLn();
+
+ srcName = srcDesc = null;
+ Arrays.fill(dstNames, null);
+ lvIndex = -1;
+
+ return targetKind == MappedElementKind.CLASS
+ || (tsrg2 && targetKind == MappedElementKind.METHOD);
+ }
+
+ @Override
+ public void visitComment(MappedElementKind targetKind, String comment) throws IOException {
+ // not supported, skip
+ }
+
+ private void write(String str) throws IOException {
+ writer.write(str);
+ }
+
+ private void writeTab() throws IOException {
+ writer.write('\t');
+ }
+
+ private void writeSpace() throws IOException {
+ writer.write(' ');
+ }
+
+ private void writeLn() throws IOException {
+ writer.write('\n');
+ }
+
+ private static final Set tsrgFlags = EnumSet.of(MappingFlag.NEEDS_ELEMENT_UNIQUENESS, MappingFlag.NEEDS_SRC_METHOD_DESC);
+ private static final Set tsrg2Flags;
+
+ static {
+ tsrg2Flags = EnumSet.copyOf(tsrgFlags);
+ tsrg2Flags.add(MappingFlag.NEEDS_SRC_FIELD_DESC);
+ }
+
+ private final Writer writer;
+ private final boolean tsrg2;
+ private String srcName;
+ private String srcDesc;
+ private String[] dstNames;
+ private int lvIndex = -1;
+}
diff --git a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
index dc8083f6..425101c6 100644
--- a/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
+++ b/src/test/java/net/fabricmc/mappingio/write/WriteTest.java
@@ -73,6 +73,21 @@ public void xsrgFile() throws Exception {
check(MappingFormat.XSRG_FILE);
}
+ @Test
+ public void csrgFile() throws Exception {
+ check(MappingFormat.CSRG_FILE);
+ }
+
+ @Test
+ public void tsrgFile() throws Exception {
+ check(MappingFormat.TSRG_FILE);
+ }
+
+ @Test
+ public void tsrg2File() throws Exception {
+ check(MappingFormat.TSRG_2_FILE);
+ }
+
@Test
public void proguardFile() throws Exception {
check(MappingFormat.PROGUARD_FILE);