Skip to content

Instantly share code, notes, and snippets.

@BenjaminUrquhart
Last active October 17, 2024 06:41
Show Gist options
  • Save BenjaminUrquhart/1de2d69c48c92dca3045c35458a74d98 to your computer and use it in GitHub Desktop.
Save BenjaminUrquhart/1de2d69c48c92dca3045c35458a74d98 to your computer and use it in GitHub Desktop.
Java utility class to properly read/write decompressed RPGMaker MV/MZ save files.
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.json.JSONArray;
import org.json.JSONObject;
public class JsonEx {
// Listen this is not how you use Java but
// I don't care.
record Reference(Object base, Object key, int id) {}
public static Object deserialize(String json) {
return deserialize(new JSONObject(json));
}
// This modifies the input field btw I'm too lazy to change it
public static Object deserialize(JSONObject data) {
Map<Integer, Object> objects = new HashMap<>();
Set<Reference> references = new HashSet<>();
// It's possible that this is a JSONArray and not a JSONObject.
// Given the intended use case for this little project is RPGMaker
// save file parsing, that shouldn't happen, but who knows?
Object result = deserializeInternal(data, objects, references, "root");
Object obj;
for(Reference ref : references) {
if(objects.containsKey(ref.id)) {
obj = objects.get(ref.id);
}
else {
System.err.println("Failed to find reference " + ref.id);
obj = null;
}
if(ref.base instanceof JSONArray arr) {
arr.put((int)ref.key, obj);
}
else if(ref.base instanceof JSONObject o) {
o.put((String)ref.key, obj);
}
else {
throw new IllegalArgumentException(String.format("Invalid type %s for reference %d", ref.base.getClass().getName(), ref.id));
}
}
return result;
}
private static Object deserializeInternal(JSONObject data, Map<Integer, Object> objects, Set<Reference> references, String path) {
if(data.has("@r")) {
throw new IllegalStateException(String.format("Cannot deserialize reference at %s", path));
}
boolean hasType = data.has("@");
if(hasType) {
System.out.printf("%s is type %s\n", path, data.get("@"));
//data.remove("@");
}
if(data.has("@c")) {
if(data.has("@a")) {
if(hasType) {
throw new IllegalStateException(String.format("Array entry at %s contains a type entry ('@') which is not allowed", path));
}
if(data.keySet().size() > 2) {
throw new IllegalStateException(String.format("Array entry at %s contains extra keys (expected [@a, @c], got %s)", path, data.keySet()));
}
JSONArray arr = data.getJSONArray("@a");
for(int i = 0; i < arr.length(); i++) {
if(arr.get(i) instanceof JSONObject element) {
if(element.has("@r")) {
references.add(new Reference(arr, i, element.getInt("@r")));
}
else {
arr.put(i, deserializeInternal(element, objects, references, path + "[" + i + "]"));
}
}
}
objects.put(data.getInt("@c"), arr);
data.remove("@c");
data.remove("@a");
return arr;
}
else {
objects.put(data.getInt("@c"), data);
data.remove("@c");
}
}
for(String key : data.keySet()) {
if(data.get(key) instanceof JSONObject obj) {
if(obj.has("@r")) {
references.add(new Reference(data, key, obj.getInt("@r")));
}
else {
data.put(key, deserializeInternal(obj, objects, references, path + "." + key));
}
}
}
return data;
}
public static JSONObject serialize(JSONObject data) {
Map<Object, Integer> ids = new HashMap<>();
AtomicInteger id = new AtomicInteger(1);
return serializeInternal(data, ids, id);
}
public static JSONObject serialize(JSONArray data) {
Map<Object, Integer> ids = new HashMap<>();
AtomicInteger id = new AtomicInteger(1);
return serializeInternal(data, ids, id);
}
private static JSONObject serializeInternal(JSONObject data, Map<Object, Integer> ids, AtomicInteger id) {
if(ids.containsKey(data)) {
return new JSONObject().put("@r", ids.get(data));
}
if(data.has("@c")) {
throw new IllegalStateException("Illegal key: @c");
}
if(data.has("@a")) {
throw new IllegalStateException("Illegal key: @a");
}
int c = id.getAndIncrement();
JSONObject out = new JSONObject().put("@c", c);
ids.put(data, c);
for(String key : data.keySet()) {
if(data.get(key) instanceof JSONObject obj) {
out.put(key, serializeInternal(obj, ids, id));
}
else if(data.get(key) instanceof JSONArray arr) {
out.put(key, serializeInternal(arr, ids, id));
}
else {
out.put(key, data.get(key));
}
}
return out;
}
private static JSONObject serializeInternal(JSONArray data, Map<Object, Integer> ids, AtomicInteger id) {
JSONObject out = new JSONObject();
if(ids.containsKey(data)) {
return out.put("@r", ids.get(data));
}
JSONArray array = new JSONArray(data.length());
int c = id.getAndIncrement();
out.put("@a", array).put("@c", c);
ids.put(data, c);
for(int i = 0; i < data.length(); i++) {
if(data.get(i) instanceof JSONObject obj) {
array.put(i, serializeInternal(obj, ids, id));
}
else if(data.get(i) instanceof JSONArray arr) {
array.put(i, serializeInternal(arr, ids, id));
}
else {
array.put(i, data.get(i));
}
}
return out;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment