Skip to content

Commit

Permalink
Add Recaf Simple reader and writer (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
NebelNidas authored Mar 7, 2024
1 parent c772a56 commit 1cd9184
Show file tree
Hide file tree
Showing 14 changed files with 345 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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 Recaf Simple reader and writer
- Added `MappingFormat#hasWriter` boolean

## [0.5.1] - 2023-11-30
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/net/fabricmc/mappingio/MappingReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import net.fabricmc.mappingio.format.enigma.EnigmaDirReader;
import net.fabricmc.mappingio.format.enigma.EnigmaFileReader;
import net.fabricmc.mappingio.format.proguard.ProGuardFileReader;
import net.fabricmc.mappingio.format.simple.RecafSimpleFileReader;
import net.fabricmc.mappingio.format.srg.SrgFileReader;
import net.fabricmc.mappingio.format.srg.TsrgFileReader;
import net.fabricmc.mappingio.format.tiny.Tiny1FileReader;
Expand Down Expand Up @@ -95,7 +96,7 @@ public static MappingFormat detectFormat(Reader reader) throws IOException {
return MappingFormat.TSRG_FILE;
}

// TODO: CSRG
// TODO: CSRG, Recaf Simple

return null; // unknown format or corrupted
}
Expand Down Expand Up @@ -269,6 +270,9 @@ public static void read(Reader reader, MappingFormat format, MappingVisitor visi
case PROGUARD_FILE:
ProGuardFileReader.read(reader, visitor);
break;
case RECAF_SIMPLE_FILE:
RecafSimpleFileReader.read(reader, visitor);
break;
default:
throw new IllegalStateException();
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/net/fabricmc/mappingio/MappingWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import net.fabricmc.mappingio.format.enigma.EnigmaDirWriter;
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.SrgFileWriter;
import net.fabricmc.mappingio.format.tiny.Tiny1FileWriter;
import net.fabricmc.mappingio.format.tiny.Tiny2FileWriter;
Expand Down Expand Up @@ -56,6 +57,7 @@ static MappingWriter create(Writer writer, MappingFormat format) throws IOExcept
case SRG_FILE: return new SrgFileWriter(writer, false);
case XSRG_FILE: return new SrgFileWriter(writer, true);
case PROGUARD_FILE: return new ProGuardFileWriter(writer);
case RECAF_SIMPLE_FILE: return new RecafSimpleFileWriter(writer);
default: return null;
}
}
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/net/fabricmc/mappingio/format/MappingFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@
* <td>-</td>
* <td>-</td>
* </tr>
* <tr>
* <td>Recaf Simple</td>
* <td>-</td>
* <td>src & dst</td>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* </tr>
* </table>
*/
// Format order is determined by importance to Fabric tooling, format family and release order therein.
Expand Down Expand Up @@ -158,7 +167,12 @@ public enum MappingFormat {
/**
* ProGuard's mapping format, as specified <a href="https://www.guardsquare.com/manual/tools/retrace">here</a>.
*/
PROGUARD_FILE("ProGuard file", "txt", false, true, false, false, false, true);
PROGUARD_FILE("ProGuard file", "txt", false, true, false, false, false, true),

/**
* Recaf's {@code Simple} mapping format, as specified <a href="https://github.com/Col-E/Recaf/blob/e9765d4e02991a9dd48e67c9572a063c14552e7c/src/main/java/me/coley/recaf/mapping/SimpleMappings.java#L14-L23">here</a>.
*/
RECAF_SIMPLE_FILE("Recaf Simple file", "txt", false, true, false, false, false, true);

MappingFormat(String name, @Nullable String fileExt,
boolean hasNamespaces, boolean hasFieldDescriptors,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* 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.simple;

import java.io.IOException;
import java.io.Reader;
import java.util.Collections;
import java.util.Set;

import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingUtil;
import net.fabricmc.mappingio.MappingVisitor;
import net.fabricmc.mappingio.format.ColumnFileReader;
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;

/**
* {@linkplain MappingFormat#RECAF_SIMPLE Recaf Simple file} reader.
*/
public final class RecafSimpleFileReader {
private RecafSimpleFileReader() {
}

public static void read(Reader reader, MappingVisitor visitor) throws IOException {
read(reader, MappingUtil.NS_SOURCE_FALLBACK, MappingUtil.NS_TARGET_FALLBACK, visitor);
}

public static void read(Reader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
read(new ColumnFileReader(reader, ' '), sourceNs, targetNs, visitor);
}

private static void read(ColumnFileReader reader, String sourceNs, String targetNs, MappingVisitor visitor) throws IOException {
Set<MappingFlag> flags = visitor.getFlags();
MappingVisitor parentVisitor = null;

if (flags.contains(MappingFlag.NEEDS_ELEMENT_UNIQUENESS)) {
parentVisitor = visitor;
visitor = new MemoryMappingTree();
} else if (flags.contains(MappingFlag.NEEDS_MULTIPLE_PASSES)) {
reader.mark();
}

for (;;) {
if (visitor.visitHeader()) {
visitor.visitNamespaces(sourceNs, Collections.singletonList(targetNs));
}

if (visitor.visitContent()) {
String line;
String lastClass = null;
boolean visitClass = false;

do {
line = reader.nextCols(true);

// Skip comments and empty lines
if (line == null || line.trim().isEmpty() || line.trim().startsWith("#")) continue;

String[] parts = line.split(" ");
int dotPos = parts[0].lastIndexOf('.');
String clsSrcName;
String clsDstName = null;
String memberSrcName = null;
String memberSrcDesc = null;
String memberDstName = null;
boolean isMethod = false;

if (dotPos < 0) { // class
clsSrcName = parts[0];
clsDstName = parts[1];
} else { // member
clsSrcName = parts[0].substring(0, dotPos);
String memberIdentifier = parts[0].substring(dotPos + 1);
memberDstName = parts[1];

if (parts.length >= 3) { // field with descriptor
memberSrcName = memberIdentifier;
memberSrcDesc = parts[1];
memberDstName = parts[2];
} else if (parts.length == 2) { // field without descriptor or method
int mthDescPos = memberIdentifier.lastIndexOf("(");

if (mthDescPos < 0) { // field
memberSrcName = memberIdentifier;
} else { // method
isMethod = true;
memberSrcName = memberIdentifier.substring(0, mthDescPos);
memberSrcDesc = memberIdentifier.substring(mthDescPos);
}
} else {
throw new IOException("Invalid Recaf Simple line "+reader.getLineNumber()+": Insufficient column count!");
}
}

if (!clsSrcName.equals(lastClass)) {
visitClass = visitor.visitClass(clsSrcName);
lastClass = clsSrcName;

if (visitClass) {
if (clsDstName != null) visitor.visitDstName(MappedElementKind.CLASS, 0, clsDstName);
visitClass = visitor.visitElementContent(MappedElementKind.CLASS);
}
}

if (visitClass && memberSrcName != null) {
if (!isMethod && visitor.visitField(memberSrcName, memberSrcDesc)) {
visitor.visitDstName(MappedElementKind.FIELD, 0, memberDstName);
} else if (isMethod && visitor.visitMethod(memberSrcName, memberSrcDesc)) {
visitor.visitDstName(MappedElementKind.METHOD, 0, memberDstName);
}
}
} while (reader.nextLine(0));
}

if (visitor.visitEnd()) break;
reader.reset();
}

if (parentVisitor != null) {
((MappingTree) visitor).accept(parentVisitor);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* 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.simple;

import java.io.IOException;
import java.io.Writer;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingWriter;
import net.fabricmc.mappingio.format.MappingFormat;

/**
* {@linkplain MappingFormat#RECAF_SIMPLE Recaf Simple file} writer.
*/
public final class RecafSimpleFileWriter implements MappingWriter {
public RecafSimpleFileWriter(Writer writer) {
this.writer = writer;
}

@Override
public void close() throws IOException {
writer.close();
}

@Override
public Set<MappingFlag> getFlags() {
return flags;
}

@Override
public void visitNamespaces(String srcNamespace, List<String> dstNamespaces) throws IOException {
}

@Override
public boolean visitClass(String srcName) throws IOException {
classSrcName = srcName;

return true;
}

@Override
public boolean visitField(String srcName, String srcDesc) throws IOException {
memberSrcName = srcName;
memberSrcDesc = srcDesc;

return true;
}

@Override
public boolean visitMethod(String srcName, String srcDesc) throws IOException {
memberSrcName = srcName;
memberSrcDesc = srcDesc;

return true;
}

@Override
public boolean visitMethodArg(int argPosition, int lvIndex, String srcName) throws IOException {
return false; // not supported, skip
}

@Override
public boolean visitMethodVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, 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 true;
write(classSrcName);

if (targetKind != MappedElementKind.CLASS) {
if (memberSrcName == null) throw new IllegalArgumentException("member source name cannot be null!");
writer.write('.');
write(memberSrcName);

if (memberSrcDesc != null) {
if (targetKind == MappedElementKind.FIELD) writeSpace();
write(memberSrcDesc);
}
}

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 writeLn() throws IOException {
writer.write('\n');
}

private void writeSpace() throws IOException {
writer.write(' ');
}

private static final Set<MappingFlag> flags = EnumSet.of(MappingFlag.NEEDS_SRC_FIELD_DESC, MappingFlag.NEEDS_SRC_METHOD_DESC);

private final Writer writer;
private String classSrcName;
private String memberSrcName;
private String memberSrcDesc;
private String dstName;
}
2 changes: 2 additions & 0 deletions src/test/java/net/fabricmc/mappingio/TestHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public static String getFileName(MappingFormat format) {
return "tsrg2.tsrg";
case PROGUARD_FILE:
return "proguard.txt";
case RECAF_SIMPLE_FILE:
return "recaf-simple.txt";
default:
return null;
}
Expand Down
6 changes: 6 additions & 0 deletions src/test/java/net/fabricmc/mappingio/read/DetectionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ public void proguardFile() throws Exception {
check(format);
}

@Test
public void recafSimpleFile() throws Exception {
MappingFormat format = MappingFormat.RECAF_SIMPLE_FILE;
assertThrows(AssertionFailedError.class, () -> check(format));
}

private void check(MappingFormat format) throws Exception {
Path path = dir.resolve(TestHelper.getFileName(format));
assertEquals(format, MappingReader.detectFormat(path));
Expand Down
Loading

0 comments on commit 1cd9184

Please sign in to comment.