MinshuG / pyUE4Parse

ue4 asset parser/reader
MIT License
47 stars 15 forks source link

Could not read Texture2D correctly #9

Closed joric closed 1 year ago

joric commented 1 year ago

Could not load texture from Supraland (~UE 4.27). What am I doing wrong? WIndows 10, used VC 2017 to build the module.

Also what's up with the output format? FModel has hierarchy but here it looks just like an array of dictionaries that do not really refer to each other, I'm not sure, does order matter? How to collect hierarchical nodes? FModel can't read large files and cannot be automated so I can't use it.

ffrom UE4Parse.Assets.Objects.FGuid import FGuid
from UE4Parse.Provider import DefaultFileProvider, MappingProvider
from UE4Parse.Versions import EUEVersion, VersionContainer
from UE4Parse.Encryption import FAESKey
import logging
import json

#path = r'E:/Games/Supraland/Supraland/Content/Paks'
#prefix = 'Supraland/Content/FirstPersonBP/Maps/'

path = r'E:/Games/Supraland - Six Inches Under/SupralandSIU/Content/Paks'
prefix = 'SupralandSIU/Content/FirstPersonBP/Maps/'

logging.getLogger("UE4Parse").setLevel(logging.INFO)  # set logging level

aeskeys = { FGuid(0,0,0,0): FAESKey('0x'+'0'*64), } # unencrypted
import gc; gc.disable() # temporarily disabling garbage collector gives a huge performance boost

provider = DefaultFileProvider(path, VersionContainer(EUEVersion.LATEST))
provider.initialize()
provider.submit_keys(aeskeys)  # mount files
provider.load_localization("en")
gc.enable() # enable garbage collector again

#package_path = 'Supraland/Content/Blueprints/PlayerMap/Textures/T_Downscale0.uasset'
package_path = 'SupralandSIU/Content/Blueprints/PlayerMap/Textures/T_SIUMapV7Q0'
package = provider.try_load_package(package_path)

try:
    if texture := package.find_export_of_type("Texture2D"):
        image = texture.decode()  # returns PIL Image object
        image.save("cool_image.png", "PNG")  # save image
except Exception as e:
    print(e) # Could not read Texture2D correctly

maps = [
    'DLC2_Area0',
]

for m in maps:
    package_path = prefix + m
    package = provider.try_load_package(package_path)
    if package is not None:
        package_dict = package.get_dict() # get json serializable dict
        with open('UE4Parse.'+ m+'.json', 'w') as f:
            json.dump(package_dict, f, indent=2)
joric commented 1 year ago

this worked:

provider = DefaultFileProvider(path, VersionContainer(EUEVersion.GAME_UE4_27))

Images are fine now.

Still, can't collect hierarchy. Any help? How FModel parses that? Tried that POS FModel again, it could not parse 11Mb file, ate up 8 gigs or RAM and froze. There's an object that doesn't get referenced, but it should. What should I do to identify it? It's in DLC2_SecretLavaArea, I can only find it by the coordinates:

  {
    "Type": "SceneComponent",
    "Name": "DefaultSceneRoot",
    "Properties": {
      "RelativeLocation": {
        "X": 11637.44140625,
        "Y": 12546.80078125,
        "Z": 4227.0
      },
      "RelativeRotation": {
        "Pitch": 0.0,
        "Yaw": -81.9996109008789,
        "Roll": 0.0
      },
      "Mobility": "EComponentMobility::Static",
      "CreationMethod": "EComponentCreationMethod::Instance"
    }
  },

In FModel this object has an "outer" key ("Outer": "FortressRoot"), so I can find it by name.

Here we have hundreds of SceneComponent:DefaultSceneRoot objects, index doesn't help either, nobody refers them by index. Are there properties that should be there but skipped by the reader? FModel exports ObjectPath with index 2905 like this:

        "ObjectPath": "SupralandSIU/Content/FirstPersonBP/Maps/DLC2_SecretLavaArea.2905"

but I don't see this number in ue4parse json. Is it possible to add support for this key perhaps so I can find referred items?

Upd. Ok I did this and now I have working references to parent nodes, i.e. "ObjectName": "DefaultSceneRoot:SceneComponent.2905", (+1 rather, so it's 2906).

-                ObjectName = f"{Resource.ObjectName.string}:{Resource.ClassIndex.Name.string}"
+                ObjectName = f"{Resource.ObjectName.string}:{Resource.ClassIndex.Name.string}.{self.Index}"

Is there more elegant non-breaking way? Maybe like so:

@@ -100,6 +101,7 @@ class FPackageIndex:
             else:
                 ObjectName = f"{Resource.ObjectName.string}:{Resource.ClassIndex.Name.string}"
             return {
+                "ObjectIndex": self.Index,
                 "ObjectName": ObjectName,
                 "OuterIndex": Resource.OuterIndex.GetValue()
             }
MinshuG commented 1 year ago

https://github.com/MinshuG/pyUE4Parse/commit/5e0e6f094065cfd337c22510006d014961784423 added Outer to JSON

joric commented 1 year ago

Love you, man! I was able to collect all nodes https://gist.github.com/joric/075e4a2689cd18d69dee6caf19da33de

Upd. did the interactive map https://joric.github.io/supraland and the script that does all export automatically https://github.com/joric/supraland/blob/main/scripts/supraland_parser.py