Skip to content

Commit

Permalink
support for reading array properties, at last
Browse files Browse the repository at this point in the history
  • Loading branch information
shrimpza committed Apr 23, 2020
1 parent b51f1b2 commit 091f7d6
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 30 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ contained within these files.

## Current Implementation

- Reading all a packages' exported objects and their properties.
- Read and export textures from most supported formats.
- Light-weight memory efficient implementation.
- Use Umod package contents in combination with the package reader, to allow
inspecting and exporting objects without needing to extract the Umod
- Reading all a packages' exported objects and their properties.
- Read and export textures from most supported UE1 and UE2 formats.
- Read and export sounds in WAV format.
- Use UMOD package contents in combination with the package reader, to allow
inspecting and exporting objects without needing to extract the UMOD
contents.
- Extendable with more object readers if needed (meshes, sounds, etc).
- Reading array properties on objects is not implemented.
- There is currently no support for reading or extraction of data such as
- There is currently no support for reading or extraction of data such as
UnrealScript classes.


Expand All @@ -59,12 +59,12 @@ Property shotProp = level.property("Screenshot");
ExportedObject shotObject = pkg.objectByRef(((ObjectProperty)shotProp).value);
Texture shot = (Texture)shotObject.object();

// get and save the first mipmap (the full size texture) to file
// get and save the screenshot from the first mipmap (the full size texture) to file
Texture.MipMap[] mipMaps = shot.mipMaps();
ImageIO.write(mipMaps[0].get(), "png", Path.get("scheenshot.png").toFile());
```

In this example, we unpack/extract the contents of a UMod file to dist:
In this example, we unpack/extract the contents of a UMOD file to disk:

```java
Path dest = Paths.get("/path/to/unpack/to");
Expand Down Expand Up @@ -115,7 +115,7 @@ private String filePath(String path) {

```

For further usage examples, including reading of a umod packages contents in
For further usage examples, including reading of a UMOD packages contents in
combination with the Package reader, refer to the unit tests.


Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group = 'net.shrimpworks'
version = "1.4"
version = "1.5"

sourceCompatibility = '11'
targetCompatibility = '11'
Expand Down
60 changes: 46 additions & 14 deletions src/main/java/net/shrimpworks/unreal/packages/Package.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import net.shrimpworks.unreal.packages.entities.properties.PropertyType;
import net.shrimpworks.unreal.packages.entities.properties.StringProperty;
import net.shrimpworks.unreal.packages.entities.properties.StructProperty;
import net.shrimpworks.unreal.packages.entities.properties.UnknownArrayProperty;

/**
* An Unreal package.
Expand Down Expand Up @@ -472,13 +473,7 @@ public Object object(ExportedObject export) {
header = null;
}

List<Property> properties = new ArrayList<>();
for (int i = 0; i < MAX_PROPERTIES; i++) {
Property p = readProperty();

if (p.name.equals(Name.NONE)) break;
else properties.add(p);
}
List<Property> properties = readProperties();

// keep track of how long the properties were, so we can potentially continue reading object data from this point
long propsLength = reader.currentPosition();
Expand All @@ -490,6 +485,35 @@ public Object object(ExportedObject export) {
return newObject;
}

private List<Property> readProperties() {
List<Property> properties = new ArrayList<>();
for (int i = 0; i < MAX_PROPERTIES; i++) {
Property p = readProperty();

if (p.name.equals(Name.NONE)) break;
else {
if (p instanceof ArrayProperty.ArrayItem && !properties.isEmpty()) {
/*
Array handling:
We should magically know that if this property is part of an array, the previous
property is either an array we need to add to, or the previous property is
actually the first item of the array. In the second case, we need to replace
it with a new array property.
*/
Property lastProperty = properties.get(properties.size() - 1);
if (lastProperty instanceof ArrayProperty) {
properties.remove(lastProperty);
properties.add(((ArrayProperty)lastProperty).add((ArrayProperty.ArrayItem)p));
} else if (lastProperty.name.equals(p.name)) {
properties.remove(lastProperty);
properties.add(new ArrayProperty(((ArrayProperty.ArrayItem)p).property));
} else properties.add(((ArrayProperty.ArrayItem)p).property);
} else properties.add(p);
}
}
return properties;
}

/**
* Utility method to read an individual object property.
*
Expand All @@ -505,8 +529,8 @@ private Property readProperty() {
byte propInfo = reader.readByte();

byte type = (byte)(propInfo & 0b00001111); // bits 0 to 3 are the type
int size = (propInfo & 0b01110000) >> 4; // bits 4 to 6 is the size
boolean arrayFlag = (propInfo & 0x80) != 0; // bit 7 is either array size (??), or boolean value
int size = (propInfo & 0b01110000) >> 4; // bits 4 to 6 are the size
boolean arrayFlag = (propInfo & 0b10000000) != 0; // bit 7 is either indicates an array, or if the value is a boolean

PropertyType propType = PropertyType.get(type);

Expand Down Expand Up @@ -551,7 +575,17 @@ private Property readProperty() {
arrayIndex = reader.readByte();
}

return createProperty(name, propType, structType, arrayIndex, size, arrayFlag);
Property property = createProperty(name, propType, structType, size, arrayFlag);

/*
special case for array handling. array elements are just normal properties
with the arrayFlag set and an array index.
*/
if (arrayFlag && propType != PropertyType.BooleanProperty) {
return new ArrayProperty.ArrayItem(property, arrayIndex);
}

return property;
}

/**
Expand All @@ -561,13 +595,12 @@ private Property readProperty() {
* @param name property name
* @param type type of property
* @param structType if a struct property, the struct type
* @param arrayIndex in an array property, the index of this property within an array
* @param size the byte length of the property
* @param arrayFlag the final bit of the property header, sometimes used to infer things
* @return a new property
*/
private Property createProperty(
Name name, PropertyType type, StructProperty.StructType structType, int arrayIndex, int size, boolean arrayFlag) {
Name name, PropertyType type, StructProperty.StructType structType, int size, boolean arrayFlag) {

int startPos = reader.position();

Expand Down Expand Up @@ -618,7 +651,7 @@ private Property createProperty(
case VectorProperty:
return new StructProperty.VectorProperty(this, name, reader.readFloat(), reader.readFloat(), reader.readFloat());
case ArrayProperty:
return new ArrayProperty(this, name, new ObjectReference(this, reader.readIndex()));
return new UnknownArrayProperty(this, name, new ObjectReference(this, reader.readIndex()));
case FixedArrayProperty:
return new FixedArrayProperty(this, name, new ObjectReference(this, reader.readIndex()), reader.readIndex());
default:
Expand All @@ -627,7 +660,6 @@ private Property createProperty(
} finally {
// if we didn't read all the property's bytes somehow, fast-forward to the end of the property...
// FIXME PointRegionProperty in version >= 126 specifically seems larger than specs indicate; 7 extra bytes
// this also lets move past array payloads without consuming them yet in this version
if (reader.position() - startPos < size) {
reader.moveRelative(size - (reader.position() - startPos));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,57 @@
package net.shrimpworks.unreal.packages.entities.properties;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import net.shrimpworks.unreal.packages.Package;
import net.shrimpworks.unreal.packages.entities.Name;
import net.shrimpworks.unreal.packages.entities.ObjectReference;

public class ArrayProperty extends Property {

public final ObjectReference arrayType;
public final List<Property> values;

public ArrayProperty(Property initialValue) {
this(initialValue.pkg, initialValue.name, List.of(initialValue));
}

public ArrayProperty(Package pkg, Name name, ObjectReference arrayType) {
public ArrayProperty(Package pkg, Name name, List<Property> values) {
super(pkg, name);
this.arrayType = arrayType;
this.values = Collections.unmodifiableList(values);
}

public ArrayProperty add(ArrayItem value) {
List<Property> nextValues = new ArrayList<>(values);
nextValues.add(Math.min(value.index, nextValues.size()), value.property);
return new ArrayProperty(pkg, name, nextValues);
}

@Override
public String toString() {
return String.format("ArrayProperty [name=%s, arrayType=%s]", name, arrayType);
return String.format("ArrayProperty [name=%s, values=%s]", name, values);
}

/**
* A special magical transitional item which represents an element within
* an array.
* <p>
* The Package managing this property should unwrap the property within
* and add it to an ArrayProperty.
*/
public static class ArrayItem extends Property {

public final Property property;
public final int index;

public ArrayItem(Property property, int index) {
super(property.pkg, property.name);
this.property = property;
this.index = index;
}

@Override
public String toString() {
return String.format("ArrayItem [index=%s, property=%s]", index, property);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import net.shrimpworks.unreal.packages.entities.Name;
import net.shrimpworks.unreal.packages.entities.ObjectReference;

public class FixedArrayProperty extends ArrayProperty {
public class FixedArrayProperty extends UnknownArrayProperty {

public final int count;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package net.shrimpworks.unreal.packages.entities.properties;

import net.shrimpworks.unreal.packages.Package;
import net.shrimpworks.unreal.packages.entities.Name;
import net.shrimpworks.unreal.packages.entities.ObjectReference;

/**
* This is a property type defined by {@link PropertyType#ArrayProperty},
* identified as property type 9.
* <p>
* However, there's no documentation for this type, and it appears to
* be something that was never actually implemented or used, since array
* properties are simply regular properties, with indexes and things
* defined by the property header.
*/
public class UnknownArrayProperty extends Property {

public final ObjectReference arrayType;

public UnknownArrayProperty(Package pkg, Name name, ObjectReference arrayType) {
super(pkg, name);
this.arrayType = arrayType;
}

@Override
public String toString() {
return String.format("ArrayProperty [name=%s, arrayType=%s]", name, arrayType);
}
}
11 changes: 11 additions & 0 deletions src/test/java/net/shrimpworks/unreal/packages/PackageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import net.shrimpworks.unreal.packages.entities.objects.Object;
import net.shrimpworks.unreal.packages.entities.objects.Polys;
import net.shrimpworks.unreal.packages.entities.objects.Texture;
import net.shrimpworks.unreal.packages.entities.properties.ArrayProperty;
import net.shrimpworks.unreal.packages.entities.properties.ObjectProperty;
import net.shrimpworks.unreal.packages.entities.properties.Property;
import net.shrimpworks.unreal.packages.entities.properties.StringProperty;
Expand Down Expand Up @@ -70,6 +71,16 @@ public void readGeometryTypes() throws IOException {
}
}

@Test
public void arrayProperties() throws IOException {
try (Package pkg = new Package(unrMap)) {
ExportedObject pathNode = pkg.objectsByClassName("DynamicAmbientSound").stream().findFirst().get();
Property sounds = pkg.object(pathNode).property("Sounds");
assertTrue(sounds instanceof ArrayProperty);
assertFalse(((ArrayProperty)sounds).values.isEmpty());
}
}

@Test
public void readLevelInfo() throws IOException {
try (Package pkg = new Package(unrMap)) {
Expand Down

0 comments on commit 091f7d6

Please sign in to comment.