Skip to content

Commit

Permalink
More material support
Browse files Browse the repository at this point in the history
No more guessing algorithms
  • Loading branch information
Amrsatrio committed Jun 19, 2020
1 parent 81b3cde commit 304f23a
Show file tree
Hide file tree
Showing 10 changed files with 917 additions and 618 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# BlenderUmap
A Java tool to export Fortnite .umaps and a Python script to import it. More games will be supported as time goes on.

## Usage
* Before running the tool you need to have 64-bit Java installed. [Get it here. (choose 64-bit Offline)](https://www.java.com/en/download/manual.jsp)\
**32-bit Java won't work with this tool!**
* Extract the zip file
* Edit config.json to suit your needs
* Execute the bat
* Make sure you have the [Blender PSK import/export plugin](https://github.com/Befzz/blender3d_import_psk_psa) **at least version 2.7.13** installed. If you use prior versions of the plugin the scaling of the props will appear broken.
* In Blender, import the python file as a script (via scripting tab)
* (Optional) If you want to see the output of the script, show the system console (Window > Toggle System Console)
* Click Run Script (Alt+P), Blender will freeze if all goes well
* Profit!

## config.json
* **`PaksDirectory`: Path to the Paks folder.**
* `UEVersion`: Unreal Engine version. Supports up to UE4.25.
* **`EncryptionKeys`: List of AES keys to use for loading the paks**
* `Guid`: Identify a pak by its encryption key GUID. Use `00000000000000000000000000000000` (32 0's) to refer to the main paks.
* `FileName`: Alternatively, you can use this to identify a pak by its file name.
* `Key`: The pak's encryption key, in either hex (starting with "0x") or base64.
* `bReadMaterials`: Export materials. Materials are experimental! Not all imported materials will be perfect. **Min. 24GB of RAM recommended!**
* `bRunUModel`: Run UModel within the exporting process to export meshes, materials, and textures.
* `UModelAdditionalArgs`: Additional command line args when starting UModel.
* `bDumpAssets`: Save assets as JSON format, useful for debugging.
* **`ExportPackage`: The .umap you want to export.**
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group 'com.tb24'
version '0.1.1'
version '0.2.0'

sourceCompatibility = 1.8

Expand Down Expand Up @@ -38,13 +38,15 @@ repositories {
mavenCentral()
maven { url 'https://dl.bintray.com/fungamesleaks/mavenRepo' }
// maven { url 'https://libraries.minecraft.net' }
maven { url 'https://jitpack.io' }
}

dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
implementation 'com.google.code.gson:gson:2.8.6'
// implementation 'com.mojang:brigadier:1.0.17'
implementation 'me.fungames:JFortniteParse:+' // :3.0.2'
implementation 'org.slf4j:slf4j-api:1.7.30'
testImplementation 'junit:junit:4.12'
}

Expand Down
36 changes: 36 additions & 0 deletions src/main/java/com/tb24/blenderumap/ByteArrayUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* (C) 2020 amrsatrio. All rights reserved.
*/
package com.tb24.blenderumap;

import java.nio.ByteOrder;

public class ByteArrayUtils {
private static final char[] LOOKUP_TABLE_LOWER = new char[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66};
private static final char[] LOOKUP_TABLE_UPPER = new char[]{0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46};

// https://stackoverflow.com/a/58118078
public static String encode(byte[] byteArray, boolean upperCase, ByteOrder byteOrder) {
// our output size will be exactly 2x byte-array length
final char[] buffer = new char[byteArray.length * 2];

// choose lower or uppercase lookup table
final char[] lookup = upperCase ? LOOKUP_TABLE_UPPER : LOOKUP_TABLE_LOWER;

int index;
for (int i = 0; i < byteArray.length; i++) {
// for little endian we count from last to first
index = (byteOrder == ByteOrder.BIG_ENDIAN) ? i : byteArray.length - i - 1;

// extract the upper 4 bit and look up char (0-A)
buffer[i << 1] = lookup[(byteArray[index] >> 4) & 0xF];
// extract the lower 4 bit and look up char (0-A)
buffer[(i << 1) + 1] = lookup[(byteArray[index] & 0xF)];
}
return new String(buffer);
}

public static String encode(byte[] byteArray) {
return encode(byteArray, false, ByteOrder.BIG_ENDIAN);
}
}
85 changes: 73 additions & 12 deletions src/main/java/com/tb24/blenderumap/JWPSerializer.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/*
* (C) 2020 amrsatrio. All rights reserved.
*/
package com.tb24.blenderumap

import com.google.gson.*
Expand All @@ -6,9 +9,10 @@ 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.assets.objects.FTextHistory.Base
import me.fungames.jfortniteparse.ue4.assets.util.FName
import me.fungames.jfortniteparse.util.parseHexBinary
import java.lang.reflect.Type
import java.util.*

/**
* Provides John Wick Parse JSON data structure for JFortniteParse objects.
Expand All @@ -21,25 +25,38 @@ object JWPSerializer {
.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`)
addProperty("name", src.name.text)
add("name", context.serialize(src.name))
}
} 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))
}
})
.registerTypeAdapter(FGameplayTagContainer::class.java, JsonSerializer<FGameplayTagContainer> { src, typeOfSrc, context ->
JsonObject().apply {
add("gameplay_tags", JsonArray().apply { src.gameplayTags.forEach { add(it.text) } })
add("gameplay_tags", JsonArray().apply { src.gameplayTags.forEach { add(context.serialize(it)) } })
}
})
.registerTypeAdapter(FGuid::class.java, JsonSerializer<FGuid> { src, typeOfSrc, context ->
JsonPrimitive("%08x%08x%08x%08x".format(src.part1.toInt(), src.part2.toInt(), src.part3.toInt(), src.part4.toInt()))
.registerTypeAdapter(FGuid::class.java, GuidSerializer())
.registerTypeAdapter(FLinearColor::class.java, JsonSerializer<FLinearColor> { src, typeOfSrc, context ->
JsonObject().apply {
addProperty("r", src.r)
addProperty("g", src.g)
addProperty("b", src.b)
addProperty("a", src.a)
}
})
.registerTypeAdapter(FName::class.java, JsonSerializer<FName> { src, typeOfSrc, context ->
JsonPrimitive(src.text)
Expand All @@ -53,11 +70,11 @@ object JWPSerializer {
out.addProperty("export", src.index)
} else if (importObject != null) {
out = JsonArray()
out.add(importObject.objectName.text)
out.add(src.outerImportObject!!.objectName.text)
out.add(context.serialize(importObject.objectName))
out.add(context.serialize(src.outerImportObject!!.objectName))

if (src.outerImportObject!!.outerIndex.importObject != null) {
out.add(src.outerImportObject!!.outerIndex.importObject!!.objectName.text)
out.add(context.serialize(src.outerImportObject!!.outerIndex.importObject!!.objectName))
}
}

Expand All @@ -73,6 +90,16 @@ object JWPSerializer {
.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))
})
}
}
})
.registerTypeAdapter(FSoftObjectPath::class.java, JsonSerializer<FSoftObjectPath> { src, typeOfSrc, context ->
JsonObject().apply {
addProperty("asset_path_name", src.assetPathName.text)
Expand All @@ -83,11 +110,19 @@ object JWPSerializer {
JsonObject().apply { serializeProperties(this, src.properties, context) }
})
.registerTypeAdapter(FText::class.java, JsonSerializer<FText> { src, typeOfSrc, context ->
val h = if (src.textHistory is FTextHistory.None) Base("", "", "") else src.textHistory
val h = if (src.textHistory is FTextHistory.None) FTextHistory.Base("", "", "") else src.textHistory
JsonObject().apply {
addProperty("string", (h as Base).sourceString)
addProperty("namespace", h.nameSpace)
addProperty("key", h.key)
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)
}
}
}
})
.registerTypeAdapter(FVector::class.java, JsonSerializer<FVector> { src, typeOfSrc, context ->
Expand Down Expand Up @@ -127,4 +162,30 @@ object JWPSerializer {
return obj
}
}

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()))
}

override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): FGuid? {
return FGuid(json.asString)
}
}

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

override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ByteArray? {
val s = json.asString

return if (s.startsWith("0x")) {
s.substring(2).parseHexBinary()
} else {
Base64.getDecoder().decode(s);
}
}
}
}
Loading

0 comments on commit 304f23a

Please sign in to comment.