Skip to content

Commit

Permalink
Initial work on new asset format
Browse files Browse the repository at this point in the history
  • Loading branch information
Amrsatrio committed Dec 22, 2020
1 parent ec8bb3e commit 60363b3
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 273 deletions.
7 changes: 4 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins {
id 'application'
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.4.0'
id 'org.jetbrains.kotlin.jvm' version '1.4.20'
}

group 'com.tb24'
Expand Down Expand Up @@ -43,13 +43,14 @@ repositories {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.collection:collection-ktx:1.1.0'
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
implementation 'me.fungames:JFortniteParse:+' // :3.0.2'
implementation 'me.fungames:JFortniteParse:+'
implementation 'org.slf4j:slf4j-api:1.7.30'
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13.1'
}

compileKotlin {
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
6 changes: 1 addition & 5 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
includeBuild('../JFortniteParse') {
dependencySubstitution {
substitute module('me.fungames:JFortniteParse') with project(':')
}
}
includeBuild('../JFortniteParse')
8 changes: 1 addition & 7 deletions src/main/java/com/tb24/blenderumap/AssetUtils.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.tb24.blenderumap

import me.fungames.jfortniteparse.ue4.assets.exports.UExport
import me.fungames.jfortniteparse.ue4.assets.exports.UObject
import me.fungames.jfortniteparse.ue4.assets.objects.FPropertyTag
import me.fungames.jfortniteparse.ue4.objects.core.misc.FGuid
import kotlin.math.max

fun <T> getProp(properties: List<FPropertyTag>, name: String, clazz: Class<T>): T? {
Expand Down Expand Up @@ -31,9 +29,5 @@ fun <T> getProps(properties: List<FPropertyTag>, name: String, clazz: Class<T>):
return out
}

fun <T> UExport.getProp(name: String, clazz: Class<T>) = (this as UObject).getProp(name, clazz)
fun <T> UObject.getProp(name: String, clazz: Class<T>) = getProp(properties, name, clazz)
inline fun <reified T> UExport.get(name: String): T? = getProp(name, T::class.java)
inline fun <reified T> UObject.get(name: String): T? = getProp(name, T::class.java)

fun FGuid?.asString() = if (this == null) null else "%08x%08x%08x%08x".format(part1.toInt(), part2.toInt(), part3.toInt(), part4.toInt())
inline fun <reified T> UObject.get(name: String): T? = getProp(name, T::class.java)
264 changes: 160 additions & 104 deletions src/main/java/com/tb24/blenderumap/JWPSerializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
package com.tb24.blenderumap

import com.google.gson.*
import me.fungames.jfortniteparse.ue4.assets.IoPackage
import me.fungames.jfortniteparse.ue4.assets.IoPackage.*
import me.fungames.jfortniteparse.ue4.assets.PakPackage
import me.fungames.jfortniteparse.ue4.assets.UProperty
import me.fungames.jfortniteparse.ue4.assets.exports.ECurveTableMode
import me.fungames.jfortniteparse.ue4.assets.exports.UCurveTable
import me.fungames.jfortniteparse.ue4.assets.exports.UDataTable
import me.fungames.jfortniteparse.ue4.assets.exports.UExport
import me.fungames.jfortniteparse.ue4.assets.exports.UObject
import me.fungames.jfortniteparse.ue4.assets.objects.*
import me.fungames.jfortniteparse.ue4.objects.core.i18n.FText
Expand All @@ -18,138 +23,174 @@ import me.fungames.jfortniteparse.ue4.objects.uobject.FName
import me.fungames.jfortniteparse.ue4.objects.uobject.FPackageIndex
import me.fungames.jfortniteparse.ue4.objects.uobject.FSoftObjectPath
import me.fungames.jfortniteparse.util.parseHexBinary
import me.fungames.jfortniteparse.util.printHexBinary
import java.lang.reflect.Type
import java.math.BigInteger
import java.util.*

/**
* Provides John Wick Parse JSON data structure for JFortniteParse objects.
* @author amrsatrio
*/
@ExperimentalUnsignedTypes
object JWPSerializer {
@JvmField
var sUseNonstandardFormat = false

@JvmField
val GSON: Gson = GsonBuilder()
.disableHtmlEscaping()
//.setPrettyPrinting()
.serializeNulls()
.registerTypeAdapter(ByteArray::class.java, ByteArraySerializer())
.registerTypeAdapter(UByte::class.java, JsonSerializer<UByte> { src, typeOfSrc, context -> JsonPrimitive(src.toByte()) })
.registerTypeHierarchyAdapter(UExport::class.java, ExportSerializer())
.registerTypeHierarchyAdapter(FPropertyTagType::class.java, JsonSerializer<FPropertyTagType> { src, typeOfSrc, context ->
if (src is FPropertyTagType.DelegateProperty) {
JsonObject().apply {
addProperty("object", src.`object`)
add("name", context.serialize(src.name))
.disableHtmlEscaping()
//.setPrettyPrinting()
.serializeNulls()
.setFieldNamingStrategy {
it.getAnnotation(UProperty::class.java)?.name
?: separateCamelCase(it.name, "_").toLowerCase(Locale.ENGLISH)
}
.registerTypeAdapter(ByteArray::class.java, ByteArraySerializer())
.registerTypeAdapter(UByte::class.java, JsonSerializer<UByte> { src, typeOfSrc, context -> JsonPrimitive(src.toShort()) })
.registerTypeAdapter(UShort::class.java, JsonSerializer<UShort> { src, typeOfSrc, context -> JsonPrimitive(src.toInt()) })
.registerTypeAdapter(UInt::class.java, JsonSerializer<UInt> { src, typeOfSrc, context -> JsonPrimitive(src.toLong()) })
.registerTypeAdapter(ULong::class.java, JsonSerializer<ULong> { src, typeOfSrc, context -> JsonPrimitive(BigInteger(src.toString())) })
.registerTypeHierarchyAdapter(UObject::class.java, ExportSerializer())
.registerTypeHierarchyAdapter(FProperty::class.java, JsonSerializer<FProperty> { src, typeOfSrc, context ->
context.serialize(src.getTagTypeValue())
})
.registerTypeAdapter(FBox::class.java, JsonSerializer<FBox> { src, typeOfSrc, context ->
JsonObject().apply {
add("min", context.serialize(src.min))
add("max", context.serialize(src.max))
add("valid", context.serialize(src.isValid))
}
})
.registerTypeAdapter(FBox2D::class.java, JsonSerializer<FBox2D> { src, typeOfSrc, context ->
JsonObject().apply {
add("min", context.serialize(src.min))
add("max", context.serialize(src.max))
add("valid", context.serialize(src.isValid))
}
})
.registerTypeAdapter(FGameplayTagContainer::class.java, JsonSerializer<FGameplayTagContainer> { src, typeOfSrc, context ->
JsonObject().apply {
add("gameplay_tags", JsonArray().apply { src.gameplayTags.forEach { add(context.serialize(it)) } })
}
})
.registerTypeAdapter(FGuid::class.java, GuidSerializer())
.registerTypeHierarchyAdapter(FName::class.java, JsonSerializer<FName> { src, typeOfSrc, context ->
JsonPrimitive(src.text)
})
.registerTypeAdapter(FPackageIndex::class.java, JsonSerializer<FPackageIndex> { src, typeOfSrc, context ->
if (src.isImport()) {
val pkg = src.owner
if (pkg is PakPackage) {
JsonArray().apply {
var current = pkg.run { src.getResource() }
while (current != null) {
add(current.objectName.text)
current = pkg.run { current!!.outerIndex.getResource() }
}
}
} else {
context.serialize(src.getTagTypeValue())
}
})
.registerTypeAdapter(FBox::class.java, JsonSerializer<FBox> { src, typeOfSrc, context ->
JsonObject().apply {
add("min", context.serialize(src.min))
add("max", context.serialize(src.max))
add("valid", context.serialize(src.isValid))
}
})
.registerTypeAdapter(FBox2D::class.java, JsonSerializer<FBox2D> { src, typeOfSrc, context ->
JsonObject().apply {
add("min", context.serialize(src.min))
add("max", context.serialize(src.max))
add("valid", context.serialize(src.isValid))
when (val resolved = (pkg as IoPackage).resolveObjectIndex(pkg.importMap[src.toImport()])) {
is ResolvedScriptObject -> JsonArray().apply {
var current: ResolvedObject = resolved
do {
add(current.getName().text)
} while (current.getOuter()?.also { current = it } != null)
}
is ResolvedExportObject -> JsonArray().apply {
add(resolved.getName().text)
add(resolved.pkg.fileName)
}
else -> null
}
}
})
.registerTypeAdapter(FGameplayTagContainer::class.java, JsonSerializer<FGameplayTagContainer> { src, typeOfSrc, context ->
} else {
JsonObject().apply {
add("gameplay_tags", JsonArray().apply { src.gameplayTags.forEach { add(context.serialize(it)) } })
addProperty("export", src.index)
}
})
.registerTypeAdapter(FGuid::class.java, GuidSerializer())
.registerTypeAdapter(FName::class.java, JsonSerializer<FName> { src, typeOfSrc, context ->
JsonPrimitive(src.text)
})
.registerTypeAdapter(FPackageIndex::class.java, JsonSerializer<FPackageIndex> { src, typeOfSrc, context ->
val out: JsonElement

if (src.index < 0) {
val importObject = src.importObject
out = JsonArray()
out.add(context.serialize(importObject!!.objectName))
out.add(context.serialize(src.outerImportObject!!.objectName))

if (src.outerImportObject!!.outerIndex.importObject != null) {
out.add(context.serialize(src.outerImportObject!!.outerIndex.importObject!!.objectName))
}
} else {
out = JsonObject()
out.addProperty("export", src.index)

/*if (src.index > 0) {
out.addProperty("__object_name", src.exportObject!!.objectName.text)
}*/
}
})
.registerTypeAdapter(UScriptArray::class.java, JsonSerializer<UScriptArray> { src, typeOfSrc, context ->
JsonArray().apply { src.contents.forEach { add(context.serialize(it)) } }
})
.registerTypeAdapter(UScriptMap::class.java, JsonSerializer<UScriptMap> { src, typeOfSrc, context ->
JsonArray().apply {
for ((k, v) in src.mapData) {
add(JsonObject().apply {
add("key", context.serialize(k))
add("value", context.serialize(v))
})
}

out
})
.registerTypeAdapter(UScriptArray::class.java, JsonSerializer<UScriptArray> { src, typeOfSrc, context ->
JsonArray().apply { src.contents.forEach { add(context.serialize(it)) } }
})
.registerTypeAdapter(UScriptMap::class.java, JsonSerializer<UScriptMap> { src, typeOfSrc, context ->
JsonArray().apply {
for ((k, v) in src.mapData) {
add(JsonObject().apply {
add("key", context.serialize(k))
add("value", context.serialize(v))
})
}
})
.registerTypeHierarchyAdapter(FSoftObjectPath::class.java, JsonSerializer<FSoftObjectPath> { src, typeOfSrc, context ->
JsonObject().apply {
addProperty("asset_path_name", src.assetPathName.text)
addProperty("sub_path_string", src.subPathString)
}
})
.registerTypeAdapter(FStructFallback::class.java, JsonSerializer<FStructFallback> { src, typeOfSrc, context ->
JsonObject().apply { serializeProperties(this, src.properties, context) }
})
.registerTypeAdapter(FText::class.java, JsonSerializer<FText> { src, typeOfSrc, context ->
val h = if (src.textHistory is FTextHistory.None) FTextHistory.Base("", "", "") else src.textHistory
JsonObject().apply {
addProperty("string", h.text)
when (h) {
is FTextHistory.Base -> {
addProperty("namespace", h.namespace)
addProperty("key", h.key)
}
}
})
.registerTypeAdapter(FSoftObjectPath::class.java, JsonSerializer<FSoftObjectPath> { src, typeOfSrc, context ->
JsonObject().apply {
addProperty("asset_path_name", src.assetPathName.text)
addProperty("sub_path_string", src.subPathString)
}
})
.registerTypeAdapter(FStructFallback::class.java, JsonSerializer<FStructFallback> { src, typeOfSrc, context ->
JsonObject().apply { serializeProperties(this, src.properties, context) }
})
.registerTypeAdapter(FText::class.java, JsonSerializer<FText> { src, typeOfSrc, context ->
val h = if (src.textHistory is FTextHistory.None) FTextHistory.Base("", "", "") else src.textHistory
JsonObject().apply {
addProperty("string", h.text)
when (h) {
is FTextHistory.Base -> {
addProperty("namespace", h.namespace)
addProperty("key", h.key)
}
is FTextHistory.StringTableEntry -> {
add("table_id", context.serialize(h.tableId))
addProperty("key", h.key)
}
is FTextHistory.StringTableEntry -> {
add("table_id", context.serialize(h.tableId))
addProperty("key", h.key)
}
}
})
.create()
}
})
.create()

private fun serializeProperties(obj: JsonObject, properties: List<FPropertyTag>, context: JsonSerializationContext) {
properties.forEach {
obj.add(it.name.text + (if (it.arrayIndex != 0) "[${it.arrayIndex}]" else ""), context.serialize(it.tag))
// obj.add(it.name.text + (if (it.arrayIndex != 0) "[${it.arrayIndex}]" else ""), context.serialize(it.prop))
obj.add(it.name.text + (if (it.arrayIndex != 0) "[${it.arrayIndex}]" else ""), context.serialize(it.prop))
}
}

private class ExportSerializer : JsonSerializer<UExport> {
override fun serialize(src: UExport, typeOfSrc: Type, context: JsonSerializationContext): JsonElement? {
private class ExportSerializer : JsonSerializer<UObject> {
override fun serialize(src: UObject, typeOfSrc: Type, context: JsonSerializationContext): JsonElement? {
if (src is UCurveTable) {
return JsonObject().apply {
add("super_object", normal(src, context))
addProperty("curve_table_mode", src.curveTableMode.name)
add("row_map", JsonArray(src.rowMap.size).apply {
for ((k, v) in src.rowMap) {
add(JsonArray(2).apply {
add(context.serialize(k))
add(JsonObject().apply {
addProperty("export_type", when (src.curveTableMode) {
ECurveTableMode.Empty -> "Empty"
ECurveTableMode.SimpleCurves -> "SimpleCurveKey"
ECurveTableMode.RichCurves -> "RichCurveKey"
}) // this is actually wrong but we're serializing it anyways to keep compatibility
context.serialize(v).asJsonObject.entrySet().forEach {
add(it.key, it.value)
}
})
})
}
})
}
} else {
return normal(src, context)
}
}

private fun normal(src: UObject, context: JsonSerializationContext): JsonObject {
val obj = JsonObject()
if (sUseNonstandardFormat && src.export != null) obj.addProperty("object_name", src.export!!.objectName.text)
//if (sUseNonstandardFormat && src.export != null) obj.addProperty("object_name", src.export!!.objectName.text)
obj.addProperty("export_type", src.exportType)
if (src !is UObject) return obj

if (src !is UDataTable || sUseNonstandardFormat)
serializeProperties(obj, src.properties, context)
serializeProperties(obj, (src as UObject).properties, context)

if (src is UDataTable) {
if (sUseNonstandardFormat) {
Expand Down Expand Up @@ -177,7 +218,7 @@ object JWPSerializer {

private class GuidSerializer : JsonSerializer<FGuid>, JsonDeserializer<FGuid> {
override fun serialize(src: FGuid, typeOfSrc: Type, context: JsonSerializationContext): JsonElement? {
return JsonPrimitive("%08x%08x%08x%08x".format(src.part1.toInt(), src.part2.toInt(), src.part3.toInt(), src.part4.toInt()))
return JsonPrimitive("%08x%08x%08x%08x".format(src.a.toInt(), src.b.toInt(), src.c.toInt(), src.d.toInt()))
}

override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): FGuid? {
Expand All @@ -187,7 +228,7 @@ object JWPSerializer {

private class ByteArraySerializer : JsonSerializer<ByteArray>, JsonDeserializer<ByteArray> {
override fun serialize(src: ByteArray, typeOfSrc: Type, context: JsonSerializationContext): JsonElement? {
return context.serialize(ByteArrayUtils.encode(src))
return JsonPrimitive("0x" + src.printHexBinary())
}

override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ByteArray? {
Expand All @@ -196,8 +237,23 @@ object JWPSerializer {
return if (s.startsWith("0x")) {
s.substring(2).parseHexBinary()
} else {
Base64.getDecoder().decode(s);
Base64.getDecoder().decode(s)
}
}
}

fun separateCamelCase(name: String, separator: String): String {
val translation = StringBuilder()
var i = 0
val length = name.length
while (i < length) {
val character = name[i]
if (Character.isUpperCase(character) && translation.isNotEmpty()) {
translation.append(separator)
}
translation.append(character)
i++
}
return translation.toString()
}
}
Loading

0 comments on commit 60363b3

Please sign in to comment.