Skip to content

Commit

Permalink
Add JOBF reader and writer
Browse files Browse the repository at this point in the history
  • Loading branch information
NebelNidas committed Dec 8, 2023
1 parent 7e7e77e commit 72332db
Show file tree
Hide file tree
Showing 15 changed files with 376 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 JOBF reader and writer

## [0.5.1] - 2023-11-30
- Improved documentation
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/net/fabricmc/mappingio/MappingReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.format.enigma.EnigmaDirReader;
import net.fabricmc.mappingio.format.enigma.EnigmaFileReader;
import net.fabricmc.mappingio.format.jobf.JobfFileReader;
import net.fabricmc.mappingio.format.proguard.ProGuardFileReader;
import net.fabricmc.mappingio.format.srg.SrgFileReader;
import net.fabricmc.mappingio.format.srg.TsrgFileReader;
Expand Down Expand Up @@ -89,7 +90,13 @@ public static MappingFormat detectFormat(Reader reader) throws IOException {

String headerStr = String.valueOf(buffer, 0, pos);

if (headerStr.contains(" -> ")) {
if ((headerStr.startsWith("p ")
|| headerStr.startsWith("c ")
|| headerStr.startsWith("f ")
|| headerStr.startsWith("m "))
&& headerStr.contains(" = ")) {
return MappingFormat.JOBF_FILE;
} else if (headerStr.contains(" -> ")) {
return MappingFormat.PROGUARD_FILE;
} else if (headerStr.contains("\n\t")) {
return MappingFormat.TSRG_FILE;
Expand Down Expand Up @@ -269,6 +276,9 @@ public static void read(Reader reader, MappingFormat format, MappingVisitor visi
case PROGUARD_FILE:
ProGuardFileReader.read(reader, visitor);
break;
case JOBF_FILE:
JobfFileReader.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 @@ -27,6 +27,7 @@
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.format.enigma.EnigmaDirWriter;
import net.fabricmc.mappingio.format.enigma.EnigmaFileWriter;
import net.fabricmc.mappingio.format.jobf.JobfFileWriter;
import net.fabricmc.mappingio.format.proguard.ProGuardFileWriter;
import net.fabricmc.mappingio.format.srg.SrgFileWriter;
import net.fabricmc.mappingio.format.tiny.Tiny1FileWriter;
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 JOBF_FILE: return new JobfFileWriter(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 @@ -102,6 +102,15 @@
* <td>-</td>
* <td>-</td>
* </tr>
* <tr>
* <td>JOBF</td>
* <td>-</td>
* <td>src</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 @@ -156,7 +165,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);
PROGUARD_FILE("ProGuard file", "txt", false, true, false, false, false),

/**
* The {@code JOBF} mapping format, as specified <a href="https://github.com/skylot/jadx/blob/2d5c0fda4a0c5d16207a5f48edb72e6efa7d5bbd/jadx-core/src/main/java/jadx/core/deobf/DeobfPresets.java">here</a>.
*/
JOBF_FILE("JOBF file", "jobf", false, true, false, false, false);

MappingFormat(String name, @Nullable String fileExt,
boolean hasNamespaces, boolean hasFieldDescriptors,
Expand Down
148 changes: 148 additions & 0 deletions src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* 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.jobf;

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#JOBF_FILE JOBF file} reader.
*/
public class JobfFileReader {
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 (;;) {
boolean visitHeader = visitor.visitHeader();

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

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

do {
boolean isField;

if (reader.nextCol("c")) { // class: c <name-a> = <name-b>
String srcName = reader.nextCol();
if (srcName == null || srcName.isEmpty()) throw new IOException("missing class-name-a in line "+reader.getLineNumber());

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

if (visitLastClass) {
readSeparator(reader);

String dstName = reader.nextCol();
if (dstName == null || dstName.isEmpty()) throw new IOException("missing class-name-b in line "+reader.getLineNumber());

visitor.visitDstName(MappedElementKind.CLASS, 0, dstName);
visitLastClass = visitor.visitElementContent(MappedElementKind.CLASS);
}
}
} else if ((isField = reader.nextCol("f")) || reader.nextCol("m")) {
// field: f <cls-a>.<name-a>:<desc-a> = <name-b>
// method: m <cls-a>.<name-a><desc-a> = <name-b>
String src = reader.nextCol();
if (src == null || src.isEmpty()) throw new IOException("missing class/name/desc a in line "+reader.getLineNumber());

int nameSepPos = src.lastIndexOf('.');
if (nameSepPos <= 0 || nameSepPos == src.length() - 1) throw new IOException("invalid class/name/desc a in line "+reader.getLineNumber());

int descSepPos = src.lastIndexOf(isField ? ':' : '(');
if (descSepPos <= 0 || descSepPos == src.length() - 1) throw new IOException("invalid name/desc a in line "+reader.getLineNumber());

readSeparator(reader);

String dstName = reader.nextCol();
if (dstName == null || dstName.isEmpty()) throw new IOException("missing name-b in line "+reader.getLineNumber());

String srcOwner = src.substring(0, nameSepPos);

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

if (visitLastClass) {
visitLastClass = visitor.visitElementContent(MappedElementKind.CLASS);
}
}

if (visitLastClass) {
String srcName = src.substring(nameSepPos + 1, descSepPos);
String srcDesc = src.substring(descSepPos + (isField ? 1 : 0));

if (isField && visitor.visitField(srcName, srcDesc)
|| !isField && visitor.visitMethod(srcName, srcDesc)) {
MappedElementKind kind = isField ? MappedElementKind.FIELD : MappedElementKind.METHOD;
visitor.visitDstName(kind, 0, dstName);
visitor.visitElementContent(kind);
}
}
} else if (reader.nextCol("p")) { // package: p <name-a> = <name-b>
// TODO
}
} while (reader.nextLine(0));
}

if (visitor.visitEnd()) break;

reader.reset();
}

if (parentVisitor != null) {
((MappingTree) visitor).accept(parentVisitor);
}
}

private static void readSeparator(ColumnFileReader reader) throws IOException {
if (!reader.nextCol("=")) {
throw new IOException("missing separator in line "+reader.getLineNumber()+" (expected \" = \")");
}
}
}
150 changes: 150 additions & 0 deletions src/main/java/net/fabricmc/mappingio/format/jobf/JobfFileWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* 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.jobf;

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#JOBF_FILE JOBF file} writer.
*/
public final class JobfFileWriter implements MappingWriter {
public JobfFileWriter(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, @Nullable String srcDesc) throws IOException {
memberSrcName = srcName;
memberSrcDesc = srcDesc;

return true;
}

@Override
public boolean visitMethod(String srcName, @Nullable String srcDesc) throws IOException {
memberSrcName = srcName;
memberSrcDesc = 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 {
boolean isClass = targetKind == MappedElementKind.CLASS;
boolean isField = false;

if (dstName == null) return isClass;

if ((isClass)) {
write("c ");
} else if ((isField = targetKind == MappedElementKind.FIELD)
|| targetKind == MappedElementKind.METHOD) {
if (memberSrcDesc == null) return false;
write(isField ? "f " : "m ");
} else {
throw new IllegalStateException("unexpected invocation for "+targetKind);
}

write(classSrcName);

if (!isClass) {
write(".");
write(memberSrcName);

if (isField) write(":");
write(memberSrcDesc);
}

write(" = ");
write(dstName);

writeLn();

dstName = null;
return isClass; // 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 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 JOBF_FILE:
return "jobf.jobf";
default:
return null;
}
Expand Down
Loading

0 comments on commit 72332db

Please sign in to comment.