HearthSim / UnityPack

Python deserialization library for Unity3D Asset format
https://hearthsim.info/
MIT License
720 stars 153 forks source link

Support for UnityWebData1.0 files #74

Open andyearnshaw opened 6 years ago

andyearnshaw commented 6 years ago

Example UnityWebData1.0 File

The signature on these files is UnityWebData1.0.

These files just appear to contain one or more .unity3d files, so it would be great to cut out the middle-man of extracting them and then extracting the .unity3d files.

einstein95 commented 5 years ago

QuickBMS script for these files:

idstring "UnityWebData1.0\0"
get HEADLEN long
do
    get OFF long
    get SIZE long
    get NAMEZ long
    getdstring NAME NAMEZ
    log NAME OFF SIZE
    savepos i
while i < HEADLEN
mildsunrise commented 4 years ago

python code

from unitypack.utils import BinaryReader

SIGNATURE = 'UnityWebData1.0'

class DataFile:
    def load(self, file):
        buf = BinaryReader(file, endian="<")
        self.path = file.name

        self.signature = buf.read_string()
        header_length = buf.read_int()
        if self.signature != SIGNATURE:
            raise NotImplementedError('Invalid signature {}'.format(repr(self.signature)))

        self.blobs = []
        while buf.tell() < header_length:
            offset = buf.read_int()
            size = buf.read_int()
            namez = buf.read_int()
            name = buf.read_string(namez)
            self.blobs.append({ 'name': name, 'offset': offset, 'size': size })
        if buf.tell() > header_length:
            raise NotImplementedError('Read past header length, invalid header')

        for blob in self.blobs:
            buf.seek(blob['offset'])
            blob['data'] = buf.read(blob['size'])
            if len(blob['data']) < blob['size']:
                raise NotImplementedError('Invalid size or offset, reading past file')

usage:

import os
f = open('data', 'rb')
df = DataFile()
df.load(f)
EXTRACTION_DIR = 'extracted'
for blob in df.blobs:
    print('extracting @ {}:\t{} ({})'.format(blob['offset'], blob['name'], blob['size']))
    dest = os.path.join(EXTRACTION_DIR, blob['name'])
    os.makedirs(os.path.dirname(dest), exist_ok=True)
    with open(dest, 'wb') as f:
        f.write(blob['data'])
mildsunrise commented 4 years ago

Maybe this is obvious, but once it's extracted, remember to set base_path to the extracted dir:

file = open(os.path.join(EXTRACTION_DIR, 'data.unity3d'), 'rb')
env = unitypack.environment.UnityEnvironment(base_path=EXTRACTION_DIR)
bundle = unitypack.load(file, env)

Or start python from the extracted directory, because base_path defaults to the current directory. This should be documented, IMO.

Otherwise it won't find the assets referenced from the bundle, i.e.:

KeyError: "No such asset: 'sharedassets0.resource'"