Skip to content

Commit

Permalink
Add int[] UUID support, array/compound set support, --convert-nbt swi…
Browse files Browse the repository at this point in the history
…tch (fixes #1)
  • Loading branch information
unascribed committed Jan 17, 2023
1 parent 30e9525 commit dceb309
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 68 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ java -jar /opt/unbted/unbted-1.2.jar "$@"

* ANSI-colorized output for more distinctive and easier to skim output
* Can convert NBT files to well-formed JSON for processing by anything that can parse JSON
* As of 1.1, a special NBT JSON format is supported that can be roundtripped. Want to edit an NBT file with jq? Now you can.
* A special NBT JSON format is supported that can be roundtripped. Want to edit an NBT file with jq? Now you can.
* Support for "inferred" types when printing NBT trees in its default format. This includes:
* **JSON** - JSON objects such as `generatorOptions` in level.dat will be colorized, indented, and split into multiple lines for easier reading
* **UUID** - Pairs of long NBT tags with names ending in `Most` and `Least` will be printed as a UUID
* The `set` command also supports UUIDs, which makes working with them easy
* **Old-style UUID** - Pairs of long NBT tags with names ending in `Most` and `Least` will be printed as a UUID
* **New-style UUID** - Int arrays of length 4 will be decoded into UUIDs
* The `set` command supports both forms of UUIDs, which makes working with them easy
* **Forge registries** - Lists containing compounds with only two children, `K` and `V`, will be printed in a condensed format for easier skimming, and sorted by their value
* **Booleans** - unbted will try to guess whether or not an NBT byte is a boolean, and if it thinks it is, will print 0 as false and 1 as true.
* Support for all NBT tags, including int and long arrays
Expand All @@ -36,11 +37,11 @@ java -jar /opt/unbted/unbted-1.2.jar "$@"
* Compression autodetection
* Written in Java and compiled to a native statically linked executable
* Support for little-endian legacy Pocket Edition NBT files
* SNBT (i.e. command block format) support for `set --compound`

## Planned features

* Anvil file support
* Mojangson (i.e. command block format) support for `set --compound`
* Scripting

## Building
Expand Down
73 changes: 53 additions & 20 deletions src/main/java/com/unascribed/nbted/CommandProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@

package com.unascribed.nbted;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.text.DecimalFormat;
Expand Down Expand Up @@ -66,13 +68,16 @@

import io.github.steveice10.opennbt.NBTIO;
import io.github.steveice10.opennbt.NBTRegistry;
import io.github.steveice10.opennbt.SNBTIO.StringifiedNBTReader;
import io.github.steveice10.opennbt.tag.NBTCompound;
import io.github.steveice10.opennbt.tag.NBTIndexed;
import io.github.steveice10.opennbt.tag.NBTList;
import io.github.steveice10.opennbt.tag.NBTParent;
import io.github.steveice10.opennbt.tag.NBTString;
import io.github.steveice10.opennbt.tag.NBTTag;
import io.github.steveice10.opennbt.tag.array.NBTByteArray;
import io.github.steveice10.opennbt.tag.array.NBTIntArray;
import io.github.steveice10.opennbt.tag.array.NBTLongArray;
import io.github.steveice10.opennbt.tag.array.support.NBTArrayFake;
import io.github.steveice10.opennbt.tag.number.NBTByte;
import io.github.steveice10.opennbt.tag.number.NBTDouble;
Expand Down Expand Up @@ -297,17 +302,8 @@ public CommandProcessor(NBTTag _root, TagPrinter _printer, FileInfo _fileInfo) {
// no action can possibly be more destructive than deleting the root, so break
break;
} else {
if (parent instanceof NBTCompound) {
NBTCompound ct = (NBTCompound)parent;
if (ct.contains(t.getName())) {
ct.remove(t.getName());
dirty = true;
} else {
throw new ConsistencyError("Tried to delete tag from parent, but it's not in its parent!?");
}
} else {
throw new CommandException(VALUE_NYI, "Tried to delete tag with non-compound parent (NYI)");
}
t.removeFromParent();
dirty = true;
}
int idx = contextParents.indexOf(t);
if (idx != -1) {
Expand Down Expand Up @@ -566,7 +562,8 @@ public CommandProcessor(NBTTag _root, TagPrinter _printer, FileInfo _fileInfo) {
for (String s : NBTRegistry.allByTypeName().keySet()) {
exclusive.add(parser.accepts(s, "equivalent to --type="+s).availableUnless("type"));
}
exclusive.add(parser.accepts("uuid", "equivalent to --type=uuid").availableUnless("type"));
exclusive.add(parser.accepts("olduuid", "equivalent to --type=olduuid").availableUnless("type"));
exclusive.add(parser.accepts("newuuid", "equivalent to --type=newuuid").availableUnless("type"));
parser.mutuallyExclusive(exclusive.toArray(new OptionSpecBuilder[exclusive.size()]));
})
.action((alias, set, args) -> {
Expand All @@ -577,18 +574,25 @@ public CommandProcessor(NBTTag _root, TagPrinter _printer, FileInfo _fileInfo) {
if ("add".equals(alias)) shift = true;
String path = args.get(0);
boolean uuid = false;
boolean newuuid = false;
Class<? extends NBTTag> explicitType = null;
if (set.has("type")) {
String typeStr = set.valueOf("type").toString();
if ("uuid".equals(typeStr)) {
if ("olduuid".equals(typeStr)) {
uuid = true;
} else if ("newuuid".equals(typeStr)) {
uuid = true;
newuuid = true;
} else {
explicitType = NBTRegistry.classByTypeName(typeStr);
if (explicitType == null) throw new CommandException(VALUE_BAD_USAGE, "Unrecognized type "+set.valueOf("type"));
}
} else {
if (set.has("uuid")) {
if (set.has("olduuid")) {
uuid = true;
} else if (set.has("newuuid")) {
uuid = true;
newuuid = true;
} else {
for (Map.Entry<String, Class<? extends NBTTag>> en : NBTRegistry.allByTypeName().entrySet()) {
if (set.has(en.getKey())) {
Expand All @@ -600,13 +604,13 @@ public CommandProcessor(NBTTag _root, TagPrinter _printer, FileInfo _fileInfo) {
}
String str = SPACE_JOINER.join(args.subList(1, args.size()));
if (root == null) {
if (uuid) {
throw new CommandException(VALUE_CMDSPECIFIC_4, "UUIDs are actually two tags, and cannot be the root of a file");
if (uuid && !newuuid) {
throw new CommandException(VALUE_CMDSPECIFIC_4, "Old-style UUIDs are two tags, and cannot be the root of a file");
}
if (explicitType == null) {
if (explicitType == null && !uuid) {
throw new CommandException(VALUE_CMDSPECIFIC_3, "An explicit type must be specified to create new tags");
}
NBTTag tag = NBTRegistry.createInstance(explicitType, path);
NBTTag tag = uuid ? new NBTIntArray(path, UUIDs.toIntArray(UUID.fromString(str))) : NBTRegistry.createInstance(explicitType, path);
try {
parseAndSet(tag, str);
} catch (NumberFormatException e) {
Expand Down Expand Up @@ -638,8 +642,15 @@ public CommandProcessor(NBTTag _root, TagPrinter _printer, FileInfo _fileInfo) {
} catch (IllegalArgumentException e) {
throw new CommandException(VALUE_BAD_USAGE, str+" is not a valid UUID");
}
commands.get("set").execute("set", "--type=long", "--", pathNoTrailingSlashes+"Most", Long.toString(u.getMostSignificantBits()));
commands.get("set").execute("set", "--type=long", "--", pathNoTrailingSlashes+"Least", Long.toString(u.getLeastSignificantBits()));
if (newuuid) {
int[] arr = UUIDs.toIntArray(u);
commands.get("set").execute("set", "--type=int-array", "--", pathNoTrailingSlashes,
Integer.toString(arr[0]), Integer.toString(arr[1]),
Integer.toString(arr[2]), Integer.toString(arr[3]));
} else {
commands.get("set").execute("set", "--type=long", "--", pathNoTrailingSlashes+"Most", Long.toString(u.getMostSignificantBits()));
commands.get("set").execute("set", "--type=long", "--", pathNoTrailingSlashes+"Least", Long.toString(u.getLeastSignificantBits()));
}
return;
}
if (p.leaf != null && !(p.immediateParent instanceof NBTIndexed)) {
Expand Down Expand Up @@ -771,6 +782,28 @@ private void parseAndSet(NBTTag tag, String str) {
} catch (IllegalArgumentException e) {
throw new CommandException(VALUE_BAD_USAGE, "Invalid base64");
}
} else if (tag instanceof NBTIntArray) {
try {
((NBTIntArray)tag).setValue(Arrays.stream(str.split(" "))
.mapToInt(Integer::parseInt)
.toArray());
} catch (IllegalArgumentException e) {
throw new CommandException(VALUE_BAD_USAGE, "Invalid number "+e.getMessage());
}
} else if (tag instanceof NBTLongArray) {
try {
((NBTLongArray)tag).setValue(Arrays.stream(str.split(" "))
.mapToLong(Long::parseLong)
.toArray());
} catch (IllegalArgumentException e) {
throw new CommandException(VALUE_BAD_USAGE, "Invalid number "+e.getMessage());
}
} else if (tag instanceof NBTCompound && !str.trim().isEmpty()) {
try {
tag.destringify(new StringifiedNBTReader(new ByteArrayInputStream(str.trim().getBytes(Charsets.UTF_8))));
} catch (IOException e) {
throw new CommandException(VALUE_BAD_USAGE, "SNBT parsing failed: "+e.getMessage());
}
} else if (!str.trim().isEmpty()) {
throw new CommandException(VALUE_BAD_USAGE, "Tags of type "+NBTRegistry.typeNameFromClass(tag.getClass())+" cannot be created with a value");
} else if (tag instanceof NBTParent) {
Expand Down
Loading

0 comments on commit dceb309

Please # to comment.